简介基于Python的PyScada工业自动化系统设计源码是一套面向个人开发者、中小企业至大型工业场景的开源自动化解决方案重点解决工业过程的数据采集、监控与控制问题。压缩包共包含509个文件以264个Python脚本为核心支撑后端逻辑与数据处理另有70个JavaScript脚本、29个HTML文件、23张PNG图片及CSS、reStructuredText文档等构成前端界面与说明体系整体大小约10.27MB结构清晰且便于二次开发。项目中setup.py、AUTHORS、README.rst、LICENSE、tests、docker、docs等模块齐备既适合快速部署也利于深入学习工业自动化系统的设计思路。目前已有122人学习下载适合自动化工程师、Python开发者及工业物联网学习者研究参考可借助其模块化目录快速理解从数据采集到监控控制的全链路实现。1. PyScada 源码包用 Python 把工业自动化监控搬进浏览器值不值得下当年我第一次给车间做设备监控时最大的阻力不是采集而是上位机组态软件的价格——一套带曲线和报警的授权动辄几千上万领导还总觉得不就是个网页嘛。PyScada 就是来打破这个局面的它是一套基于 Python 和 Django 的开源工业自动化 SCADA 源码包协议侧能接 Modbus、西门子 S7、MQTT 这类常见设备数据侧落进 MariaDB/MySQL展示侧直接用浏览器打开实时值、趋势曲线和报警列表。它适合三类人想把监控成本压下来的自动化工程师、想切入工业场景但缺完整落地方案的 Python 开发者、以及拿它做课程设计或毕设题材的学生。这篇我按源码分包 → 跑起来 → 改监控点 → 避坑 → 验证链路的顺序把能直接复现的命令和你大概率会踩的坑一次性摊开讲。2. 拆开 PyScada 的源码骨架从 Modbus 采集到 Django 页面的数据管线2.1 数据从设备到屏幕要过四道关先说清楚 PyScada 和传统组态软件的本质区别。WinCC、组态王这类产品把采集、存储、画面打成一个黑匣子你只能填表配置PyScada 把这四段全部拆开任何一段你都能用 Python 改第一段是协议驱动负责按 Modbus 寄存器地址、S7 数据块地址去读设备第二段是后台采集进程按每个设备配置的轮询周期把数据读回来第三段是 Django 模型层用 Device设备、Variable变量这些表把测点元数据和最新值落库第四段是 Web 视图层Django 的 views 把数据库里的值塞进模板前端 JavaScript 负责定时刷新和画曲线。我一般会把这条链路记成一句话设备 → 协议驱动 → 采集进程写库 → Django 视图读库 → 浏览器渲染。排查问题的时候就沿着这条线一段一段查页面没数据先看库里有没有值库里有值再看视图和模板库里没值就去查采集进程的日志。PyScada 的采集不是 Django 请求时同步去读设备的而是由一个独立的常驻进程daemon在后台轮询所以哪怕网页没人打开数据照样在往库里写。这一条如果没弄明白后面很容易出现网页能开但曲线全平的困惑。它的另一个特点是协议插件化。源码包里每个协议是一个独立的 Python 子包比如 Modbus 是pyscada.modbusS7 是pyscada.s7它们各自维护自己的模型字段和连接逻辑再通过继承或一对一关系挂到核心的 Variable 模型上。所以你想新增一个私有协议不需要动核心代码照着现有协议子包抄一个就行——这也是这套源码比很多闭源组态软件值钱的地方。2.2 源码目录逐个拆采集、模型、前端各在哪个房间拿到源码包后先别急着跑花十分钟把目录结构捋一遍后面所有改动你都知道该去哪个文件下手。下面是这套工程里最关键的几块目录 / 文件职责你改的时候碰它pyscada/models.py核心模型Device、Variable、Unit、Alarm 定义加字段、改报警逻辑pyscada/views.py渲染页面、提供 JSON 数据接口加页面、改返回格式pyscada/urls.pyURL 路由挂新页面时注册地址pyscada/modbus/Modbus 协议子包含连接与读写实现改超时、加协议地址解析templates/Django 模板HMI 页面都在这里改布局、塞自定义图表static/JS/CSS曲线绘制和前端逻辑改刷新频率、图表样式manage.pyDjango 管理入口跑 migrate、runserver、采集进程这里面最容易忽略的是pyscada/modbus/这类协议子包下的models.py它定义的是 Modbus 协议专属的扩展字段比如功能码、寄存器地址、数据类型、字节序。你在页面上配好的一个测点最终会同时在核心 Variable 表和协议子表里各有一行记录两者通过一对一关系关联。所以排查明明配了变量但采集不上的问题时要记得去协议子包的模型里核对字段是否完整。打开核心models.pyVariable 模型的核心字段大致是这样不同小版本字段名会微调以你下的源码为准class Variable(models.Model): name models.CharField(max_length100) # 测点名称如 1#炉出口温度 description models.TextField(blankTrue) # 备注写安装位置、仪表编号 unit models.ForeignKey(Unit, on_deletemodels.PROTECT) # 单位关联 Unit 表 value_class models.CharField(max_length50) # 数据类型Boolean/Int/Float... value models.FloatField(default0) # 最新值采集进程实时更新 timestamp models.DateTimeField(auto_nowTrue) # 最后更新时间 has_alarm models.BooleanField(defaultFalse) # 是否启用报警 alarm_low models.FloatField(nullTrue, blankTrue) # 低报警阈值 alarm_high models.FloatField(nullTrue, blankTrue) # 高报警阈值 warn_low models.FloatField(nullTrue, blankTrue) # 低预警阈值 warn_high models.FloatField(nullTrue, blankTrue) # 高预警阈值这里我要单独解释一个容易看懵的点value存的是最新值但历史数据不在这个字段里。PyScada 的历史曲线走的是另一套数据记录机制采集进程会把每次轮询的结果按时间追加到历史记录中。所以你在数据库里会看到最新值表和历史值表并存页面上曲线画的是历史表卡片上实时数读的是最新值字段。调试时如果发现实时值在跳、曲线却是平的先去确认历史表有没有新记录写入。3. 把源码跑起来环境、数据库初始化与第一条实时曲线3.1 环境准备Python 版本、依赖与协议扩展包这套源码对 Python 版本不算挑剔3.8 到 3.10 我都跑过再新的版本建议先确认依赖兼容性。第一步是建虚拟环境并安装依赖在 Linux 上的完整过程如下# 克隆或解压源码后进入工程目录 cd pyscada # 创建并激活 Python 虚拟环境 python3 -m venv venv source venv/bin/activate # 升级 pip避免旧版解析依赖出问题 pip install --upgrade pip # 先装基础依赖Django、numpy、dateutil 等都在 requirements 里 pip install -r requirements.txt # 再装 Modbus 协议扩展包装完才能建 Modbus 设备 pip install pyscada[modbus]这段命令里有三个值得说的细节。第一python3 -m venv venv会创建一个干净的 Python 环境后面所有依赖都装在这里不会污染系统 Python——工业现场的机器通常还跑着别的服务我吃过乱装依赖把环境搞坏的亏所以虚拟环境这步不要省。第二pyscada[modbus]是协议扩展的安装写法方括号里的名字对应你真正要用的协议比如pyscada[s7]、pyscada[opc]如果下载的这版源码不支持这种 extra 写法就手动pip install pymodbus补齐。第三装完后用pip list | grep pyscada确认扩展包确实装上否则后面采集进程会直接报 ImportError还不太好排查。如果你是在 VSCode 里做开发记得在.vscode/settings.json里把 Python 解释器指到虚拟环境路径否则终端能跑、编辑器里调试却找不到包这种环境问题非常消耗耐心。3.2 初始化数据库与超级用户PyScada 官方文档推荐的生产数据库是 MariaDB 或 MySQL因为采集进程要高频写历史数据SQLite 在这种负载下容易锁库。我建议哪怕只是试验也直接上 MariaDB省得后面迁移。先建库建用户mysql -uroot -p -e CREATE DATABASE pyscada CHARACTER SET utf8mb4; CREATE USER pyscadalocalhost IDENTIFIED BY pyscada123; GRANT ALL PRIVILEGES ON pyscada.* TO pyscadalocalhost; 然后在工程的 settings 里把数据库配置指向这个库。找到pyscada/settings.py里的DATABASES段改成下面这样DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: pyscada, # 上一步建的库名 USER: pyscada, PASSWORD: pyscada123, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }注意utf8mb4这个字符集很关键。工业测点名称里经常有 ℃、± 这类符号如果用默认的utf8建表阶段就可能报 Specified key was too long 或者中文乱码。接下来执行建表和创建管理账号# 把模型同步成数据库表 python manage.py migrate # 创建 Django 超级用户用于登录管理后台 python manage.py createsuperuser # 先起 Web 服务验证页面能开 python manage.py runserver 0.0.0.0:8080migrate会创建几十张表看到OK结尾就说明模型没问题。createsuperuser交互式输入用户名、邮箱、密码密码要求不算严本地试验随便设生产环境务必用强密码。runserver监听 8080 端口浏览器访问http://127.0.0.1:8080/admin能出现 Django 后台登录页说明 Web 这一段已经通了。3.3 用 Modbus 模拟器让第一条曲线动起来现场设备不好找我调试 PyScada 的习惯是先写一个 Modbus 模拟从站把数据链路打通再去接真设备。用 pymodbus 写一个最小模拟器监听 1502 端口模拟一个温度在 25℃ 上下波动的变送器# mock_modbus.py —— 模拟一台只读温度变送器 import math import time import threading from pymodbus.server import StartTcpServer from pymodbus.datastore import (ModbusSlaveContext, ModbusServerContext, ModbusDataBlock) def update_registers(context): 每秒把 0 号保持寄存器写成当前温度*10模拟仪表实时刷新 while True: # 温度在 20~30℃ 之间按正弦波动乘 10 是模拟变送器的精度习惯 current int((25 5 * math.sin(time.time() / 5)) * 10) context[0].setValues(3, 0, [current]) time.sleep(1) def make_context(): store ModbusSlaveContext( diModbusDataBlock([0] * 100), # 离散输入 coModbusDataBlock([0] * 100), # 线圈 hrModbusDataBlock([0] * 100), # 保持寄存器 irModbusDataBlock([0] * 100), # 输入寄存器 ) return ModbusServerContext(slaves{1: store}, singleTrue) if __name__ __main__: ctx make_context() threading.Thread(targetupdate_registers, args(ctx,), daemonTrue).start() # 监听 127.0.0.1:1502unit id 为 1 StartTcpServer(contextctx, address(127.0.0.1, 1502))这段代码里setValues(3, 0, [current])的第一个参数 3 表示保持寄存器区0 是起始地址current是要写入的值列表。singleTrue表示不管请求里的 unit id 是多少都返回同一份数据测试时省事。跑起来后可以用 pymodbus 自带客户端工具modbus_client或者python manage.py shell里手动读一次验证from pymodbus.client import ModbusTcpClient client ModbusTcpClient(127.0.0.1, port1502) print(client.read_holding_registers(0, 1, unit1)) # 应打印出 200~300 之间的数 client.close()模拟器通了之后回到 Django 管理后台依次创建站点Site→ 设备Device协议选 Modbus TCP填 127.0.0.1 和 1502→ 变量Variable寄存器地址填 0类型选 16bit 无符号系数填 0.1。最后启动后台采集进程数据就会开始往库里写# 启动采集进程DEBUG 级别日志方便看轮询细节 # 老版本命令可能是 python manage.py pyScada以 manage.py help 输出为准 python manage.py pyscada_daemon --log DEBUG提示采集进程和runserver是两个独立进程都要保持运行。网页只是读库展示数据写入靠的是这个 daemon。4. 按工艺改监控点变量映射、报警规则与前端模板定制4.1 在管理后台挂一台设备和一组变量PyScada 的日常配置主要发生在 Django admin 后台不需要写代码就能把测点挂上去。我建议按站点 → 设备 → 变量的顺序建因为变量表里有外键引用设备设备又挂在站点下。给 Modbus TCP 设备填参数时有几个关键字段要理解清楚IP/端口不必多说Unit ID 对应 Modbus 报文里的从站地址轮询周期单位是秒超时时间一般设 1~3 秒设备异常时不要在超时上死等。变量配置是重头戏一个测点要填寄存器地址、功能码、数据类型、缩放系数四个核心参数。常见仪表测点映射我习惯用一张表先列出来再录入后台变量名寄存器地址功能码数据类型缩放系数单位1#炉温度003读保持寄存器16bit 无符号0.1℃1#炉压力203读保持寄存器32bit 浮点1.0kPa2#泵状态001读线圈Boolean——电表总电量1004读输入寄存器32bit 无符号0.01kWh这里最坑的是缩放系数。很多变送器为了保留小数位会把实际值放大 10 倍或 100 倍再放进寄存器比如 25.3℃ 存成 253。你在 PyScada 里把系数填成 0.1它读取后乘 0.1 再入库曲线和报警用的才是真实工程值。如果忘了填系数页面上看到 253℃ 这种离谱数值先别怀疑采集回去看系数。数据类型选错也是高频翻车点。16bit 和 32bit 的区别不需要多解释但要注意很多 PLC 的浮点数在寄存器里是高字在前还是低字在前字节序PyScada 的 Modbus 子包里有 word order 配置项拿到设备的接口文档后先确认这一点。4.2 给变量加报警规则并让前端实时联动报警是 SCADA 系统的半条命。在 admin 编辑变量时勾选has_alarm然后填四个阈值alarm_high高报警、alarm_low低报警、warn_high高预警、warn_low低预警。采集进程每次写完值后会顺带做一次阈值比较状态变化会记录到报警历史里同时前端页面能在几秒内刷新出报警样式。调试报警逻辑时我喜欢直接用 Django shell 批量核查哪些变量启用了报警、当前值落在什么区间# python manage.py shell 里执行 from pyscada.models import Variable for v in Variable.objects.filter(has_alarmTrue): now v.value if v.alarm_high is not None and now v.alarm_high: print(f[报警] {v.name} {now} 超过上限 {v.alarm_high}) elif v.warn_high is not None and now v.warn_high: print(f[预警] {v.name} {now} 接近上限 {v.warn_high}) else: print(f[正常] {v.name} {now})这段代码里我特意把alarm_high和warn_high分开判断因为预警和报警在页面上一般用不同颜色黄色 vs 红色。如果你要用邮件或微信通知PyScada 的核心包里带通知机制需要在 settings 里配 SMTP 服务器和接收人列表我建议先把页面报警跑通再接通知否则采集没通时全是误报邮件。4.3 改一个自己的监控页面模板定制是 PyScada 相对好上手的二次开发点。工程里templates/目录下放着 HMI 页面最简单的做法是复制index.html改掉其中的设备/变量 ID再挂一个自己的 URL。前端实时更新依赖 JavaScript 定时拉取最新值我一般在模板里写这样一段// 每 5 秒拉取一次 1 号变量的最新值并刷新页面元素 setInterval(() { // 具体路由以 urls.py 实际注册的为准这里示意取值接口 fetch(/pyscada/get-value/1) .then(r r.json()) .then(d { document.getElementById(temp1).innerText d.value ℃; if (d.value 30) { document.getElementById(temp1).className text-danger; } }) .catch(() console.warn(取值接口暂时不可用)); }, 5000);注意这里的/pyscada/get-value/是示意路由不同版本的实际地址可能在urls.py里命名不同照抄之前先看一眼源码。前端改完记得清掉浏览器缓存Django 开发模式下模板一般实时生效但如果后面切了生产模式缓存会让你怀疑改了个寂寞。5. PyScada 实战避坑采集进程、库锁与寄存器格式的五个翻车点5.1 采集进程起不来先分清楚网页能开和数据在跑是两回事现象runserver正常后台能登录页面也能打开但所有变量都是 0启动 daemon 时直接报错退出或根本没反应。原因两种常见情况。其一是协议扩展包没装全daemon 导入 Modbus 子包时抛 ImportError其二是启动命令名不对不同版本的管理命令叫pyScada还是pyscada_daemon有差异照抄网上老教程自然会翻车。解决先用python manage.py help | grep -i pyscada查到本版本的真实命令名再以 DEBUG 级别启动看日志。缺包就用第 3 章的方法补装pyscada[modbus]。从那以后我每次拿到新版本源码第一件事就是跑help确认命令名而不是翻教程。5.2 页面曲线是平的不是没采到是新值没写进历史表现象管理后台能看到变量最新值在变化但趋势图页面一直是直线或空白。原因PyScada 的实时值字段和历史曲线是两条存储路径采集进程把值写进 Variable 表后还要经过历史归档才进入曲线数据表。部分版本里归档逻辑依赖独立的配置比如采样间隔、归档开关没打开就会出现最新值在跳、曲线全平。解决去 admin 检查该设备/变量的归档配置是否启用把归档间隔设成和轮询周期一致或稍大。再不行就在数据库里查历史表的最近记录时间确认写入是否真的发生。5.3 数据全是巨值或负数寄存器按 16 位读了 32 位数据现象某个压力、温度测点读数要么是 65000 这种天价数字要么在正负值之间乱跳。原因设备实际按 32 位 IEEE 754 浮点存储你在变量配置里却选了 16bit 无符号两个寄存器被拆成两个独立值自然面目全非。这是 Modbus 采集最经典的错误没有之一。解决先查设备寄存器表确认数据宽度把变量类型改成 32bit 浮点同时确认字节序word order。改完立即无效也没关系daemon 下一轮轮询就会重新解析。5.4 SQLite 下跑几个小时就锁库开发库和生产库必须分开现象网页开始随机报 database is lockeddaemon 日志刷出一堆操作超时重启后好一阵又复发。原因PyScada 的采集进程是多线程高频写库历史归档的写入量对 SQLite 的锁机制极不友好写并发一上来就锁。这是我在项目里踩得最重的一个坑浪费了整整两天在调超时参数最后换库五分钟解决。解决按第 3 章的步骤切到 MariaDB/MySQL字符集用utf8mb4。SQLite 只适合刚下载源码时验证页面能开正式调试数据链路的第一天就该迁移。5.5 改了模板不生效Django 的模板缓存和静态文件收集现象改了templates/下的 HTML 和static/下的 JS刷新页面毫无变化。原因开发模式DEBUGTrue下模板一般即时生效但如果你按生产模式跑过collectstatic静态文件会被收集到别的目录浏览器也可能命中缓存。另外某些版本开启了模板缓存改完要重启进程。解决开发阶段保持 DEBUGTrue确实要以生产模式跑改完静态文件后重新执行python manage.py collectstatic --noinput并强制刷新浏览器CtrlF5。我在调试页面样式时养成一个习惯先开无痕窗口验证排除浏览器缓存干扰再决定要不要怀疑 Django。6. 进阶验证用 pymodbus 模拟从站把采集到报警的链路完整串一遍前面各章把 PyScada 拆成了采集、存储、展示、报警几段最后这一步是教你怎么把它们合起来做一次端到端验证。方法是让模拟从站、采集进程、报警规则协作起来人为制造一次超温事件看整条链路哪个环节掉链子。假设你已经按第 3 章跑起了模拟从站并在后台给1#炉温度配了报警alarm_high设为 30℃。现在手动把寄存器里的温度从正常区拉到报警区# trigger_alarm.py —— 手动把模拟从站的 0 号寄存器写成 320即 32℃ from pymodbus.client import ModbusTcpClient client ModbusTcpClient(127.0.0.1, port1502) # 写保持寄存器地址 0值 320unit id 1 client.write_registers(0, [320], unit1) client.close() print(已将温度寄存器置为 32℃请在页面上观察报警联动)写进去之后观察三件事第一后台变量详情页的当前值是否在几秒内变成 32第二趋势曲线上这一时刻是否出现一个向上的阶跃第三报警区域是否出现该变量的高报警记录。如果前两步通过但第三步没有问题多半出在报警阈值或单位换算上——寄存器里存的是 320换算系数 0.1 后是 32℃但报警判断发生在换算前还是换算后不同版本有差异这需要看源码里阈值比较的上下文。链路全部打通后我的经验是再花十分钟做一个反向验证把寄存器值写回正常区间确认报警能在下一个轮询周期内复位。能自动复位的报警才敢交给现场否则半夜一个假报警能把值班人吓到打车进场。这套验证流程我在每个新项目开始前都会完整跑一遍从那以后每次接真设备我都强制先走模拟从站 手动写寄存器触发报警这一套确认采集、归档、页面、报警四个环节全部在线才会把网线接到现场 PLC 上。省下来的调试时间远比写模拟器那半小时值钱。希望这篇拆解能让你在 PyScada 上的第一周少走弯路需要的源码包和配套脚本直接下载即可开始复现。本文还有配套的精品资源点击获取