当前位置: 首页 > news >正文

从零搭建 VisionMaster 自动上传系统

🧭 从零搭建 VisionMaster 自动上传系统

这篇文章完整记录了我在 Ubuntu 服务器上部署 FTP 服务(vsftpd)、通过 WinSCP 客户端验证连接、并在 海康 VisionMaster 中用 C# 脚本实现自动创建 DFS 文件与图像存储目录 的全过程。


🧩 一、项目背景与需求说明

在实际的生产检测场景中(例如 面板检测 / AOI / 外观检测 系统),检测设备往往需要将检测结果上传到服务器进行集中存储与 MES 对接。
而在海康的 VisionMaster 平台中,流程节点可以运行 C# 脚本,因此可以实现自定义的上传逻辑。

🔧 需求目标

实现检测系统自动在 FTP 服务器上生成对应目录与文件结构。

具体需求如下:

模块功能描述
1. 文件创建检测完成后自动创建 3 个 DFS 空文件(.pnl.mes.dmy, .pnl.dmy, .pnl
2. 图像目录创建根据 PanelID 规则(4/3/3/2/2/2)生成层级目录,用于保存检测图片
3. FTP 上传文件和目录均在远程 FTP 服务器上创建
4. 可配置参数FTP 地址、用户名、密码、设备编号、路径根目录等均可在全局变量中设定
5. 日志输出执行过程实时记录,便于调试与追踪
6. 文件覆盖控制可选择是否允许覆盖同名文件(Overwrite=1 覆盖,0 跳过)

系统整体结构如下:

VisionMaster (C# 脚本)│├─ 自动创建文件 → /data/{设备号}/...├─ 自动创建目录 → /image/{设备号}/Z667/55G/P22/08/B1/08│▼
FTP 服务器(Ubuntu + vsftpd)└─ 统一存储检测数据

📦 二、在服务器上部署 FTP 服务(vsftpd)

1️⃣ 安装 vsftpd

在 Ubuntu 上执行:

sudo apt update
sudo apt install vsftpd -y

2️⃣ 创建独立的 FTP 用户

出于安全考虑,我们不使用 root,而是创建一个独立账号:

sudo adduser vmftp

设置密码后,系统会自动创建主目录 /home/vmftp

3️⃣ 修改配置文件

编辑 /etc/vsftpd.conf

sudo nano /etc/vsftpd.conf

确认或添加以下内容(建议直接替换原文件):

# 基础设置
listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
write_enable=YES# 允许本地用户上传
chroot_local_user=YES
allow_writeable_chroot=YES# 支持中文文件名
utf8_filesystem=YES# 开启被动模式端口范围(根据需要调整)
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
# 替换为你的公网 IP
pasv_address=公网 IP# 安全选项
user_sub_token=$USER
local_root=/home/$USER
seccomp_sandbox=NO

保存后重启:

sudo systemctl restart vsftpd
sudo systemctl enable vsftpd
sudo systemctl status vsftpd

如果显示:

Active: active (running)

说明配置成功。

4️⃣ 设置目录权限

创建两个存储目录:

sudo mkdir -p /home/vmftp/data/800MC0
sudo mkdir -p /home/vmftp/image/800MC0
sudo chown -R vmftp:vmftp /home/vmftp

5️⃣ 防火墙放行

如果启用了 UFW:

sudo ufw allow 21/tcp
sudo ufw allow 40000:40100/tcp
sudo ufw reload

🖥️ 三、FTP 客户端工具对比

客户端名称主要特点是否中文推荐度
WinSCP免费、稳定、界面直观,可保存站点配置✅ 支持中文⭐⭐⭐⭐⭐
FileZilla开源跨平台,界面较复杂,适合开发者✅ 支持中文⭐⭐⭐⭐
FlashFXP支持多站点并发、同步上传,但为付费软件✅ 支持中文⭐⭐⭐
Cyberduck界面简洁,支持 WebDAV、S3、FTP 等协议❌ 英文界面⭐⭐
Windows 自带 FTP命令行简洁但功能有限,适合基础调试❌ 英文命令行

👉 推荐使用 WinSCP:简单好用,能快速验证你的 FTP 是否配置正确。

WinSCP 连接配置

参数示例值
协议FTP
加密不加密
主机名IP 地址
端口号21
用户名vmftp
密码(刚设置的)
传输模式被动 (Passive)

登录后右侧显示 /(实际对应 /home/vmftp)。
如果能上传文件 → 表示 FTP 服务工作正常。


⚙️ 四、VisionMaster 实现自动创建文件与目录

1️⃣ 创建全局变量

在 VisionMaster → 全局变量 → 添加以下变量组(建议命名为 “FTP配置”):

序号名称类型说明
1HoststringFTP 服务器地址,例如 1.1.1.1
2UsernamestringFTP 用户名,例如 vmftp
3PasswordstringFTP 密码
4PanelIDstring面板编号(例如 Z66755GP2208B108
5StepIDstring工站编号
6EQIDstring设备编号
7DeviceIDstring存图设备号
8DataRootstring数据根目录(示例:/data/800MC0
9ImageRootstring图像根目录(示例:/image/800MC0
10Overwriteint是否覆盖(1=覆盖,0=跳过)

在这里插入图片描述

2️⃣ 创建 C# 脚本节点

在流程中添加 C# Script Node,粘贴以下代码:

✅ 已在 .NET Framework 4.6.1 下测试通过

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Windows.Forms;
using Script.Methods;
/************************************
Shell Module default code: using .NET Framwwork 4.6.1
*************************************/
public partial class UserScript:ScriptMethods,IProcessMethods
{	//the count of process//执行次数计数int processCount ;// 私有日志缓冲:用于拼接日志,再一次性写到 Log(避免读取 Log)private StringBuilder logBuf = new StringBuilder();/// <summary>/// Initialize the field's value when compiling/// 预编译时变量初始化/// </summary>public void Init(){//You can add other global fields here//变量初始化,其余变量可在该函数中添加AppendLog("Init 开始");processCount = 0;logBuf.Clear();}/// <summary>/// Enter the process function when running code once/// 流程执行一次进入Process函数/// </summary>/// <returns></returns>public bool Process(){//You can add your codes here, for realizing your desired function//每次执行将进入该函数,此处添加所需的逻辑流程处理	logBuf.Clear();AppendLog("Process 开始");try {AppendLog("Host=" + Host + ", User=" + Username);AppendLog("PanelID=" + PanelID + ", StepID=" + StepID + ", EQID=" + EQID + ", DeviceID=" + DeviceID);AppendLog("DataRoot=" + DataRoot + ", ImageRoot=" + ImageRoot + ", Overwrite=" + Overwrite);DateTime now = DateTime.Now;// 创建DFS文件string baseNameLocal = CreateDfsFiles(Host, Username, Password, PanelID, StepID, EQID, now, Overwrite == 1);AppendLog("DFS文件创建完成: base=" + baseNameLocal);FileBaseName = baseNameLocal;// 创建图片路径string imageDirLocal = EnsureImageFolders(Host, Username, Password, DeviceID, PanelID);AppendLog("图片目录创建完成: " + imageDirLocal);ImageDir = imageDirLocal;processCount++;AppendLog("Process 结束(第 " + processCount + " 次)");// 输出日志Log = logBuf.ToString();return true;}catch (WebException wex) {string msg = "WebException: " + wex.Message;FtpWebResponse r = wex.Response as FtpWebResponse;if (r != null) {msg += " | Status=" + r.StatusCode + " (" + r.StatusDescription + ")";}AppendLog(msg);Log = logBuf.ToString();return false;}catch (Exception ex) {AppendLog("Exception: " + ex.Message);Log = logBuf.ToString();return false;}}// ========== 创建三个 0KB 文件 ==========private string CreateDfsFiles(string host, string user, string pass, string panelId, string stepId, string eqId, DateTime ts, bool overwrite){string date = ts.ToString("yyyyMMdd");string time = ts.ToString("HHmmss");string baseName = panelId + "_" + stepId + "_" + eqId + "_" + date + "_" + time;string[] files = new string[] {baseName + ".pnl.mes.dmy",baseName + ".pnl.dmy",baseName + ".pnl"};AppendLog("确保目录存在: " + DataRoot);EnsureFtpDirectory(host, user, pass, DataRoot);for (int i = 0; i < files.Length; i++) {string remotePath = DataRoot + "/" + files[i];AppendLog("上传(0KB): " + remotePath);UploadBytes(host, user, pass, remotePath, new byte[0], overwrite);}return baseName;}// ========== 创建存图目录(4/3/3/2/2/2 结构) ==========private string EnsureImageFolders(string host, string user, string pass, string deviceId, string panelId){string[] seg = SplitPanelId(panelId);// /image/800MC0/{DeviceID}/{4}/{3}/{3}/{2}/{2}/{2}string fullPath = ImageRoot + "/" + deviceId + "/" + string.Join("/", seg);AppendLog("确保图片目录存在: " + fullPath);EnsureFtpDirectory(host, user, pass, fullPath);return fullPath;}// ========== PanelID 分段工具:4/3/3/2/2/2 ==========private string[] SplitPanelId(string panelId){int[] spec = new int[] { 4, 3, 3, 2, 2, 2 };int needLen = 0;for (int i = 0; i < spec.Length; i++) {needLen += spec[i];}if (string.IsNullOrEmpty(panelId) || panelId.Length < needLen) {throw new Exception("PanelID长度不足(需≥" + needLen.ToString() + "):当前为 " + (panelId == null ? "null" : panelId));}string[] segs = new string[spec.Length];int pos = 0;for (int i = 0; i < spec.Length; i++) {segs[i] = panelId.Substring(pos, spec[i]);pos += spec[i];}return segs;}// ========== 逐级确保 FTP 目录存在 ==========private void EnsureFtpDirectory(string host, string user, string pass, string fullPath){string trimmed = fullPath.Trim('/');if (trimmed.Length == 0) {return;}string[] parts = trimmed.Split('/');string cur = "";for (int i = 0; i < parts.Length; i++) {cur += "/" + parts[i];TryMakeDirectory(host, user, pass, cur);}}private void TryMakeDirectory(string host, string user, string pass, string path){// ftp://{host}{path}string uri = "ftp://" + host + Uri.EscapeUriString(path);FtpWebRequest req = (FtpWebRequest)WebRequest.Create(uri);req.Method = WebRequestMethods.Ftp.MakeDirectory;req.Credentials = new NetworkCredential(user, pass);req.UsePassive = true;req.UseBinary = true;req.KeepAlive = false;try {using (FtpWebResponse resp = (FtpWebResponse)req.GetResponse()) { }}catch (WebException ex) {FtpWebResponse resp = ex.Response as FtpWebResponse;if (resp != null && resp.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable) {AppendLog("mkdir Exists: " + path);}else {AppendLog("mkdir FAIL: " + path + " | " + ex.Message);throw;}}}// ========== 上传字节(用于 0KB 文件) ==========private void UploadBytes(string host, string user, string pass, string remotePath, byte[] data, bool overwrite){if (!overwrite && FtpFileExists(host, user, pass, remotePath)) {AppendLog("跳过上传(已存在): " + remotePath);return;}string uri = "ftp://" + host + Uri.EscapeUriString(remotePath);FtpWebRequest req = (FtpWebRequest)WebRequest.Create(uri);req.Method = WebRequestMethods.Ftp.UploadFile;req.Credentials = new NetworkCredential(user, pass);req.UsePassive = true;req.UseBinary = true;req.KeepAlive = false;using (Stream s = req.GetRequestStream()) {if (data != null && data.Length > 0) {s.Write(data, 0, data.Length);}// data.Length==0 时,直接关闭流即可实现 0KB 文件}using (FtpWebResponse resp = (FtpWebResponse)req.GetResponse()) { }AppendLog("上传成功: " + remotePath);}// ========== 探测文件是否存在 ==========private bool FtpFileExists(string host, string user, string pass, string remotePath){try {string uri = "ftp://" + host + Uri.EscapeUriString(remotePath);FtpWebRequest req = (FtpWebRequest)WebRequest.Create(uri);req.Method = WebRequestMethods.Ftp.GetFileSize;req.Credentials = new NetworkCredential(user, pass);req.UsePassive = true;req.UseBinary = true;req.KeepAlive = false;using (FtpWebResponse resp = (FtpWebResponse)req.GetResponse()) { }return true;}catch (WebException ex) {FtpWebResponse resp = ex.Response as FtpWebResponse;if (resp != null && resp.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable) {return false;}AppendLog("GetFileSize 异常: " + ex.Message);throw;}}// ========== 日志工具 ==========private void AppendLog(string line){string ts = DateTime.Now.ToString("HH:mm:ss.fff");logBuf.Append("[").Append(ts).Append("] ").Append(line).Append('\n');}
}

在这里插入图片描述
在这里插入图片描述


📜 五、调试输出日志示例

脚本执行后,Log 变量会打印详细信息:

[11:39:58.924] Process 开始
[11:39:59.460] mkdir Exists: /data
[11:40:00.245] 上传成功: /data/800MC0/Z66755GP2208B108_700400_M1OLB0350_20251031_113958.pnl.mes.dmy
[11:40:09.397] 图片目录创建完成: /image/800MC0/M1OLB0350/Z667/55G/P22/08/B1/08
[11:40:09.397] Process 结束 (第 1 次)

在 WinSCP 中可见:

/home/vmftp/data/800MC0/
├── Z66755GP2208B108_700400_M1OLB0350_20251031_113958.pnl
├── ...
/home/vmftp/image/800MC0/M1OLB0350/Z667/55G/P22/08/B1/08/

在这里插入图片描述


🎯 六、总结

模块说明
vsftpd轻量、稳定的 FTP 服务,支持被动模式与中文路径
WinSCP简洁易用的 GUI 客户端,调试阶段查看非常方便
VisionMaster 脚本自动生成 3 个 DFS 空文件并创建图像目录结构(4/3/3/2/2/2)
日志系统脚本内部记录所有上传行为,方便排查
http://www.dtcms.com/a/554246.html

相关文章:

  • 微信小程序因视频播放不合规问题解决,微信小程序包含视频功能审核不通过解决方案
  • 江苏网站建设要多少钱html5做图网站
  • 企业网站建站意义建筑师必看的16部纪录片
  • BLDC直流无刷电机开环与PID闭环无扰切换
  • 泗水做网站ys178万能优化大师下载
  • 3D城市模型COLLADA数据格式详解
  • 外贸网站建设推广公司价格网站建设基础知识及专业术语
  • Go Web 编程快速入门 20 - 附录D:ORM 简介(可选,GORM)
  • 长春制作手机网站门户网站制作方法
  • 遵义网站开发的公司有哪些虚拟机iis网站建设
  • 网站备案号是什么能看人与动物做的网站
  • Mermaid语法、实战
  • DOM Attribute
  • php 企业网站 后台图片上传ps怎么制作网页
  • 建设对公银行网站打不开想做电商网站运营要怎么做
  • 服务器偶尔连接超时connection timedout
  • NestJS 路由顺序问题解决指南
  • 做的最好的手机网站秦皇岛建设规划
  • 苏州网站优化排名推广做网站分辨率多少
  • 做淘宝浏览单的网站创意设计文案
  • 手机网站建设视频教程_公司网络营销的方案
  • 喀什住房和城乡建设局网站ui设计到底能不能学
  • KingbaseES 表空间与模式优化策略深度研究报告
  • 国家网络安全事件报告管理办法
  • 网站建设流国际新闻最新消息今天2023
  • 沙利文报告:连续8年全球第一,影石全景相机市占率升至85%
  • Linux驱动开发指南
  • 专注于网站营销服务wordpress 主页排序
  • RSL3 别名:1S,3R-RSL3(AbMole)
  • 做快三网站平果县免费网站哪家好