主题
11.6 批量导出所有设备的程序(程序归档 / AI 分析)
尊敬的电气工程师:前面 11.2~11.5 是把同一份程序写给一批设备。这一节反过来——把现场所有设备的程序读回来,每台一个文件夹,按序列号归档。
它适合:
- 备份归档:把一批设备的 Lua 程序和梯形图整体留存,出问题时能比对、能还原。
- 交给 AI 分析:比如想让 AI 分析各台设备的控制逻辑与数据,找出最优配方——第一步就是把程序都取回来(这正是本节例程的由来)。
想要更简洁的写法?
如果您是让 AI 写脚本,或只想用一个文件搞定,直接用 11.7 给 AI 与脚本的调用模板——它用 dev list 一条命令取设备清单,单个 .ps1 即可运行。本页侧重于讲清批量导出的场景与结果。
前置条件
lpplc-manager.exe版本 2026-09-29 或更新(ladder pull子命令从该版本起提供)。用lpplc-manager.exe --version可查看版本;lpplc-manager.exe --commands能列出全部子命令,看到ladder pull就说明支持。- 设备与电脑在同一网络(能正常搜索到设备)。云端设备需要先在云参数里配置并连上云。
- 下面两个文件放在同一个文件夹,且和
lpplc-manager.exe同目录。
第 1 步:参数文件 程序归档.bat
新建文本文档,粘入下面内容,另存为 程序归档.bat(编码 UTF-8)。日常使用只改这 4 行云服务参数(云端设备才需要;只导本地设备可保持默认不动):
bat
@echo off
chcp 65001 >nul
setlocal
REM ===== 云服务连接参数(云端设备才需要改;只导本地设备可不动)=====
set "CLOUD_ADDR=www.lpplc.cn"
set "CLOUD_PORT=5550"
set "CLOUD_USER=你的云用户名"
set "CLOUD_PASS=你的云密码"
REM ===== 主程序:与本 .bat 同目录的 lpplc-manager.exe =====
set "CLI_EXE=%~dp0lpplc-manager.exe"
if not exist "%CLI_EXE%" (
echo [错误] 没找到 lpplc-manager.exe,请把本脚本放到它旁边。
exit /b 1
)
set "OUT_DIR=%~dp0程序归档"
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0程序归档.ps1"
pause第 2 步:导出脚本 程序归档.ps1
同一个文件夹里新建 程序归档.ps1(编码 UTF-8 带 BOM——中文必须带 BOM,否则 Windows PowerShell 会读成乱码报错)。内容:
powershell
$cli = [System.IO.Path]::GetFullPath($env:CLI_EXE)
$out = [System.IO.Path]::GetFullPath($env:OUT_DIR)
$td = if ($env:TEMP) { $env:TEMP } else { 'C:\Windows\Temp' }
if (-not (Test-Path $out)) { New-Item -ItemType Directory -Force -Path $out | Out-Null }
# 调用 CLI(lpplc-manager.exe 是界面程序,必须 Start-Process -Wait 等它跑完,stdout 重定向到临时文件再读回)
function Invoke-Cli {
param([string[]]$CliArgs)
$tmp = Join-Path $td ('mgr_' + [guid]::NewGuid().ToString('N') + '.json')
$psi = @{ FilePath = $cli; ArgumentList = $CliArgs; Wait = $true; NoNewWindow = $true
RedirectStandardOutput = $tmp; RedirectStandardError = ($tmp + '.err') }
Start-Process @psi | Out-Null
return (Get-Content $tmp -Raw -ErrorAction SilentlyContinue)
}
Write-Host '[1] 列出设备 ...'
# dev list 一条命令给出干净的设备清单(纯数组),不用解析扫描事件流、也不用担心跨进程设备表
$devJson = Join-Path $td 'devices.json'
Invoke-Cli @('dev', 'list', '--out', $devJson) | Out-Null
$devs = @()
if (Test-Path $devJson) {
foreach ($x in (Get-Content $devJson -Raw -Encoding UTF8 | ConvertFrom-Json)) { $devs += $x }
}
if ($devs.Count -eq 0) { Write-Host '没扫到设备。'; exit 0 }
$rows = @('序列号,硬件,来源,LUA已拉取,梯形图已拉取')
foreach ($d in $devs) {
$id = $d.id
$dir = Join-Path $out $id
New-Item -ItemType Directory -Force -Path $dir | Out-Null
# lua pull / ladder pull 一条命令即可:工具按设备的连接方式(UDP / 串口 / 云服务 …)
# 自动走对应的专用读程序通道,不用您管通道、也不用开透传。
$luaOut = Invoke-Cli @('lua', 'pull', '--device', $id, '--out', "$dir\main.lua")
$luaOk = (Test-Path "$dir\main.lua") -and ((Get-Item "$dir\main.lua").Length -gt 0)
$luaLink = ''
foreach ($line in ($luaOut -split "`n")) {
try { $o = ($line.Trim() | ConvertFrom-Json) } catch { continue }
if ($o.type -eq 'cli_result' -and $o.cli -eq 'lua pull') { $luaLink = $o.link_type }
}
Invoke-Cli @('ladder', 'pull', '--device', $id, '--out', "$dir\ladder.il") | Out-Null
$ladOk = (Test-Path "$dir\ladder.il") -and ((Get-Item "$dir\ladder.il").Length -gt 0)
$rows += ("{0},{1},{2},{3},{4}" -f $id, $d.hardware, $luaLink, $luaOk, $ladOk)
Write-Host ("设备 {0} [{1}] Lua={2} 梯形图={3}" -f $id, $luaLink, $luaOk, $ladOk)
}
$rows | Set-Content (Join-Path $out 'manifest.csv') -Encoding UTF8
Write-Host ('完成,共 ' + $devs.Count + ' 台,归档于 ' + $out)第 3 步:双击运行
双击 程序归档.bat,脚本自动扫描、逐台读取、按序列号建文件夹。
跑完得到什么
text
程序归档\
├─ 10000001\ ← 以序列号命名
│ ├─ main.lua ← 该设备的 Lua 程序
│ └─ ladder.il ← 该设备的梯形图(指令表文本)
├─ 12596159\
│ ├─ main.lua
│ └─ ladder.il
└─ manifest.csv ← 汇总:每台设备拉没拉到manifest.csv 用 Excel 打开即可看到每台设备的结果:
text
序列号,硬件,来源,LUA已拉取,梯形图已拉取
10000001,,local,True,True在软 PLC 模拟器(序列号 10000001)上实测:main.lua 取回真实程序正文(如 -- x023 verify 5 / plc.d(503, 5)),ladder.il 取回指令表(空程序时为 END)。
读不到怎么办
- 某台显示
False:先看它是否在线且能被搜到(lpplc-manager.exe dev scan)。跨网段、设备掉线都会读不到。 - 真实 PLC 上梯形图读回时报「接收 FX 应答超时」:多为该设备当时正忙或未响应 FX 握手,稍后重试即可,不是脚本问题。
- 梯形图是空程序时
ladder.il内容就是END,这是正常的(不是失败)。
关于读取
- 不管设备是网线、WiFi、串口还是 4G 云连接,用
lua pull/ladder pull即可,工具会按连接方式自动走对应的专用读程序通道——您完全不用关心通道,也不用开透传。 - 本例程只读取、不写入,不会改动设备,所以不需要
--yes。 - 读不到会如实告诉您(结果里
False并带原因),不会生成空文件冒充成功。