二次开发只适合深度软件开发用户,否则请用我们的SDRTU发布版本,不然会浪费你大量时间!
demo代码: do_it_yourself.zip
提供单次定时,循环定时,定时器管理等功能
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协程是非常强大的功能,但是用起来也很复杂。
线程和协程区别:
1.线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
2.在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
3.协程的运行是单线程的,谨记这一点
框架内置网络通信主题
端口消息 | 主题 | 说明 |
---|---|---|
从网络收到消息 | N_RECV_ | {SID}代表网络通道ID |
发送消息到网络 | N_SEND_ | {SID}代表网络通道ID |
网络连接 | N_OPEN_ | {SID}代表网络通道ID |
网络断开 | N_CLOSE_ | {SID}代表网络通道ID |
数据通信主题:串口ID为1234(具体代表什么见硬件说明), 5控制台,6规则引擎(手动),21+(自定义1),自定义端点用于灵活功能
端口消息 | 主题 | 说明 |
---|---|---|
从端点收到消息 | D_RECV_ | {SID}代表数据通道ID |
发送消息到端点 | D_SEND_ | {SID}代表数据通道ID |
程序从mian.lua启动运行;如果是基于固件的补丁开发,用户脚本启动为userapp.lua
基于Lua54环境实现自己的独立应用,这里介绍一些demo,代码不一定是最新的,最新代码请到Gitee仓库同步,地址:
PROJECT = "SDRTU"
VERSION = "1.0.1"
-- 系统变量
VER = ""
if rtos.hw then VER = rtos.hw():upper() end
log.warn("IMEI:", misc.getImei(), "Dev", VER)
if VER == "ZL500" or VER == "ZL400" then
log.info("mem sys", rtos.meminfo("sys"))
log.info("mem lua", rtos.meminfo())
end
--时间
log.info("timestamp", os.time())
log.info("time format", os.date("%Y-%m-%d %H:%M:%S", os.time()))
----------------------------
----------------------------
--------日志
----------------------------
----------------------------
-- #define RTU_LOG_DEBUG 1
-- #define RTU_LOG_INFO 2
-- #define RTU_LOG_WARN 3
-- #define RTU_LOG_ERROR 4
-- #define RTU_LOG_CLOSE 7
log.setLevel(1) -- 只打印 >= 对应等级的数据
log.debug("test debug log out")
log.info("test info log out")
log.warn("test warn log out")
log.error("test error log out")
----------------------------
----------------------------
--------基本计算功能演示
----------------------------
----------------------------
-- 数据类型: 数组
local testarry = { 1, 2, 3, 4, 5, 6, 7 } -- lua数组,下标是从1开始
log.info("testarry[1]=", testarry[1], testarry) -- testarry是个table
-- 数组的遍历
for index, value in ipairs(testarry) do
log.info("数组的遍历 index value:", index, value) -- testarry是个table
end
-- 数据类型:表
local testarry = { a1 = 123, b2 = 345 } -- lua数组,下标是从1开始
log.info("testarry[1]=", testarry[a1], testarry.b2) -- 两种访问方式
-- 表的遍历
for key, value in pairs(testarry) do
log.info("表的遍历 key value:", key, value) -- testarry是个table
end
--------------------------------------------------
--------------------------------------------------
--------------------------------------------------
local test1 = 8765
local test2 = 1234
log.info("test1=" .. test1 .. " test2=" .. test2)
-- 数值计算
log.info("test1+test2=", test1 + test2)
log.info("test1-test2=", test1 - test2)
log.info("test1*test2=", test1 * test2)
log.info("test1/test2=", test1 / test2) -- 小数除法
log.info("test1//test2=", test1 // test2) -- 整数除法,取整等于c语言整数除法
log.info("test1%test2=", test1 % test2) -- 取余
log.info("test1^test2=", test1 ^ test2) -- 求幂 = math.pow(test1,test1)
-- 按位计算
test1 = 0x43210
test2 = 0x41234
log.info("test1=" .. test1 .. " test2=" .. test2)
log.info("test1 & test2=", test1 & test2) -- 按位与
log.info("test1 | test2=", test1 | test2) -- 按位或
log.info("test1 ~ test2=", test1 ~ test2) -- 按位异或 注意异或比较特别
log.info("1<<5 = ", 1 << 5) -- 左移
log.info("test1 >> 2 = ", test1 >> 2) -- 右移
log.info("~test1", ~test1) -- 按位非,取反;注意和异或是一样的符号
--------------------------------------------------
--------------------------------------------------
--------------------------------------------------
-- json 解压缩
-- 压缩,输入table,输出字符串
local testjson = json.encode({
data = "rrpc,getimei,168228129166144\r\n",
a = 12,
b = 14.5
})
log.warn("cjson encode test", testjson)
-- 解压缩
testjson = json.decode(
"{\"sites\":[{\"name\":\"13213\",\"url\":\"www.runoob.com\"},{\"name\":2,\"url\":\"www.google.com\"},{\"name\":3.55,\"url\":\"www.weibo.com\"}]}")
log.warn("cjson decode test", testjson) -- 输出是table
-- math函数
-- math.abs (x)
-- 返回x和之间的最大值-x。(整数/浮点数)
-- math.acos (x)
-- 返回x(以弧度为单位)的反余弦值。
-- math.asin (x)
-- 返回x(以弧度为单位)的反正弦。
-- math.atan (y [, x])
-- 返回y/x(以弧度为单位)的反正切,但使用两个参数的符号来查找结果的象限。它还可以正确处理x为零的情况。
-- 的默认值为x1,以便调用math.atan(y) 返回 的反正切y。
-- math.ceil (x)
-- 返回大于或等于 的最小整数值x。
-- math.cos (x)
-- 返回x(假设为弧度)的余弦值。
-- math.deg (x)
-- 将角度x从弧度转换为度数。
-- math.exp (x)
-- 返回值e x (其中e是自然对数的底数)。
-- math.floor (x)
-- 返回小于或等于 的最大整数值x。
-- math.fmod (x, y)
-- 返回的除法的余数x由y 该轮向零商。(整数/浮点数)
-- math.huge
-- 浮点值HUGE_VAL,一个大于任何其他数值的值。
-- math.log (x [, base])
-- 返回x给定底数的对数。默认base是Ë (这样的函数返回的自然对数x)。
-- math.max (x, ···)
-- 根据 Lua operator 返回具有最大值的参数<。
-- math.maxinteger
-- 具有整数最大值的整数。
-- math.min (x, ···)
-- 根据 Lua operator 返回具有最小值的参数<。
-- math.mininteger
-- 具有整数最小值的整数。
-- math.modf (x)
-- 返回 的整数部分x和小数部分x。它的第二个结果总是一个浮点数。
-- math.pi
-- π 的值。
-- math.rad (x)
-- 将角度x从度数转换为弧度。
-- math.random ([m [, n]])
-- 当不带参数调用时,返回一个在[0,1)范围内均匀分布的伪随机浮点数 。当用两个整数mand调用时n, math.random返回一个在[m, n]范围内均匀分布的伪随机整数。math.random(n)对于正数n,调用等效于math.random(1,n)。该调用math.random(0)生成一个所有位(伪)随机的整数。
-- 此函数使用该xoshiro256**算法生成伪随机 64 位整数,这些整数是参数为 0 的调用结果。其他结果(范围和浮点数)从这些整数中无偏提取。
-- Lua 初始化其伪随机生成器,相当于调用不math.randomseed带参数的 ,因此math.random每次程序运行时应该生成不同的结果序列。
-- math.randomseed ([x [, y]])
-- 当使用至少一个参数调用时,整数参数x和y连接到一个 128 位的种子中,用于重新初始化伪随机生成器;相等的种子产生相等的数字序列。的默认值y为零。
-- 当不带参数调用时,Lua 会生成一个随机性较弱的种子。
-- 此函数返回有效使用的两个种子组件,以便再次设置它们会重复序列。
-- 为了确保初始状态所需的随机性级别(或者相反,具有确定性序列,例如在调试程序时),您应该math.randomseed使用显式参数调用。
-- math.sin (x)
-- 返回x(假定为弧度)的正弦值。
-- math.sqrt (x)
-- 返回 的平方根x。(您也可以使用表达式x^0.5来计算此值。)
-- math.tan (x)
-- 返回x(假定为弧度)的切线。
-- math.tointeger (x)
-- 如果该值x可转换为整数,则返回该整数。否则,返回失败。
-- math.type (x)
-- 返回“ integer”如果x是一个整数,“ float”如果它是一个浮动,或失败,如果x不是数字。
-- math.ult (m, n)
-- 返回一个布尔值,当且仅当它们作为无符号整数进行比较时整数m小于整数n时才为真。
----------------------------
----------------------------
--------基本接口,定时器
----------------------------
----------------------------
-- 单次定时调用, 入参:callbackfun, time, contx_a, contx_b, contx_c
-- 定时单位是ms,误差小于10ms
-- sys.timerStart(fun(a,b,c), time, a,b,c)
-- 测量tick的时间
local tickstart = rtos.tick() -- 单位是ms
sys.timerStart(function(a, b, c)
endt = rtos.tick()
log.warn("5s period tick is", (endt - tickstart), a, b, c)
end, 5000, "aa", "vv", 55)
-- 周期定时调用, 入参:callbackfun, time, contx_a, contx_b, contx_c
-- 定时单位是ms,误差小于10ms
-- sys.timerLoopStart(fun(a,b,c), time, a,b,c)
sys.timerLoopStart(function(a, b, c)
log.info("mem sys", rtos.meminfo("sys"))
log.info("mem lua", rtos.meminfo())
log.info("contex", a, b, c)
end, 15000, "aa", "bb", 12354)
----------------------------
----------------------------
--------基本接口,协程创建
----------------------------
----------------------------
-- 创建协程,入参:fun, contx_a, contx_b, contx_c
-- sys.taskInit(fun, a, b, c)
local taskcnt = 0
local taskHandle = function(a, b, c)
log.debug("taskHandle contex", a, b, c) -- 打印可变参数
-- log.debug(arg[1]) -- 打印可变参数
-- body
while true do -- 协程内部可以做成while(1) 记得加wait延迟,不要100%占用cpu
sys.wait(1000) -- 延迟1000ms, 必须在协程内部使用
taskcnt = taskcnt + 1
if taskcnt == 15 then
log.warn("taskInit test2", taskcnt, a, b, c)
end
end
end
log.warn("taskInit test1", taskcnt)
sys.taskInit(taskHandle, 1, 2, "3333")
----------------------------
----------------------------
--------基本接口,高性能消息总线
----------------------------
----------------------------
-- 发布消息
-- @string topic, 订阅的目标主题
-- @param msg1 各种类型的数据
-- sys.publish(topic, msg1, msg2, msg3)
-- 对应的订阅接口
-- @string topic, 订阅的目标主题
-- @param handle, 为function时,调用回调函数;为string时为数据转发到to目标主题
-- sys.subscribe(topic, [handle | nextTopic])
--- 取消订阅消息
-- @param id 消息topic
-- @param callback 消息回调处理,function, string
-- @usage unsubscribe("NET_STATUS", callback)
-- sys.unsubscribe(topic, callback)
-- sys.unsubscribeAll(topic)
-- 启动消息总线,需要启动后,消息才能处理
-- while true do sys.eventloop() end
-- 使用实例
-- 基本发布订阅使用
local sendTopic = "XX_SEND_1"
local recvTopic = "XX_RECV_1"
local function handle(msg) log.info("test publish msg to recvTopic", msg) end
sys.subscribe(recvTopic, handle)
sys.publish(recvTopic, "12344")
-- 运行后打印1234
------------------------------------
-- 带消息来源的使用
local function handle1(msg, id, from) log.info("msg1", msg) end
local function handle2(msg, id, from) log.info("msg2", msg) end
local hanleTab = { ["D_RECV_1"] = handle1, ["D_RECV_2"] = handle2 }
local function ruleWorker(data, id, from)
local callback = hanleTab[from]
if callback and data then callback(data) end
end
sys.subscribe("N_SEND_7", ruleWorker)
-- 模拟串口发送到规则通道
sys.publish("N_SEND_7", "12344", 1, "D_RECV_1")
sys.publish("N_SEND_7", "12344", 2, "D_RECV_2")
------------------------------------
-- 消息转发的使用
local function handle1(msg, id, from)
log.info("publish msg from N_SEND_8 to N_SEND_1", msg)
end
sys.subscribe("N_SEND_1", handle1)
sys.subscribe("N_SEND_8", "N_SEND_1")
-- 发送消息到通道8,流转到通道1
sys.publish("N_SEND_8", "12344", 2, "D_RECV_2")
-- 测试消息队列
timerCn = 0
sys.testsubfun = function(...)
timerCnt = timerCnt + 1
if timerCnt >= 100 then
log.warn("msg test1 tick")
-- test unsubscribe
sys.unsubscribe("testtopic", sys.testsubfun)
end
end
sys.subscribe("testtopic", sys.testsubfun)
-- 连续发一百条消息
for i = 1, 100 do sys.publish("testtopic", i) end
----------------------------------
----------------------------------
----------------------------------
-- 协程调度测试,生产消费模型
log.info("start coroutine perf test2, conusmer test with 200 cycle")
local start3 = rtos.tick()
timerCnt = 0
sys.taskInit(function(...)
-- body
while true do
sys.waitUntil("utest1", 20000) -- 和wait不同,从消息队列取消息,收到了立即结束。否则等到超时,单位ms
timerCnt = timerCnt + 1
if timerCnt == 200 then
endt = rtos.tick()
log.warn("coroutine test2 tick", endt - start3)
return
end
sys.publish("utest2", "i1", rtos.tick())
end
end)
sys.taskInit(function(...)
-- body
while true do
sys.waitUntil("utest2", 20000) -- 和wait不同,从消息队列取消息,收到了立即结束。否则等到超时,单位ms
timerCnt = timerCnt + 1
if timerCnt == 200 then
endt = rtos.tick()
log.warn("coroutine test2 tick", endt - start3)
return
end
sys.publish("utest1", "i1", rtos.tick())
end
end)
-- 启动测试
sys.publish("utest1", "i1", rtos.tick()) -- 启动测试
log.info("mem sys", rtos.meminfo("sys"))
log.info("mem lua", rtos.meminfo())
-- 启动消息总线
while true do sys.eventloop() end
-- PROJECT = "UART"
-- VERSION = "1.0.0"
-- 模拟接收数据
sys.subscribe("UART_ECHO", function(id, data)
log.info("test UART_ECHO", id, #data)
if #data > 0 then uart.write(id, data) end
end)
sys.timerLoopStart(function()
log.info("start uart test app")
log.info("mem sys", rtos.meminfo("sys"))
log.info("mem lua", rtos.meminfo())
end, 5000)
-- uart.setup(2, 9600)
-- 函数原型
-- uart.setup(id, baud_rate, data_bits, partiy, stop_bits, rs485, rwait, buff_size)
-- 串口id, uart1是1, uart2是2
-- 波特率 1200~460800
-- 数据位 7或8, 默认是8
-- 校验位 uart.None/uart.Even/uart.Odd,默认无
-- 停止位 1或2, 默认是1
-- RS485使能引脚,高有效,没有写0
-- rwait 接收分帧时间,单位ms,默认200
-- buff_size 缓冲区长度默认值10k字节
uart.setup(1, 9600, 8, uart.None, 1, 0, 0)
-- uart.close(1)
-- 修改接收分帧时间
-- uart.rwait(100)
uart.on(1, "recv", function(id, len)
-- 读取数据
local data = uart.read(id, len)
log.info("test uart read", id, len, #data)
-- 发出去模拟处理数据
sys.publish("UART_ECHO", id, data)
end)
uart.on(1, "sent",
function(id, len) log.info("uart data send over, len=", len) end)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
while true do sys.eventloop() end
PROJECT = "NET"
VERSION = "1.0.0"
BSP = misc.getType():upper() -- 硬件类型,具体到模组型号,ZL401
HW = rtos.hw():upper() -- 硬件类型,具体到系列型号,ZL400,ZL500
log.info("HW PLAT IS", HW)
-- 网络状态,4G入网LED闪烁,否则常亮
if HW == "ZL400" then
netled = gpio.setup(13, 0) -- output
linkled = gpio.setup(8, 0)
elseif HW == "ZL500" then
netled = gpio.setup(33, 0) -- output
linkled = gpio.setup(12, 0)
end
-- 连上了服务器link再亮
linkled(0)
sys.taskInit(function()
while true do
while not sock.isReady() do
netled(0)
sys.wait(200)
netled(1)
sys.wait(200)
end
netled(1)
sys.wait(1000)
end
end)
local host = "aeo123.ticp.net" -- 花生壳测试地址
local port = 35251
-- 新建TCP Client 对象
local so = sock.tcp()
-- 新建UDP Client 对象
-- local so = sock.udp()
sys.taskInit(function()
while not sock.isReady() do sys.wait(200) end
if HW == "ZL500" then
sock.ntpSync() -- 4G的无需手动ntp
end
if so then
log.info("start tcp stask")
local dpp = pack.pack("bbbA", 10, 15, 4, "123")
log.info("packtest0" .. dpp)
-- 服务器地址
so:host(host)
so:port(port)
so:recon(2000, 10) -- 重连间隔时间单位ms, 重启之前的重连次数
so:keepAlive(60 * 1000, 10) -- 应用层心跳时间ms。发数据在回调里面操作,底层会自动启用链接层TCP KEEPALIVE
local connectHandle = function()
log.info("sock connet success", so:id())
-- led指示
linkled(1)
-- 连接上发注册包
so:send("CMD,GROUP,AXCV")
end
local recvHandle = function(sid, data)
log.info("netc recv", sid, data)
end
local myBeatPre = "testBBBB"
-- 演示,闭包,函数,生成心跳包
local genBeat = function() return myBeatPre .. os.time() end
local keepAliveHandle = function()
-- 发心跳
so:send(genBeat())
log.error("keepAliveHandle", so:id())
end
local offlineHandle = function()
-- led指示
linkled(0)
log.error("sock is disconnect", id) -- auto reconnect
end
local errorHandle = function()
log.error("sock is broken, id=", so:id())
-- sys.restart("sock is broken") -- 当连续重连达到重连次数后,进入error状态
end
-- 注册回调函数
so:on("connect", connectHandle)
so:on("recv", recvHandle)
so:on("keepAlive", keepAliveHandle)
so:on("error", errorHandle) -- 重连次数达到设定报错
so:on("close", offlineHandle)
if so:connect() then
log.info("sock status ", so:status())
-- 测试消息发送
local function mesgTest()
log.info("mesgTest", so:status())
if so:status() then
so:send("msg ts:" .. os.time())
end
end
-- sys.timerLoopStart(mesgTest, 5000) --定时发消息测试
else
log.error("sock connect error", so:status())
end
-- so:close() -- 主动关闭
end
end)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
while true do sys.eventloop() end
PROJECT = "NET"
VERSION = "1.0.0"
BSP = misc.getType():upper() -- 硬件类型,具体到模组型号,ZL401
HW = rtos.hw():upper() -- 硬件类型,具体到系列型号,ZL400,ZL500
log.info("HW PLAT IS", HW)
-- 网络状态,4G入网LED闪烁,否则常亮
if HW == "ZL400" then
netled = gpio.setup(13, 0) -- output
linkled = gpio.setup(8, 0)
elseif HW == "ZL500" then
netled = gpio.setup(33, 0) -- output
linkled = gpio.setup(12, 0)
end
-- 连上了服务器link再亮
linkled(0)
sys.taskInit(function()
while true do
while not sock.isReady() do
netled(0)
sys.wait(200)
netled(1)
sys.wait(200)
end
netled(1)
sys.wait(1000)
end
end)
local host = "115.159.225.58"
local port = 1883
local client = "client"
local username = "admin"
local pswd = "admin"
local keepAlive = 200 * 1000 --单位ms
local cleansession = 1
local reconnectTime = 2000 --单位ms
local mqrtry = 10 -- 重连次数
local willtopic = "/will"
local willqos = 1
local willretain = 0
local willmsg = "will msg test"
local id = 1
local mqttc, conretry
-- 主要的流程
local function mqttProc()
-- wait network ready
while not sock.isReady() do sys.wait(200) end
-- 新建实例
mqttc = mqtt.tcp(host, port, client, username, pswd, keepAlive,
cleansession, reconnectTime)
-- 遗嘱的设置
mqttc:will(willtopic, willqos, willretain, willmsg)
-- mqtt发布handle
local function mqttPubFun(topic, d)
if not d or not topic or not mqttc:pub(topic, d) then
return false
end
log.debug("MQTT 发布的数据:", id, topic, d)
return true
end
-- 意外断开
local disconnectFunc = function()
log.warn("mqtt disconnect,wait reconnect", conretry, mqrtry)
linkled(0)
end
-- 重连handle
local reconnectFunc =
function(id, res) -- res指示是重连失败还是成功
if res then
-- 连接成功
linkled(1)
conretry = mqrtry -- 连续10次连不上,30s,重启
log.info("mqtt reconnect result", mqttc:stat())
else
-- 连接失败
linkled(0)
conretry = conretry - 1
log.info("mqtt reconnect failed", mqttc:stat())
if conretry == 0 then
-- 网络有问题,需要重启
sys.timerStart(sys.restart, 2000, "mqtt reconnect fail!")
end
end
end
local errorFunc = function()
-- body
log.error("mqtt error, chanel", id)
end
local recvFunc = function(id, topic, data)
-- body
log.info("订阅的消息-----:", topic)
-- 发布回去
mqttPubFun("/public/pub11", data)
end
-- 更新配置
-- mqttc:auth(client, username, pswd)
-- mqttc:host(host)
-- mqttc:port(port)
mqttc:on("disconnect", disconnectFunc)
mqttc:on("recon", reconnectFunc)
mqttc:on("error", errorFunc)
mqttc:on("recv", recvFunc)
log.info("mqtt start connect!")
-- first connect
conretry = 10 -- 连续10次连不上,30s,重启
while not mqttc:connect() and conretry > 0 do
log.error("mqtt reconnect", conretry)
sys.wait(3000)
conretry = conretry - 1
end
if conretry == 0 then
-- 网络有问题,需要重启
sys.timerStart(sys.restart, 2000, "Net is broken, mqtt connect fail!")
end
-- link灯指示
linkled(1)
log.info("mqtt connect success", mqttc:stat())
-- sub topic
-- log.info("mqtt sub sta", mqt:sub("testtopic", 0)) -- 1 topic sub
-- 订阅主题
-- subqos = 1
-- mqttc:sub("/testtopic1", subqos)
-- mqttc:sub("/testtopic2", subqos)
-- 等同于
local subarray = {["/public/test1"] = 1, ["/public/test2"] = 1} -- {topic = qos}
mqttc:sublist(subarray)
end
sys.taskInit(mqttProc)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
while true do sys.eventloop() end
SDRTU是一套数据采集应用框架,提供保姆级功能,主要解决了设备对接平台、远程运维、数据收发等问题,一键急速上云;
有软件开发能力的用户可以在提供的功能上自由发挥增加你要的功能或者指令;对于一些隐藏的功能,你可以在sbl脚本包上叠加开发。
除了自带的函数和功能外,以下是部分SDRTU固件平台提供的接口,可以直接调用, 更多功能等待探索..
-- 得到配置json对象dtu
io.getDtu = function() return dtu end
-- 得到当前参数版本
io.getParamVer = function() return dtu.param_ver end
-- 得到当前流量统计
io.getFlowCount = function() return flowCount end
-- 得到基站定位坐标, {lat, lng}
io.getLbs = function() return lbs end
-- 得到网络通道连接状态,通道1对应bit(1),通道6对应bit(6)
net.getDatalink()
-- 获取设备软件版本
rrpc,getfwver
-- 获取设备脚本版本
rrpc,getver
-- 获取设备信号,ZL500返回固定32
rrpc,getcsq
-- 重启设备
rrpc,reboot
-- 获取设备IMEI
rrpc,getimei
-- 获取设备sim卡ICCID
rrpc,geticcid
-- 获取设备网络信息,ZL500打印IP
rrpc,getnetinfo
-- 获取设备网络信息,ZL500打印dns
rrpc,getdns
-- 获取设备时间
rrpc,gettime
-- 获取设备网络连接状态,通道1等于bit(1),不为0则link灯亮
rrpc,getlink
-- 通过命令执行脚本
rrpc,function
使用框架开发的最佳实践,补丁是在内置固件的基础上补充用户自己的功能。和裸机开发不一样
使用上位机下载这个pac,功能是删除userapp.lua文件,下载了重启一次确保删除了,然后在去下载一个最新的标准固件pac下载即可。
delete_userapp.pac
-- demo 功能: modbus自定义数据的简要使用,需要注意订阅主题的使用
-- 说明:
-- 此demo对应配置为demo1_config.txt
-- 必须修改本脚本文件名为userapp.lua,通过上位机增量下载脚本写入。
-- userapp.lua 是用户端脚本启动入口(类似main),不限量脚本写入;如果是单个脚本,也可以用远程补丁写入;
-- 网络连接1配置为Tcp client,连接到PC上位机,数据源选规则,由脚本手动控制;
-- 网络连接2配置为TCP client,连接到Modbus slave,mb tcp模式,数据源选规则,由脚本手动控制。
-- modbus 设置为ATCP(任意输入的MBTCP协议),定义为自定义1通道。输入数据选择网络通道2,连接到pc的mb slave。
-- Mb主机使用网络通道2读写数据,完毕后通过自定义1主题上传数据,此脚本里面获取数据,处理后指定发送到网络通道1.
-- 如果只是需要自定义上行数据格式,直接通过“模板模式”的数据结构就可以实现,使用方法见下次分
local replytopic = "rwer"
-- 上行数据模板
local template = {
version = "1.0",
type = "variant_data",
params = {Va = 221.5},
time = 0
}
-- 回复数据模板
local reply = {
id = "1",
version = "1.0",
type = "write_variant_reply",
time = 0,
code = 0
}
-- 下行数据的处理函数
local function msgSubHook(msg)
data = json.decode(type(msg)=="table" and msg.payload or msg) --如果是mqtt, 数据在payload字段;tcp直接就是数据
-- 这里的主题就是和发送是反的
sys.publish("D_SEND_21", data)
end
-- 写入回复的处理函数
local function msgReplyHook(data)
log.debug("modbus reply1", data)
-- reply
reply.time = os.time() * 1000
reply.code = data.suc and 0 or 41001
-- 发送到通道1
sys.publish("N_SEND_1", {t = replytopic, d = json.encode(reply)})
end
-- 上行消息的处理函数,在这里构造json
-- msg:内部上报的数据内容,注意这里不是单纯的table了。格式 {b = buff, d = jbuff, from = "MB_SEND"}
-- b是table格式数据,d是string格式数据
local function msgPubHook(msg)
-- 包装上行数据
local buff = {}
-- 直接对数据处理要使用msg.b
for key, value in pairs(msg.b) do buff[key] = value end
template.params = buff
template.time = os.time() * 1000
-- 转json上传
buff = json.encode(template)
log.info("modbus pub msg1", buff)
-- 发送到网络通道1
sys.publish("N_SEND_1", buff)
end
local rules = {
-- modbus数据有两种,用from来区别
["MB"] = msgPubHook,
["MB_REPLY"] = msgReplyHook
}
-- 规则订阅, 不同的数据来源由不同的函数处理
-- modbus上传的可能是数据,也可能是回复
local function ruleWorker(data, id, from)
local callback = rules[from]
if callback and data then callback(data) end
end
-- 自定义通道是从21开始: 21= 自定义1
-- 这里是自定义通道主动上报的数据,要用RECV主题接收.
-- 这里逻辑可以理解为“虚拟的串口功能RECV数据,在SEND到网络”.
-- 记得把网络通道调到规则引擎=无任何反应。不然会多发一条数据
sys.subscribe("D_RECV_21", ruleWorker)
-- 收取网络通道1下发的数据
sys.subscribe("N_RECV_1", msgSubHook)
-- TEST LOOP
sys.timerLoopStart(function() log.info("i am user app", rtos.meminfo()) end,
2000)
-- demo 功能: modbus 数据判断和写入modbus功能
-- 说明:
-- 此demo对应配置为demo2_config.txt
-- 必须修改本脚本文件名为userapp.lua,通过上位机增量下载脚本写入。
-- userapp.lua 是用户端脚本启动入口(类似main),不限量脚本写入;如果是单个脚本,也可以用远程补丁写入;
-- 网络连接1配置为Tcp client,连接到PC上位机,数据源选规则,由脚本手动控制;
-- 网络连接2配置为TCP client,连接到Modbus slave,mb tcp模式,数据源选规则,由脚本手动控制。
-- modbus 设置为ATCP(任意输入的MBTCP协议),定义为自定义1通道。输入数据选择网络通道2,连接到pc的mb slave。
-- Mb主机使用网络通道2读写数据,完毕后通过自定义1主题上传数据,此脚本里面获取数据,处理后指定发送到网络通道1.
-- 如果只是需要自定义上行数据格式,直接通过“模板模式”的数据结构就可以实现,使用方法见下次分
local replytopic = "rwer"
-- 上行数据模板
local template = {
version = "1.0",
type = "variant_data",
params = {Va = 221.5},
time = 0
}
-- 回复数据模板
local reply = {
id = "1",
version = "1.0",
type = "write_variant_reply",
time = 0,
code = 0
}
-- 下行数据的处理函数
local function msgSubHook(msg)
data = json.decode(type(msg)=="table" and msg.payload or msg) --如果是mqtt, 数据在payload字段;tcp直接就是数据
-- 这里的主题就是和发送是反的
sys.publish("D_SEND_21", data)
end
-- 写入回复的处理函数
local function msgReplyHook(data)
log.debug("modbus reply1", data)
end
-- 上行消息的处理函数,在这里构造json
-- msg:内部上报的数据内容,注意这里不是单纯的table了。格式 {b = buff, d = jbuff, from = "MB_SEND"}
-- b是table格式数据,d是string格式数据
local function msgPubHook(data)
local msg = data.b
-- 判读数据是否存在
if msg.hongwai1 then
-- 启用变化上传,只在有数据的时候处理
if msg.hongwai1 == 1 then
-- 这里是把数据发送到自定义端点
sys.publish("D_SEND_21", {jidianqi1 = msg.hongwai1})
else
sys.timerStart(function() sys.publish("D_SEND_21", {jidianqi1 = msg.hongwai1}) end, 15 * 60 * 1000)
end
end
end
local rules = {
-- modbus数据有两种,用from来区别
["MB"] = msgPubHook,
["MB_REPLY"] = msgReplyHook
}
-- 规则订阅, 不同的数据来源由不同的函数处理
-- modbus上传的可能是数据,也可能是回复
local function ruleWorker(data, id, from)
local callback = rules[from]
if callback and data then callback(data) end
end
-- 自定义通道是从21开始: 21= 自定义1
-- 这里是自定义通道主动上报的数据,要用RECV主题接收.
-- 这里逻辑可以理解为“虚拟的串口功能RECV数据,在SEND到网络”.
-- 记得把网络通道调到规则引擎=无任何反应。不然会多发一条数据
sys.subscribe("D_RECV_21", ruleWorker)
-- 收取网络通道1下发的数据
sys.subscribe("N_RECV_1", msgSubHook)
-- demo 功能: 自定义数据解析
-- 说明:
-- 此demo对应配置为demo3_config.txt
-- 必须修改本脚本文件名为userapp.lua,通过上位机增量下载脚本写入。
-- userapp.lua 是用户端脚本启动入口(类似main),不限量脚本写入;如果是单个脚本,也可以用远程补丁写入;
local config = io.getDtu()
-- 在用户参数里面添加一个delay,默认10s
local delay = config and config.up and config.up.delay and
tonumber(config.up.delay) or 10
-- 这里解析串口返回的数据
local function msgPubHook(data)
-- 加入收到A100
if data and string.sub(data, 1, 1) == "A" then
data = string.sub(data, 2, -1) -- 截取第二个字符到结尾
-- 发送这个结果字符串到网络1
sys.publish("N_SEND_1", data)
end
end
-- 这里定时发送查询指令到串口
sys.timerLoopStart(function()
-- 发送字符串到串口1
sys.publish("D_SEND_1", "Q100")
-- 发送hex
-- sys.publish("D_SEND_1", string.fromHex("1234")) --0x1234
end, delay * 1000)
-- 自定义通道是从21开始: 21= 自定义1; 1=串口1,3=串口3
-- 这里是自定义通道主动上报的数据,要用RECV主题接收.
-- 这里逻辑可以理解为“虚拟的串口功能RECV数据,在SEND到网络”.
-- 记得把网络通道调到规则引擎=无任何反应。不然会多发一条数据
sys.subscribe("D_RECV_1", msgPubHook)
local config = io.getDtu()
-- 在用户参数里面添加一个delay,默认10s
local delay = config and config.up and config.up.delay and
tonumber(config.up.delay) or 10
-- 这里解析串口返回的数据
local function msgPubHook(data)
--接收0xFF C4 02 02 4C EB 00 64 9D
if not data or string.len(data) < 9 then
log.error("recv size error")
return
end
--取出5.6字节数据
-- local payload = data:sub(5, 6)
-- local _, ppm = pack.unpack(payload, ">H")
--或者方法2
local ppm = string.byte(data, 5, 5) * 256 + string.byte(data, 6, 6)
--
ppm = ppm * 0.01
log.info("read ppm , ", ppm)
--转json上传
local template =
{
ppm = ppm, --按需定制你的字段
id = 1
}
--发送数据到网络通道1
sys.publish("N_SEND_1", json.encode(template))
end
-- -- 这里定时发送查询指令到串口
-- sys.timerLoopStart(function()
-- -- 发送字符串到串口1
-- sys.publish("D_SEND_1", "Q100")
-- -- 发送hex
-- -- sys.publish("D_SEND_1", string.fromHex("1234")) --0x1234
-- end, delay * 1000)
-- 自定义通道是从21开始: 21= 自定义1; 1=串口1,3=串口3
-- 这里是自定义通道主动上报的数据,要用RECV主题接收.
-- 这里逻辑可以理解为“虚拟的串口功能RECV数据,在SEND到网络”.
-- 记得把网络通道调到规则引擎=无任何反应。不然会多发一条数据
sys.subscribe("D_RECV_1", msgPubHook)
local dtu = io.getDtu()
-- 网络通道数据源选规则,不能选串口
-- 网络通道数据源选规则,不能选串口
-- 网络通道数据源选规则,不能选串口
-- 网络收到数据下行
function nread(msg)
if type(msg) == "table" then
if msg.topic and msg.payload then
local jdata, res = json.decode(msg.payload)
if res then
jdata.topic = msg.topic
-- 下发的加上收到主题
sys.publish("D_SEND_1", json.encode(jdata))
end
end
end
end
-- 串口接收数据,json格式,提取topic然后删除topic,发送data里面的数据
-- {"topic":"213123", "data":131231}
function uread(msg)
log.info("uread msg", msg)
local jdata, res = json.decode(msg)
if jdata and res then
if jdata.topic and jdata.data then
sys.publish("N_SEND_1", { b = jdata.data, t = tp }) --发送数据的时候,b代表table输入,d代表字符串输入,t代表指定主题
-- sys.publish("D_SEND_1", "OK")
else
-- sys.publish("D_SEND_1", "ERROR") --返回给串口
end
else
-- sys.publish("D_SEND_1", "ERROR")
end
end
sys.subscribe("D_RECV_1", uread)
sys.subscribe("N_RECV_1", nread)
local config = io.getDtu()
local up = config.up
local db = require "db" --db只是一个内置的数据保存模块,使用io标准接口实现。只支持读取和保存
local appcfg = db.new("/data/appcfg.cfg") -- 保存配置,使用/data路径可能会在擦除情况下被删除。使用/appcfg.cfg也可以
local appInfo = appcfg and appcfg:export() or { ver = 10, count = 2 } --这里是初始化的情况给与一个默认值
log.info("从文件加载配置", appInfo, json.encode(appInfo))
sys.timerStart(function()
appInfo.count = os.time()
appcfg:import(appInfo) --导入数据并保存,这里可以随便导入什么table
end, 3600 * 1000)
local dtu = io.getDtu()
-- 网络通道数据源选规则,不能选串口
-- 网络通道数据源选规则,不能选串口
-- 网络通道数据源选规则,不能选串口
-- 网络收到数据下行
function nread(msg)
if type(msg) == "table" then
if msg.topic and msg.payload then
local jdata, res = json.decode(msg.payload)
if res then
jdata.topic = msg.topic
-- 下发的加上收到主题
sys.publish("D_SEND_1", json.encode(jdata))
end
end
end
end
-- 串口接收数据,json格式,提取topic然后删除topic,发送data里面的数据
-- {"topic":"213123", "data":131231}
function uread(msg)
log.info("uread msg", msg)
local jdata, res = json.decode(msg)
if jdata and res then
if jdata.topic then
local tp = string.sub(jdata.topic, 1, -1)
jdata.topic = nil
sys.publish("N_SEND_1", { b = jdata.data, t = tp })
-- sys.publish("D_SEND_1", "OK")
log.info("send ok", tp)
else
-- sys.publish("D_SEND_1", "ERROR") --返回给串口
log.info("send ERROR1")
end
else
-- sys.publish("D_SEND_1", "ERROR")
log.info("send ERROR2")
end
end
-- 接收串口1数据
sys.subscribe("D_RECV_1", uread)
-- 接收网络通道1数据
sys.subscribe("N_RECV_1", nread)
-- demo 功能: modbus自定义数据的简要使用,需要注意订阅主题的使用
-- 说明:
-- 此demo对应配置为demo1_config.txt
-- 必须修改本脚本文件名为userapp.lua,通过上位机增量下载脚本写入。
-- userapp.lua 是用户端脚本启动入口(类似main),不限量脚本写入;如果是单个脚本,也可以用远程补丁写入;
-- 网络连接1配置为Tcp client,连接到PC上位机,数据源选规则,由脚本手动控制;
-- 网络连接2配置为TCP client,连接到Modbus slave,mb tcp模式,数据源选规则,由脚本手动控制。
-- modbus 设置为ATCP(任意输入的MBTCP协议),定义为自定义1通道。输入数据选择网络通道2,连接到pc的mb slave。
-- Mb主机使用网络通道2读写数据,完毕后通过自定义1主题上传数据,此脚本里面获取数据,处理后指定发送到网络通道1.
-- 如果只是需要自定义上行数据格式,直接通过“模板模式”的数据结构就可以实现,使用方法见下次分
local replytopic = "rwer"
local config = io.getDtu()
-- 在用户参数里面添加一个delay,默认10s
local mncode = config and config.up and config.up.MN or "PS202109069998"
local db = require "db"
local appcfg = db.new("/appcfg.cfg") -- 保存配置
local appInfo = appcfg and appcfg:export() or {}
local token = 0
-- 上行数据模板
local template = {
pakSn = 120,
action = "reportData",
token = "10529",
data = {
dataType = 1, -- 测量记录
timeStamp = 1575258021, -- unix 时间戳,本次测量结果的时标
waterLevel = 500, -- 液位 0.5m
flowSpeed = 500, -- 流速 0.5m/s
insFlow = 2000, -- 瞬时流量 m3/s 扩大1000倍
lastPerFlow = 600000, -- 上一周期累计流量 m3 扩大1000倍,相当于L
sumFlow = "87654321", -- 累计流量 单位 L
batVolt = 1200, -- 电源电压 V 扩大100倍
sigStrength = 20 -- 信号强度
}
}
-- 回复数据模板
local reply = {
id = "1",
version = "1.0",
type = "write_variant_reply",
time = 0,
code = 0
}
-- 下行数据的处理函数
local function msgSubHook(msg)
-- 这里的主题就是和发送是反的
sys.publish("D_SEND_21", data)
end
-- 上行消息的处理函数,在这里构造json
-- msg:内部上报的数据内容,注意这里不是单纯的table了。格式 {b = buff, d = jbuff, from = "MB_SEND"}
-- b是table格式数据,d是string格式数据
local function msgPubHook(msg)
-- 包装上行数据
local datamap = msg.b
template.data = {
dataType = 1, -- 测量记录
power = datamap.p,
sigStrength = 20 -- 信号强度
}
-- 转json上传
local buff = json.encode(template)
log.info("modbus pub msg1", buff)
-- 发送到网络通道1
sys.publish("N_SEND_1", buff)
end
local rules = {
-- 645数据源
["D6"] = msgPubHook
}
-- 规则订阅, 不同的数据来源由不同的函数处理
-- modbus上传的可能是数据,也可能是回复
local function ruleWorker(data, id, from)
local callback = rules[from]
if callback and data then callback(data) end
end
-- 自定义通道是从21开始: 24= 自定义4
-- 这里是自定义通道主动上报的数据,要用RECV主题接收.
-- 这里逻辑可以理解为“虚拟的串口功能RECV数据,在SEND到网络”.
-- 记得把网络通道调到规则引擎=无任何反应。不然会多发一条数据
sys.subscribe("D_RECV_24", ruleWorker)
-- sys.subscribe("D_RECV_3", function(data, a, b, id, type)
-- local buff = pack.pack(">bIbAbb", 0x7e, id, type, data, 0x7e, 0x7d)
-- log.info("recv can to net", id, type)
-- sys.publish("N_SEND_1", buff)
-- end)
-- can的数据源是串口3,发到网络1.pack库也可以使用lua54标准库string.pack
sys.subscribe("D_RECV_3", function(data, a, b, id, type)
local buff = pack.pack(">IbA", id, type, data)
log.info("recv can to net", id, type)
sys.publish("N_SEND_1", buff)
end)
local temp
--测试接收网络1的数据发到can
sys.subscribe("N_RECV_1", function(data)
local _, id, type = pack.unpack(data, ">Ib")
local buff = string.sub(data, 6, -1)
log.info("recv can", id, type, buff and string.toHex(buff))
temp = { d = buff, id = id, type = type }
sys.publish("D_SEND_3", { d = buff, id = id, type = type })
end)
-- 定时发心跳,不要注释掉
sys.timerLoopStart(function()
if temp then
sys.publish("D_SEND_3", temp)
end
end, 1000)
这是一个传感器说明书嘉佰达软件板通用协议-蓝牙修正-20231216_v12.pdf
这里演示实现读取基本信息0x03指令和单体电压0x04指令解析开发过程。
这个案例较复杂,也可能pdf对协议描述不是非常清楚完整,一共对话了10轮,以及让ai自己验证自己程序正确性,才得到下面的结果
创建新会话,给ai发我要使用lua5.4开发一个程序,功能是实现发送指令到串口,接收传感器返回的数据并解析生成json, 下面有一些硬件串口发送和接收的使用案例给你参考,需要实现读取基本信息指令和单体电压指令解析,帮我写出来。需要注意,发送指令参考文档里面直接发送不用计算生成,回复的数据校验长度但是不用校验crc
还没完,把上面的自定义串口解析两个教程的代码和pdf或者图片一起发给他,等他回复, 注意有些ai程序不能直接上传pdf或者不能识别, 截图也好,直接复制指令这些文字给他越好,越详细越好,反正你只管复制给他。
这是ai的回复,简单测试了一下可以运行,也能解析,直接userapp下载
目前ai智能程度还不能实现全自动,特别是ai对pdf或者图片的理解,以及程序对crc的处理生成的完全是错的,你干脆直接不要让他生成crc。最好是先给ai一个框架,收发数据案例直接发他,让他生成关键处理函数片段,这样效率最高。
对代码的理解,个人测试感觉,claude智能一点点,生成代码比较规范就是比较啰嗦; chatgpt生成比较简单高效,就是容易出弱智问题,你纠正他也会继续错; deepseek上传文件pdf或者图片识别率过低适合文字交流。
-- demo 功能: 自定义数据解析
-- 说明:
-- 此demo对应配置为demo3_config.txt
-- 必须修改本脚本文件名为userapp.lua,通过上位机增量下载脚本写入。
-- userapp.lua 是用户端脚本启动入口(类似main),不限量脚本写入;如果是单个脚本,也可以用远程补丁写入;
local config = io.getDtu()
-- 在用户参数里面添加一个delay,默认10s
local delay = config and config.up and config.up.delay and
tonumber(config.up.delay) or 10
-- 读取基本信息 0x03指令解析
local function parseBasicInfo(data)
log.info("parseBasicInfo: Start parsing basic info data, length=", #data)
if #data < 36 then -- 校验数据长度, 根据回复指令的长度调整
log.warn("parseBasicInfo: Basic Info Data Length Error, expected at least 36, got ", #data)
return nil
end
-- 校验帧头和指令
if string.byte(data, 1) ~= 0xDD or string.byte(data, 2) ~= 0x03 then
log.warn("parseBasicInfo: Basic Info Header Error, header=", string.format("0x%02X 0x%02X", string.byte(data, 1), string.byte(data, 2)))
return nil
end
local json_data = {
voltage = (string.byte(data, 4) * 256 + string.byte(data, 5)) * 10, -- 电压 10mV
current = (string.byte(data, 6) * 256 + string.byte(data, 7)) - 0x1700, --电流 mA 修正偏移量
capacity = (string.byte(data, 8) * 256 + string.byte(data, 9)) * 0.008, -- 剩余容量 0.008%
productionDate = 2078 + (string.byte(data, 10) * 256 + string.byte(data, 11)),-- 生产日期
protectStatus = (string.byte(data, 12) * 256 + string.byte(data, 13)), -- 保护状态
softwareVersion = string.byte(data, 14) * 0.1, -- 软件版本
RSOC = string.byte(data, 15),
FETControl = string.byte(data, 16),
cellCount = string.byte(data, 17), -- 电芯串数
NTC_Count = string.byte(data, 18),
NTC_Value = ((string.byte(data, 19) * 256 + string.byte(data, 20)) * 0.1) - 273.1,-- 电芯温度0.1K -273.1
alarmStatus = (string.byte(data, 21) * 256 + string.byte(data, 22)),-- 告警状态
remainCapacity = string.byte(data, 23) * 256 + string.byte(data, 24),-- 剩余容量
byte25 = string.byte(data,25),
byte26 = string.byte(data,26),
byte27 = string.byte(data,27),
byte28 = string.byte(data,28),
byte29 = string.byte(data,29),
byte30 = string.byte(data,30),
byte31 = string.byte(data,31),
byte32 = string.byte(data,32),
byte33 = string.byte(data,33),
byte34 = string.byte(data,34),
}
local json_str = json.encode(json_data)
log.info("parseBasicInfo: Successfully parsed basic info, JSON=", json_str)
return json_str
end
-- 单体电压 0x04指令解析
local function parseCellVoltages(data)
log.info("parseCellVoltages: Start parsing cell voltages data, length=", #data)
if #data < 7 then -- 校验数据长度, 至少包含帧头,长度字节,Checksum和结束符
log.warn("parseCellVoltages: Cell Voltage Data Length Error, expected at least 7, got ", #data)
return nil
end
-- 校验帧头和指令
if string.byte(data, 1) ~= 0xDD or string.byte(data, 2) ~= 0x04 then
log.warn("parseCellVoltages: Cell Voltage Header Error, header=", string.format("0x%02X 0x%02X", string.byte(data, 1), string.byte(data, 2)))
return nil
end
local dataLength = string.byte(data, 4) -- 数据长度 从第四个字节读取
log.info("parseCellVoltages: dataLength (byte 4)=", dataLength) -- 添加日志
local expectedLength = dataLength + 7 -- 期望长度, 因为dataLength本身就算长度,而且包含了checksum和结束符
if #data ~= expectedLength then
log.warn("parseCellVoltages: Cell Voltage Incorrect length, expected:", expectedLength, " actual:", #data)
return nil
end
local cell_voltages = {}
for i = 1, dataLength/2 do -- 每次循环处理两个字节
-- 使用 string.unpack 解析 2 字节电压数据 起始位置是 5
local voltage = string.unpack(">H", data:sub(5 + (i-1) * 2, 5 + (i-1) * 2 + 1))
log.debug("parseCellVoltages: Cell " .. i .. " voltage=" .. voltage)
table.insert(cell_voltages, voltage) --string.unpack返回的是tuple
end
local json_data = {
cell_voltages = cell_voltages
}
local json_str = json.encode(json_data)
log.info("parseCellVoltages: Successfully parsed cell voltages, JSON=", json_str)
return json_str
end
-- 这里解析串口返回的数据
local function msgPubHook(data)
log.info("msgPubHook: Received data from serial, length=", #data, ", data=", string.toHex(data))
-- 基本信息 0x03
if string.byte(data, 2) == 0x03 then
log.info("msgPubHook: Received Basic Info (0x03) response")
local json_str = parseBasicInfo(data)
if json_str then
log.info("msgPubHook: Parsed Basic Info JSON:", json_str)
sys.publish("N_SEND_1", json_str) -- 发送JSON到网络1
log.info("msgPubHook: Published Basic Info JSON to N_SEND_1")
else
log.warn("msgPubHook: Failed to parse Basic Info")
end
end
-- 单体电压 0x04
if string.byte(data, 2) == 0x04 then
log.info("msgPubHook: Received Cell Voltages (0x04) response")
local json_str = parseCellVoltages(data)
if json_str then
log.info("msgPubHook: Parsed Cell Voltages JSON:", json_str)
sys.publish("N_SEND_1", json_str) -- 发送JSON到网络1
log.info("msgPubHook: Published Cell Voltages JSON to N_SEND_1")
else
log.warn("msgPubHook: Failed to parse Cell Voltages")
end
end
end
-- 定时发送查询指令
sys.timerLoopStart(function()
-- 发送读取基本信息指令
local basic_info_cmd = string.char(0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77) -- 正确的指令
sys.publish("D_SEND_1", basic_info_cmd)
log.info("timerLoopStart: Sent Basic Info cmd:",string.toHex(basic_info_cmd))
-- 发送读取单体电压指令
local cell_voltage_cmd = string.char(0xDD, 0xA5, 0x04, 0x00, 0xFF, 0xFC, 0x77) -- 正确的指令
sys.publish("D_SEND_1", cell_voltage_cmd)
log.info("timerLoopStart: Sent Cell Voltage cmd:",string.toHex(cell_voltage_cmd))
end, 2 * 1000)
sys.subscribe("D_RECV_1", msgPubHook)
log.info("app start")