exmodbus_tcp.lua 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247
  1. -- 定义类结构;
  2. local modbus = {} -- 定义 modbus 实例的元表;
  3. modbus.__index = modbus -- 定义 modbus 实例的索引元方法,用于访问实例的属性;
  4. modbus.__metatable = "instance is protected" -- 定义 modbus 实例的元表,防止外部修改;
  5. -- 模块级变量:依赖注入的引用;
  6. local exmodbus_ref -- 主模块引用,用于访问enqueue_request等核心功能;
  7. local gen_id_func -- ID生成函数引用,用于生成唯一请求ID;
  8. -- Modbus TCP 协议头长度
  9. local MODBUS_TCP_HEADER_LEN = 7
  10. local libnet = require "libnet"
  11. -- 创建 modbus 实例的构造函数;
  12. function modbus:new(config, TASK_NAME)
  13. local obj = {
  14. mode = config.mode, -- 通信模式
  15. adapter = config.adapter, -- 网络适配器
  16. ip_address = config.ip_address, -- IP 地址
  17. port = config.port, -- 端口号
  18. is_udp = config.is_udp, -- 是否使用 UDP 协议
  19. is_tls = config.is_tls, -- 是否使用 TLS 加密
  20. keep_idle = config.keep_idle, -- 连接空闲多长时间后,开始发送第一个 keepalive 探针报文(秒)
  21. keep_interval = config.keep_interval, -- 发送第一个探针后,如果没收到 ACK 回复,间隔多久再发送下一个探针(秒)
  22. keep_cnt = config.keep_cnt, -- 总共发送多少次探针后,如果依然没有回复,则判定连接已断开
  23. server_cert = config.server_cert, -- TCP模式下的服务器ca证书数据,UDP模式下的PSK
  24. client_cert = config.client_cert, -- TCP模式下的客户端证书数据,UDP模式下的PSK-ID
  25. client_key = config.client_key, -- TCP模式下的客户端私钥加密数据
  26. client_password = config.client_password, -- TCP模式下的客户端私钥口令数据
  27. }
  28. -- 连接状态
  29. obj.is_connected = false
  30. -- 从站请求处理回调函数;
  31. obj.slaveHandler = nil
  32. -- 任务名称
  33. obj.TASK_NAME = TASK_NAME
  34. -- 接收数据缓冲区
  35. obj.recv_buff = nil
  36. -- 发送请求队列
  37. obj.send_queue = {}
  38. -- 当前等待响应的事务ID
  39. obj.pending_transaction = nil
  40. -- 设置原表;
  41. setmetatable(obj, modbus)
  42. -- 返回实例;
  43. return obj
  44. end
  45. -- 构建 Modbus TCP 请求帧(主站使用)
  46. local function build_tcp_frame(request_type, config)
  47. -- 参数验证
  48. if not config or type(config) ~= "table" then
  49. log.error("exmodbus", "配置必须是表格类型")
  50. return false
  51. end
  52. -- 验证必要参数
  53. if not config.slave_id then
  54. log.error("exmodbus", "缺少必要参数: slave_id")
  55. return false
  56. end
  57. if not config.reg_type then
  58. log.error("exmodbus", "缺少必要参数: reg_type")
  59. return false
  60. end
  61. if not config.start_addr then
  62. log.error("exmodbus", "缺少必要参数: start_addr")
  63. return false
  64. end
  65. if not config.reg_count then
  66. log.error("exmodbus", "缺少必要参数: reg_count")
  67. return false
  68. end
  69. if request_type == "write" then
  70. if not config.data then
  71. log.error("exmodbus", "缺少写入请求必要参数: data")
  72. return false
  73. end
  74. end
  75. -- 参数范围验证
  76. if type(config.slave_id) ~= "number" or config.slave_id < 1 or config.slave_id > 247 then
  77. log.error("exmodbus", "从站地址必须在 1-247 范围内")
  78. return false
  79. end
  80. if type(config.start_addr) ~= "number" or config.start_addr < 0 or config.start_addr > 65535 then
  81. log.error("exmodbus", "起始地址必须在 0-65535 范围内")
  82. return false
  83. end
  84. if config.reg_type ~= exmodbus_ref.COIL_STATUS and config.reg_type ~= exmodbus_ref.INPUT_STATUS and
  85. config.reg_type ~= exmodbus_ref.HOLDING_REGISTER and config.reg_type ~= exmodbus_ref.INPUT_REGISTER then
  86. log.error("exmodbus", "无效的寄存器类型: " .. tostring(config.reg_type))
  87. return false
  88. end
  89. -- 根据操作类型和寄存器类型确定功能码
  90. local func_code
  91. local data = ""
  92. if request_type == "read" then
  93. -- 读请求
  94. if config.reg_type == exmodbus_ref.COIL_STATUS then
  95. func_code = exmodbus_ref.READ_COILS
  96. if config.reg_count < 1 or config.reg_count > 2000 then
  97. log.error("exmodbus", "线圈读取数量超出范围: " .. config.reg_count .. " (范围: 1-2000)")
  98. return false
  99. end
  100. elseif config.reg_type == exmodbus_ref.INPUT_STATUS then
  101. func_code = exmodbus_ref.READ_DISCRETE_INPUTS
  102. if config.reg_count < 1 or config.reg_count > 2000 then
  103. log.error("exmodbus", "离散输入读取数量超出范围: " .. config.reg_count .. " (范围: 1-2000)")
  104. return false
  105. end
  106. elseif config.reg_type == exmodbus_ref.HOLDING_REGISTER then
  107. func_code = exmodbus_ref.READ_HOLDING_REGISTERS
  108. if config.reg_count < 1 or config.reg_count > 125 then
  109. log.error("exmodbus", "保持寄存器读取数量超出范围: " .. config.reg_count .. " (范围: 1-125)")
  110. return false
  111. end
  112. elseif config.reg_type == exmodbus_ref.INPUT_REGISTER then
  113. func_code = exmodbus_ref.READ_INPUT_REGISTERS
  114. if config.reg_count < 1 or config.reg_count > 125 then
  115. log.error("exmodbus", "输入寄存器读取数量超出范围: " .. config.reg_count .. " (范围: 1-125)")
  116. return false
  117. end
  118. end
  119. data = string.char(config.slave_id, func_code) ..
  120. string.char((config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF) ..
  121. string.char((config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF)
  122. else
  123. -- 写请求
  124. -- 校验每一个地址是否有数据,且数据是否为数字类型
  125. for i = 0, config.reg_count - 1 do
  126. local addr = config.start_addr + i
  127. if config.data[addr] == nil then
  128. log.error("exmodbus", "缺少寄存器数据", "address:", addr)
  129. return false
  130. end
  131. if type(config.data[addr]) ~= "number" then
  132. log.error("exmodbus", "寄存器数据必须是数字类型", "address:", addr)
  133. return false
  134. end
  135. end
  136. -- 判断是否强制使用写多个功能码
  137. local use_multiple = config.force_multiple
  138. if config.reg_type == exmodbus_ref.COIL_STATUS then
  139. if config.reg_count == 1 then
  140. if not use_multiple then
  141. func_code = exmodbus_ref.WRITE_SINGLE_COIL
  142. else
  143. func_code = exmodbus_ref.WRITE_MULTIPLE_COILS
  144. end
  145. else
  146. func_code = exmodbus_ref.WRITE_MULTIPLE_COILS
  147. if config.reg_count < 1 or config.reg_count > 1968 then
  148. log.error("exmodbus", "线圈写入数量超出范围: " .. config.reg_count .. " (范围: 1-1968)")
  149. return false
  150. end
  151. end
  152. elseif config.reg_type == exmodbus_ref.HOLDING_REGISTER then
  153. if config.reg_count == 1 then
  154. if not use_multiple then
  155. func_code = exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER
  156. else
  157. func_code = exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS
  158. end
  159. else
  160. func_code = exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS
  161. if config.reg_count < 1 or config.reg_count > 123 then
  162. log.error("exmodbus", "寄存器写入数量超出范围: " .. config.reg_count .. " (范围: 1-123)")
  163. return false
  164. end
  165. end
  166. else
  167. log.error("exmodbus", "不支持的寄存器类型")
  168. return nil
  169. end
  170. -- 构建写数据
  171. if func_code == exmodbus_ref.WRITE_SINGLE_COIL then
  172. -- 写入单个线圈,值必须是 0xFF00 (ON) 或 0x0000 (OFF)
  173. local value = config.data[config.start_addr] ~= 0 and 0xFF00 or 0x0000
  174. data = string.char(config.slave_id, func_code) ..
  175. string.char((config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF) ..
  176. string.char((value >> 8) & 0xFF, value & 0xFF)
  177. elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
  178. -- 写入单个保持寄存器
  179. local value = config.data[config.start_addr]
  180. if value < 0 or value > 65535 or value ~= math.floor(value) then
  181. log.error("exmodbus", "寄存器值必须是 0~65535 范围内的整数,实际值: ", value)
  182. return false
  183. end
  184. data = string.char(config.slave_id, func_code) ..
  185. string.char((config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF) ..
  186. string.char((value >> 8) & 0xFF, value & 0xFF)
  187. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
  188. -- 写入多个线圈
  189. local byte_count = math.ceil(config.reg_count / 8)
  190. local values_bytes = ""
  191. -- 构建线圈数据(字节序为大端序)
  192. for i = 0, byte_count - 1 do
  193. local byte_value = 0
  194. -- 遍历当前字节的 8 个位
  195. for j = 0, 7 do
  196. local bit_index = i * 8 + j
  197. -- 检查当前比特是否在有效范围内
  198. if bit_index < config.reg_count then
  199. local addr = config.start_addr + bit_index
  200. local bit_val = config.data[addr]
  201. if bit_val ~= nil and bit_val ~= 0 then
  202. byte_value = byte_value | (1 << j)
  203. end
  204. end
  205. end
  206. values_bytes = values_bytes .. string.char(byte_value)
  207. end
  208. data = string.char(config.slave_id, func_code) ..
  209. string.char((config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF) ..
  210. string.char((config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF) ..
  211. string.char(byte_count) .. values_bytes
  212. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
  213. -- 写入多个保持寄存器
  214. local byte_count = config.reg_count * 2
  215. local values_bytes = ""
  216. -- 构建寄存器数据(字节序为大端序)
  217. for i = 0, config.reg_count - 1 do
  218. local addr = config.start_addr + i
  219. local value = config.data[addr]
  220. if value < 0 or value > 65535 or value ~= math.floor(value) then
  221. log.error("exmodbus", "寄存器值必须是 0~65535 范围内的整数,地址:", addr, "值:", value)
  222. return false
  223. end
  224. values_bytes = values_bytes .. string.char((value >> 8) & 0xFF, value & 0xFF)
  225. end
  226. data = string.char(config.slave_id, func_code) ..
  227. string.char((config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF) ..
  228. string.char((config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF) ..
  229. string.char(byte_count) .. values_bytes
  230. end
  231. end
  232. -- 构建完整的 TCP 请求帧
  233. local transaction_id = gen_id_func() % 0x10000
  234. local length = #data -- 长度包含从站ID
  235. local frame = string.pack(">H", transaction_id) .. -- 事务 ID
  236. string.pack(">H", 0) .. -- 协议 ID
  237. string.pack(">H", length) .. -- 长度
  238. data -- 从站ID + PDU数据
  239. return frame, func_code, transaction_id
  240. end
  241. -- 解析 Modbus TCP 响应帧(主站使用)
  242. local function parse_tcp_response(response, config, expected_func_code, expected_transaction_id)
  243. -- 定义返回数据结构;
  244. local return_data = {
  245. status = false,
  246. execption_code = nil,
  247. data = {},
  248. }
  249. -- 检查响应是否为空
  250. if not response or #response == 0 then
  251. log.error("exmodbus", "响应报文为空")
  252. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  253. return return_data
  254. end
  255. -- 检查响应帧长度
  256. if #response < MODBUS_TCP_HEADER_LEN + 1 then
  257. log.error("exmodbus", "响应帧长度不足")
  258. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  259. return return_data
  260. end
  261. -- 解析 MBAP 头
  262. local transaction_id = string.unpack(">H", response, 1)
  263. local protocol_id = string.unpack(">H", response, 3)
  264. local length = string.unpack(">H", response, 5)
  265. local slave_id = string.unpack("B", response, 7)
  266. -- 检查事务 ID 是否匹配
  267. if transaction_id ~= expected_transaction_id then
  268. log.error("exmodbus", "事务ID不匹配")
  269. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  270. return return_data
  271. end
  272. -- 检查协议 ID
  273. if protocol_id ~= 0 then
  274. log.error("exmodbus", "无效的协议ID")
  275. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  276. return return_data
  277. end
  278. -- 检查数据长度
  279. if #response ~= 6 + length then
  280. log.error("exmodbus", "数据长度与实际长度不匹配")
  281. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  282. return return_data
  283. end
  284. -- 检查从站ID是否匹配
  285. if slave_id ~= config.slave_id then
  286. log.error("exmodbus", "从站地址不匹配,期望:", config.slave_id, "实际:", slave_id)
  287. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  288. return return_data
  289. end
  290. -- 解析功能码
  291. local func_code = string.unpack("B", response, 8)
  292. local response_length = #response
  293. -- 检查异常响应
  294. if bit.band(func_code, 0x80) ~= 0 then
  295. -- 检查异常响应报文长度是否正确
  296. if response_length ~= 9 then
  297. log.error("exmodbus", "异常响应报文长度不正确,期望: 9 字节,实际:", response_length, "字节")
  298. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  299. return return_data
  300. end
  301. -- 提取异常码
  302. local exception_code = string.unpack("B", response, 9)
  303. log.error("exmodbus", "接收到 Modbus 异常响应,功能码:", func_code, "异常码:", exception_code)
  304. return_data.status = exmodbus_ref.STATUS_EXCEPTION
  305. return_data.execption_code = exception_code
  306. return return_data
  307. end
  308. -- 检查功能码是否匹配
  309. if func_code ~= expected_func_code then
  310. log.error("exmodbus", "功能码不匹配,期望:", expected_func_code, "实际:", func_code)
  311. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  312. return return_data
  313. end
  314. -- 根据不同的功能码解析数据
  315. local parsed_data = {}
  316. -- 功能码 0x01 和 0x02:读取线圈状态和离散输入状态
  317. if func_code == exmodbus_ref.READ_COILS or func_code == exmodbus_ref.READ_DISCRETE_INPUTS then
  318. -- 提取数据部分
  319. local byte_count = string.unpack("B", response, 9)
  320. local data_start_pos = 10
  321. local data_end_pos = response_length
  322. -- 验证数据长度是否正确
  323. if data_end_pos - data_start_pos + 1 ~= byte_count then
  324. log.error("exmodbus", "数据长度不匹配,期望:", byte_count, "实际:", data_end_pos - data_start_pos + 1)
  325. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  326. return return_data
  327. end
  328. -- 验证字节数是否足够表示指定数量的位
  329. local expected_bytes = math.ceil(config.reg_count / 8)
  330. if byte_count < expected_bytes then
  331. log.error("exmodbus", "数据字节数不足,无法表示所有位")
  332. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  333. return return_data
  334. end
  335. -- 解析位数据
  336. for i = 0, config.reg_count - 1 do
  337. local modbus_addr = config.start_addr + i -- 计算当前位对应的 Modbus 地址
  338. local byte_pos = data_start_pos + math.floor(i / 8) -- 计算当前位对应的字节位置
  339. local bit_pos = i % 8 -- 计算当前位对应的位位置
  340. local byte_value = string.unpack("B", response, byte_pos)
  341. parsed_data[modbus_addr] = bit.band(byte_value, bit.lshift(1, bit_pos)) ~= 0 and 1 or 0
  342. end
  343. -- 功能码 0x03 和 0x04:读取保持寄存器和输入寄存器
  344. elseif func_code == exmodbus_ref.READ_HOLDING_REGISTERS or func_code == exmodbus_ref.READ_INPUT_REGISTERS then
  345. -- 提取数据部分
  346. local byte_count = string.unpack("B", response, 9)
  347. local data_start_pos = 10
  348. local data_end_pos = response_length
  349. -- 验证数据长度是否正确
  350. if data_end_pos - data_start_pos + 1 ~= byte_count then
  351. log.error("exmodbus", "数据长度不匹配,期望:", byte_count, "实际:", data_end_pos - data_start_pos + 1)
  352. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  353. return return_data
  354. end
  355. -- 验证字节数是否足够表示指定数量的寄存器
  356. local expected_bytes = config.reg_count * 2
  357. if byte_count < expected_bytes then
  358. log.error("exmodbus", "数据字节数不足,无法表示所有寄存器")
  359. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  360. return return_data
  361. end
  362. -- 解析寄存器数据(大端序)
  363. for i = 0, config.reg_count - 1 do
  364. local modbus_addr = config.start_addr + i -- 计算当前寄存器对应的 Modbus 地址
  365. local reg_pos = data_start_pos + i * 2 -- 计算当前寄存器对应的字节位置
  366. parsed_data[modbus_addr] = bit.lshift(string.unpack("B", response, reg_pos), 8) + string.unpack("B", response, reg_pos + 1)
  367. end
  368. -- 功能码 0x05:写入单个线圈
  369. elseif func_code == exmodbus_ref.WRITE_SINGLE_COIL then
  370. -- 写入单个线圈响应格式:事务ID(2 字节) + 协议ID(2 字节) + 长度(2 字节) + 从站地址(1 字节) + 功能码(1 字节) + 线圈地址(2 字节) + 线圈值(2 字节)
  371. if response_length ~= 12 then
  372. log.error("exmodbus", "写入单个线圈响应报文长度不正确")
  373. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  374. return return_data
  375. end
  376. -- 解析线圈地址和值
  377. local coil_addr = string.unpack(">H", response, 9)
  378. local coil_value = string.unpack(">H", response, 11)
  379. -- 验证地址是否匹配请求
  380. if config.start_addr and coil_addr ~= config.start_addr then
  381. log.error("exmodbus", "线圈地址不匹配,期望:", config.start_addr, "实际:", coil_addr)
  382. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  383. return return_data
  384. end
  385. -- 线圈值应该是 0x0000(OFF) 或 0xFF00(ON)
  386. local normalized_value = (coil_value == 0x0000) and 0 or 1
  387. parsed_data[coil_addr] = normalized_value
  388. -- 功能码 0x06:写入单个保持寄存器
  389. elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
  390. -- 写入单个保持寄存器响应格式:事务ID(2 字节) + 协议ID(2 字节) + 长度(2 字节) + 从站地址(1 字节) + 功能码(1 字节) + 寄存器地址(2 字节) + 寄存器值(2 字节)
  391. if response_length ~= 12 then
  392. log.error("exmodbus", "写入单个保持寄存器响应报文长度不正确")
  393. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  394. return return_data
  395. end
  396. -- 解析寄存器地址和值
  397. local reg_addr = string.unpack(">H", response, 9)
  398. local reg_value = string.unpack(">H", response, 11)
  399. -- 验证地址是否匹配请求
  400. if config.start_addr and reg_addr ~= config.start_addr then
  401. log.error("exmodbus", "单个保持寄存器地址不匹配,期望:", config.start_addr, "实际:", reg_addr)
  402. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  403. return return_data
  404. end
  405. parsed_data[reg_addr] = reg_value
  406. -- 功能码 0x0F:写入多个线圈
  407. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
  408. -- 写入多个线圈响应格式:事务ID(2 字节) + 协议ID(2 字节) + 长度(2 字节) + 从站地址(1 字节) + 功能码(1 字节) + 起始地址(2 字节) + 线圈数量(2 字节)
  409. if response_length ~= 12 then
  410. log.error("exmodbus", "写入多个线圈响应报文长度不正确")
  411. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  412. return return_data
  413. end
  414. -- 解析起始地址和线圈数量
  415. local start_addr = string.unpack(">H", response, 9)
  416. local coil_count = string.unpack(">H", response, 11)
  417. -- 验证地址和数量是否匹配请求
  418. if config.start_addr and start_addr ~= config.start_addr then
  419. log.error("exmodbus", "线圈起始地址不匹配,期望:", config.start_addr, "实际:", start_addr)
  420. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  421. return return_data
  422. end
  423. if config.reg_count and coil_count ~= config.reg_count then
  424. log.error("exmodbus", "线圈数量不匹配,期望:", config.reg_count, "实际:", coil_count)
  425. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  426. return return_data
  427. end
  428. -- 在返回数据中记录操作成功的起始地址和数量
  429. parsed_data.start_addr = start_addr
  430. parsed_data.count = coil_count
  431. -- 功能码 0x10:写入多个保持寄存器
  432. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
  433. -- 写入多个保持寄存器响应格式:事务ID(2 字节) + 协议ID(2 字节) + 长度(2 字节) + 从站地址(1 字节) + 功能码(1 字节) + 起始地址(2 字节) + 寄存器数量(2 字节)
  434. if response_length ~= 12 then
  435. log.error("exmodbus", "写入多个保持寄存器响应报文长度不正确")
  436. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  437. return return_data
  438. end
  439. -- 解析起始地址和寄存器数量
  440. local start_addr = string.unpack(">H", response, 9)
  441. local reg_count = string.unpack(">H", response, 11)
  442. -- 验证地址和数量是否匹配请求
  443. if config.start_addr and start_addr ~= config.start_addr then
  444. log.error("exmodbus", "寄存器起始地址不匹配,期望:", config.start_addr, "实际:", start_addr)
  445. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  446. return return_data
  447. end
  448. if config.reg_count and reg_count ~= config.reg_count then
  449. log.error("exmodbus", "寄存器数量不匹配,期望:", config.reg_count, "实际:", reg_count)
  450. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  451. return return_data
  452. end
  453. -- 在返回数据中记录操作成功的起始地址和数量
  454. parsed_data.start_addr = start_addr
  455. parsed_data.count = reg_count
  456. -- 未知功能码
  457. else
  458. log.error("exmodbus", "不支持的功能码解析:", func_code)
  459. return_data.status = exmodbus_ref.STATUS_DATA_INVALID
  460. return return_data
  461. end
  462. -- 成功解析响应数据
  463. return_data.data = parsed_data
  464. return_data.status = exmodbus_ref.STATUS_SUCCESS
  465. return return_data
  466. end
  467. -- TCP 主站发送请求并等待响应
  468. local function sendRequest_waitResponse(instance, request_frame, config)
  469. -- 检查连接状态
  470. if not instance.is_connected or not instance.socket_client then
  471. log.error("exmodbus", "TCP 连接未建立或已断开,无法发送请求")
  472. return false
  473. end
  474. -- 从请求帧中提取事务ID和功能码
  475. local transaction_id = string.unpack(">H", request_frame, 1)
  476. local function_code = string.unpack("B", request_frame, 8)
  477. -- 创建请求信息并添加到发送队列
  478. local request_info = {
  479. request_frame = request_frame,
  480. function_code = function_code,
  481. transaction_id = transaction_id,
  482. config = config
  483. }
  484. table.insert(instance.send_queue, request_info)
  485. -- log.info("exmodbus", "请求已添加到发送队列", transaction_id)
  486. sys.sendMsg(instance.TASK_NAME, socket.EVENT, 0)
  487. -- 等待响应
  488. local _, response_data = sys.waitUntil("exmodbus/tcp_resp/" .. transaction_id,
  489. config.timeout or 5000)
  490. if not response_data then
  491. log.error("exmodbus", "等待响应超时")
  492. return false, nil
  493. end
  494. return true, response_data
  495. end
  496. -- TCP 主站接收数据处理函数
  497. local function tcp_master_receiver(instance)
  498. if instance.recv_buff == nil then
  499. instance.recv_buff = zbuff.create(1024)
  500. end
  501. while true do
  502. local succ, param = socket.rx(instance.socket_client, instance.recv_buff)
  503. if not succ then
  504. log.info("exmodbus", "读取数据失败")
  505. return false
  506. end
  507. if instance.recv_buff:used() > 0 then
  508. local data = instance.recv_buff:query()
  509. sys.publish("exmodbus/tcp_resp/" .. instance.current_transaction_id, data)
  510. -- log.info("exmodbus", "读取数据成功")
  511. instance.recv_buff:del()
  512. else
  513. break
  514. end
  515. end
  516. return true
  517. end
  518. -- TCP 主站发送数据处理函数
  519. local function tcp_master_sender(instance)
  520. -- 检查发送队列中是否有请求
  521. while #instance.send_queue > 0 do
  522. -- 取出队列中的第一个请求
  523. local request_info = table.remove(instance.send_queue, 1)
  524. local request_frame = request_info.request_frame
  525. local function_code = request_info.function_code
  526. local transaction_id = request_info.transaction_id
  527. local config = request_info.config
  528. -- 设置当前事务信息
  529. instance.current_transaction_id = transaction_id
  530. -- 发送请求
  531. local result, buff_full = libnet.tx(instance.TASK_NAME, 15000, instance.socket_client, request_frame)
  532. if not result then
  533. log.error("exmodbus", "发送请求失败")
  534. return true
  535. end
  536. if buff_full then
  537. log.error("exmodbus", "缓冲区已满,将请求重新放回队列队首")
  538. -- 将请求重新放回队列队首
  539. table.insert(instance.send_queue, 1, request_info)
  540. end
  541. end
  542. return true
  543. end
  544. -- TCP 主站主任务函数
  545. local function tcp_master_main_task_func(instance)
  546. local result, param
  547. while true do
  548. -- 创建 socket 客户端
  549. instance.socket_client = socket.create(instance.adapter, instance.TASK_NAME)
  550. if not instance.socket_client then
  551. log.error("exmodbus", "创建 socket 客户端失败")
  552. goto EXCEPTION_PROC
  553. end
  554. -- 配置 socket
  555. result = socket.config(instance.socket_client)
  556. if not result then
  557. log.error("exmodbus", "配置 socket 失败")
  558. goto EXCEPTION_PROC
  559. end
  560. -- 连接服务器
  561. result = libnet.connect(instance.TASK_NAME, 15000, instance.socket_client, instance.ip_address, instance.port)
  562. if not result then
  563. log.error("exmodbus", "连接服务器失败")
  564. goto EXCEPTION_PROC
  565. end
  566. log.info("exmodbus", "连接服务器成功")
  567. instance.is_connected = true
  568. -- 主循环
  569. while true do
  570. -- 处理接收数据
  571. if not tcp_master_receiver(instance) then
  572. log.info("exmodbus", "接收数据处理失败")
  573. break
  574. end
  575. -- 处理发送数据
  576. if not tcp_master_sender(instance) then
  577. log.info("exmodbus", "发送数据处理失败")
  578. break
  579. end
  580. -- 等待事件
  581. result, param = libnet.wait(instance.TASK_NAME, 0, instance.socket_client)
  582. if not result then
  583. log.info("exmodbus", "连接断开")
  584. break
  585. end
  586. end
  587. -- 异常处理
  588. ::EXCEPTION_PROC::
  589. -- 关闭连接
  590. if instance.socket_client then
  591. libnet.close(instance.TASK_NAME, 5000, instance.socket_client)
  592. socket.release(instance.socket_client)
  593. instance.socket_client = nil
  594. instance.is_connected = false
  595. end
  596. -- 等待 5 秒后重试
  597. sys.wait(5000)
  598. end
  599. end
  600. -- 解析 TCP 请求帧(从站使用)
  601. local function parse_tcp_request(data)
  602. -- 检查请求帧长度,至少包含 MBAP 头和功能码
  603. if #data < MODBUS_TCP_HEADER_LEN + 1 then
  604. log.error("exmodbus", "请求帧长度不足")
  605. return nil, "请求帧长度不足"
  606. end
  607. -- 解析 MBAP 头(事务标识符(2)、协议标识符(2)、数据长度(2)、从站地址(1))
  608. local transaction_id = string.unpack(">H", data, 1)
  609. local protocol_id = string.unpack(">H", data, 3)
  610. local length = string.unpack(">H", data, 5)
  611. local slave_id = string.unpack("B", data, 7)
  612. -- 检查数据长度是否与实际长度匹配
  613. if #data ~= 6 + length then
  614. log.error("exmodbus", "数据长度与实际长度不匹配")
  615. return nil, "数据长度与实际长度不匹配"
  616. end
  617. -- 检查协议 ID(Modbus TCP 协议 ID 必须为 0)
  618. if protocol_id ~= 0 then
  619. log.error("exmodbus", "无效的协议 ID")
  620. return nil, "无效的协议 ID"
  621. end
  622. -- 解析功能码
  623. local func_code = string.unpack("B", data, 8)
  624. local request = {
  625. transaction_id = transaction_id,
  626. protocol_id = protocol_id,
  627. length = length,
  628. slave_id = slave_id,
  629. func_code = func_code,
  630. reg_type = nil,
  631. start_addr = nil,
  632. reg_count = nil,
  633. data = {},
  634. }
  635. -- 根据功能码解析请求内容
  636. if func_code == exmodbus_ref.READ_COILS or func_code == exmodbus_ref.READ_DISCRETE_INPUTS then
  637. -- 读线圈或离散输入
  638. request.reg_type = func_code == exmodbus_ref.READ_COILS and exmodbus_ref.COIL_STATUS or exmodbus_ref.DISCRETE_INPUT_STATUS
  639. request.start_addr = string.unpack(">H", data, 9)
  640. request.reg_count = string.unpack(">H", data, 11)
  641. elseif func_code == exmodbus_ref.READ_HOLDING_REGISTERS or func_code == exmodbus_ref.READ_INPUT_REGISTERS then
  642. -- 读保持寄存器或输入寄存器
  643. request.reg_type = func_code == exmodbus_ref.READ_HOLDING_REGISTERS and exmodbus_ref.HOLDING_REGISTER or exmodbus_ref.INPUT_REGISTER
  644. request.start_addr = string.unpack(">H", data, 9)
  645. request.reg_count = string.unpack(">H", data, 11)
  646. elseif func_code == exmodbus_ref.WRITE_SINGLE_COIL then
  647. -- 写单个线圈
  648. request.reg_type = exmodbus_ref.COIL_STATUS
  649. request.start_addr = string.unpack(">H", data, 9)
  650. request.reg_count = 1
  651. local value = string.unpack(">H", data, 11)
  652. request.data = { [request.start_addr] = value == 0xFF00 and 1 or 0 }
  653. elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
  654. -- 写单个寄存器
  655. request.reg_type = exmodbus_ref.HOLDING_REGISTER
  656. request.start_addr = string.unpack(">H", data, 9)
  657. request.reg_count = 1
  658. local value = string.unpack(">H", data, 11)
  659. request.data = { [request.start_addr] = value }
  660. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
  661. -- 写多个线圈
  662. request.reg_type = exmodbus_ref.COIL_STATUS
  663. request.start_addr = string.unpack(">H", data, 9)
  664. request.reg_count = string.unpack(">H", data, 11)
  665. -- local byte_count = string.unpack("B", data, 13)
  666. request.data = {}
  667. for i = 0, request.reg_count - 1 do
  668. local byte_pos = 13 + 1 + math.floor(i / 8)
  669. local bit_pos = i % 8
  670. local byte_value = string.unpack("B", data, byte_pos)
  671. local bit_value = bit.band(byte_value, bit.lshift(1, bit_pos)) > 0 and 1 or 0
  672. request.data[request.start_addr + i] = bit_value
  673. end
  674. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
  675. -- 写多个寄存器
  676. request.reg_type = exmodbus_ref.HOLDING_REGISTER
  677. request.start_addr = string.unpack(">H", data, 9)
  678. request.reg_count = string.unpack(">H", data, 11)
  679. -- local byte_count = string.unpack("B", data, 13)
  680. request.data = {}
  681. for i = 0, request.reg_count - 1 do
  682. local value = string.unpack(">H", data, 13 + 1 + i * 2)
  683. request.data[request.start_addr + i] = value
  684. end
  685. else
  686. log.error("exmodbus", "不支持的功能码:", func_code)
  687. end
  688. return request
  689. end
  690. -- 构建 Modbus TCP 响应帧(从站使用);
  691. local function build_tcp_response(request, user_return)
  692. local slave_id = request.slave_id
  693. local func_code = request.func_code
  694. -- 用户返回异常码 -> 异常响应;
  695. if type(user_return) == "number" then
  696. local exception_code = user_return
  697. local response_payload = string.char(slave_id, bit.bor(func_code, 0x80), exception_code)
  698. -- 构建完整的 TCP 响应帧
  699. local length = #response_payload
  700. local response = string.pack(">H", request.transaction_id) .. -- 事务 ID
  701. string.pack(">H", 0) .. -- 协议 ID
  702. string.pack(">H", length) .. -- 长度
  703. response_payload
  704. return response
  705. end
  706. -- 用户返回表 -> 正常响应;
  707. if type(user_return) ~= "table" then
  708. log.error("exmodbus", "从站回调必须返回 table 或 number,实际类型: ", type(user_return))
  709. return nil
  710. end
  711. local response_payload = ""
  712. -- 处理读线圈和读离散输入响应;
  713. if func_code == exmodbus_ref.READ_COILS or func_code == exmodbus_ref.READ_DISCRETE_INPUTS then
  714. local reg_count = request.reg_count
  715. -- 校验 reg_count 是否有效;
  716. if not reg_count or reg_count <= 0 then
  717. log.error("exmodbus", "请求中 reg_count 无效")
  718. return nil
  719. end
  720. local byte_count = math.ceil(reg_count / 8)
  721. local values = {}
  722. for i = 0, reg_count - 1 do
  723. local addr = request.start_addr + i
  724. local bit_val = user_return[addr]
  725. if bit_val == nil then
  726. log.error("exmodbus", "读线圈/离散输入回调未返回地址 ", addr, " 的数据")
  727. return nil
  728. end
  729. if bit_val ~= 0 and bit_val ~= 1 then
  730. log.error("exmodbus", "地址 ", addr, " 的值必须为 0 或 1,实际: ", bit_val)
  731. return nil
  732. end
  733. local byte_idx = math.floor(i / 8)
  734. if not values[byte_idx] then values[byte_idx] = 0 end
  735. if bit_val == 1 then
  736. values[byte_idx] = bit.bor(values[byte_idx], bit.lshift(1, i % 8))
  737. end
  738. end
  739. response_payload = string.char(slave_id, func_code, byte_count)
  740. for i = 0, byte_count - 1 do
  741. response_payload = response_payload .. string.char(values[i] or 0)
  742. end
  743. -- 处理读保持寄存器和读输入寄存器响应;
  744. elseif func_code == exmodbus_ref.READ_HOLDING_REGISTERS or func_code == exmodbus_ref.READ_INPUT_REGISTERS then
  745. local reg_count = request.reg_count
  746. -- 校验 reg_count 是否有效;
  747. if not reg_count or reg_count <= 0 then
  748. log.error("exmodbus", "请求中 reg_count 无效")
  749. return nil
  750. end
  751. local values = ""
  752. for i = 0, reg_count - 1 do
  753. local addr = request.start_addr + i
  754. local val = user_return[addr]
  755. if val == nil then
  756. log.error("exmodbus", "读保持寄存器/输入寄存器回调未返回地址 ", addr, " 的数据")
  757. return nil
  758. end
  759. if type(val) ~= "number" or val ~= math.floor(val) or val < 0 or val > 65535 then
  760. log.error("exmodbus", "地址 ", addr, " 的值必须为 0~65535 的整数,实际: ", val)
  761. return nil
  762. end
  763. values = values .. string.char((val >> 8) & 0xFF, val & 0xFF)
  764. end
  765. response_payload = string.char(slave_id, func_code, #values) .. values
  766. -- 处理写单个线圈响应;
  767. elseif func_code == exmodbus_ref.WRITE_SINGLE_COIL then
  768. local addr = request.start_addr
  769. -- 校验 start_addr 是否有效;
  770. if addr == nil then
  771. log.error("exmodbus", "请求中 start_addr 无效")
  772. return nil
  773. end
  774. local coil_val = (request.data and request.data[addr]) or 0
  775. local resp_val = (coil_val ~= 0) and 0xFF00 or 0x0000
  776. response_payload = string.char(slave_id, func_code) ..
  777. string.char((addr >> 8) & 0xFF, addr & 0xFF,
  778. (resp_val >> 8) & 0xFF, resp_val & 0xFF)
  779. -- 处理写单个保持寄存器响应;
  780. elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
  781. local addr = request.start_addr
  782. -- 校验 start_addr 是否有效;
  783. if addr == nil then
  784. log.error("exmodbus", "请求中 start_addr 无效")
  785. return nil
  786. end
  787. local reg_val = (request.data and request.data[addr]) or 0
  788. -- 校验 reg_val 是否有效;
  789. if type(reg_val) ~= "number" or reg_val ~= math.floor(reg_val) or reg_val < 0 or reg_val > 65535 then
  790. log.error("exmodbus", "地址 ", addr, " 的值必须为 0~65535 的整数,实际: ", reg_val)
  791. return nil
  792. end
  793. response_payload = string.char(slave_id, func_code) ..
  794. string.char((addr >> 8) & 0xFF, addr & 0xFF,
  795. (reg_val >> 8) & 0xFF, reg_val & 0xFF)
  796. -- 处理写多个线圈/保持寄存器响应;
  797. elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS or func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
  798. local start_addr = request.start_addr
  799. local reg_count = request.reg_count
  800. -- 校验 start_addr 和 reg_count 是否有效;
  801. if not start_addr or not reg_count or reg_count <= 0 then
  802. log.error("exmodbus", "请求中 start_addr 或 reg_count 无效")
  803. return nil
  804. end
  805. response_payload = string.char(slave_id, func_code) ..
  806. string.char((start_addr >> 8) & 0xFF, start_addr & 0xFF,
  807. (reg_count >> 8) & 0xFF, reg_count & 0xFF)
  808. -- 处理未知功能码,视为错误;
  809. else
  810. log.error("exmodbus", "不支持的功能码,且未返回异常码: ", func_code)
  811. return nil
  812. end
  813. -- 构建完整的 TCP 响应帧
  814. local length = #response_payload -- 长度包含从站ID
  815. local response = string.pack(">H", request.transaction_id) .. -- 事务 ID
  816. string.pack(">H", 0) .. -- 协议 ID
  817. string.pack(">H", length) .. -- 长度(包含从站ID)
  818. response_payload -- 从站ID + PDU数据
  819. return response
  820. end
  821. -- TCP 从站接收数据处理函数;
  822. local function tcp_receiver(netc, instance)
  823. -- 如果数据接收缓冲区还没有申请过空间,则先申请内存空间
  824. if instance.recv_buff == nil then
  825. instance.recv_buff = zbuff.create(1024)
  826. end
  827. -- 循环从内核的缓冲区读取接收到的数据
  828. while true do
  829. -- 从内核的缓冲区中读取数据到 instance.recv_buff 中
  830. local succ, param = socket.rx(netc, instance.recv_buff)
  831. -- 读取数据失败
  832. if not succ then
  833. log.info("exmodbus", "读取数据失败,已接收数据长度", param)
  834. return false
  835. end
  836. -- 如果读取到了数据
  837. if instance.recv_buff:used() > 0 then
  838. -- log.info("exmodbus", "已接收数据长度", instance.recv_buff:used())
  839. -- 读取数据
  840. local data = instance.recv_buff:query()
  841. -- 解析 TCP 请求帧
  842. local request, err = parse_tcp_request(data)
  843. if request then
  844. -- 广播地址(0)不响应;
  845. if request.slave_id == 0 then
  846. -- 调用回调以允许用户记录或处理广播命令(如写寄存器);
  847. if instance.slaveHandler then
  848. instance.slaveHandler(request)
  849. -- 注意:即使回调返回数据,也不发送响应;
  850. end
  851. -- 广播请求处理完毕,清除对应的报文数据
  852. local expected_len = request.length + MODBUS_TCP_HEADER_LEN - 1
  853. instance.recv_buff:del(0, expected_len)
  854. -- log.info("exmodbus", "广播请求处理完毕,清除报文长度:", expected_len)
  855. -- 广播请求处理完毕,不回复;
  856. break
  857. end
  858. if instance.slaveHandler then
  859. local user_return = instance.slaveHandler(request)
  860. local response = build_tcp_response(request, user_return)
  861. if response then
  862. libnet.tx(instance.TASK_NAME, 0, netc, response)
  863. sys.sendMsg(instance.TASK_NAME, socket.EVENT, 0)
  864. else
  865. log.error("exmodbus", "构建响应帧失败,从站地址:", request.slave_id)
  866. end
  867. -- 清除当前请求数据
  868. local expected_len = request.length + MODBUS_TCP_HEADER_LEN - 1
  869. instance.recv_buff:del(0, expected_len)
  870. -- log.info("exmodbus", "请求处理完毕,清除报文长度:", expected_len)
  871. else
  872. log.warn("exmodbus", "收到主站请求,但未注册回调函数")
  873. -- 清除当前请求数据
  874. local expected_len = request.length + MODBUS_TCP_HEADER_LEN - 1
  875. instance.recv_buff:del(0, expected_len)
  876. log.info("exmodbus", "清除报文长度:", expected_len)
  877. end
  878. else
  879. if err == "请求帧长度不足" then
  880. -- 请求帧长度不足,等待更多数据
  881. -- log.info("exmodbus", "请求帧长度不足,等待更多数据")
  882. break
  883. elseif err == "数据长度与实际长度不匹配" then
  884. -- 数据长度与实际长度不匹配,清空缓冲区
  885. -- log.warn("exmodbus", "数据长度与实际长度不匹配,清空缓冲区")
  886. instance.recv_buff:del()
  887. break
  888. elseif err == "协议 ID 错误" then
  889. -- 协议 ID 错误,清空缓冲区
  890. -- log.warn("exmodbus", "协议 ID 错误,清空缓冲区")
  891. instance.recv_buff:del()
  892. break
  893. end
  894. end
  895. else
  896. -- 没有数据可读
  897. break
  898. end
  899. end
  900. return true
  901. end
  902. local function tcp_slave_main_task_func(instance)
  903. local netc = nil
  904. local result, param
  905. while true do
  906. -- 创建 TCP 服务器
  907. netc = socket.create(instance.adapter, instance.TASK_NAME)
  908. if not netc then
  909. log.error("exmodbus", "创建 TCP 服务器失败")
  910. goto EXCEPTION_PROC
  911. end
  912. -- 配置服务器
  913. result = socket.config(netc, instance.port)
  914. if not result then
  915. log.error("exmodbus", "配置 TCP 服务器失败")
  916. goto EXCEPTION_PROC
  917. end
  918. -- 监听端口
  919. result = libnet.listen(instance.TASK_NAME, 0, netc)
  920. if not result then
  921. log.error("exmodbus", "监听端口失败")
  922. goto EXCEPTION_PROC
  923. end
  924. log.info("exmodbus", "TCP 从站已启动,监听端口:", instance.port)
  925. -- 处理连接和数据
  926. while true do
  927. -- 处理接收数据
  928. if not tcp_receiver(netc, instance) then
  929. log.info("exmodbus", "接收数据处理失败")
  930. break
  931. end
  932. -- 等待事件
  933. result, param = libnet.wait(instance.TASK_NAME, 0, netc)
  934. if not result then
  935. log.info("exmodbus", "客户端断开连接")
  936. break
  937. end
  938. end
  939. -- 异常处理
  940. ::EXCEPTION_PROC::
  941. -- 关闭连接
  942. if netc then
  943. libnet.close(instance.TASK_NAME, 5000, netc)
  944. socket.release(netc)
  945. netc = nil
  946. end
  947. -- 等待 5 秒后重试
  948. sys.wait(5000)
  949. end
  950. end
  951. -- 创建一个新的实例;
  952. local function create(config, exmodbus, gen_request_id)
  953. exmodbus_ref = exmodbus
  954. gen_id_func = gen_request_id
  955. local TASK_NAME = "exmodbus_tcp_task_"..gen_id_func()
  956. -- 创建一个新的实例;
  957. local instance = modbus:new(config, TASK_NAME)
  958. -- 检查实例是否创建成功;
  959. if not instance then
  960. log.error("exmodbus", "创建 Modbus 实例失败")
  961. return false
  962. end
  963. -- 根据模式启动不同的任务
  964. if config.mode == exmodbus_ref.TCP_MASTER then
  965. -- 启动主站任务
  966. sys.taskInitEx(tcp_master_main_task_func, TASK_NAME, nil, instance)
  967. log.info("exmodbus", "TCP 主站任务已启动")
  968. elseif config.mode == exmodbus_ref.TCP_SLAVE then
  969. -- 启动从站任务
  970. sys.taskInitEx(tcp_slave_main_task_func, TASK_NAME, nil, instance)
  971. log.info("exmodbus", "TCP 从站任务已启动")
  972. else
  973. log.error("exmodbus", "不支持的 TCP 模式")
  974. return false
  975. end
  976. -- 返回实例;
  977. return instance
  978. end
  979. function modbus:destroy()
  980. -- 停止任务
  981. sys.taskDel(self.TASK_NAME)
  982. -- 释放缓冲区
  983. if self.recv_buff then
  984. self.recv_buff:free()
  985. self.recv_buff = nil
  986. end
  987. end
  988. -- 内部读函数
  989. function modbus:read_internal(config)
  990. -- 处理响应结果;
  991. local parsed_data = {}
  992. -- 检查是否同时指定了 slave_id 和 raw_request
  993. if config.slave_id and config.raw_request then
  994. log.error("exmodbus", "禁止同时指定 slave_id 和 raw_request")
  995. parsed_data.status = exmodbus_ref.STATUS_PARAM_INVALID
  996. return parsed_data
  997. end
  998. if config.slave_id then
  999. local request_frame, function_code, transaction_id = build_tcp_frame("read", config)
  1000. if not request_frame then
  1001. log.error("exmodbus", "构建 TCP 读取请求失败")
  1002. parsed_data.status = exmodbus_ref.STATUS_PARAM_INVALID
  1003. return parsed_data
  1004. end
  1005. -- 发送请求并等待响应;
  1006. local result, response = sendRequest_waitResponse(self, request_frame, config)
  1007. if not result then
  1008. parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
  1009. else
  1010. -- 解析响应数据;
  1011. parsed_data = parse_tcp_response(response, config, function_code, transaction_id)
  1012. end
  1013. elseif config.raw_request then
  1014. -- 发送请求并等待响应;
  1015. local result, response = sendRequest_waitResponse(self, config.raw_request, config)
  1016. if not result then
  1017. parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
  1018. else
  1019. -- 直接返回响应结果和原始响应数据;
  1020. parsed_data.status = exmodbus_ref.STATUS_SUCCESS
  1021. parsed_data.raw_response = response
  1022. end
  1023. end
  1024. return parsed_data
  1025. end
  1026. -- 主站写入请求的函数;
  1027. function modbus:write_internal(config)
  1028. -- 处理响应结果;
  1029. local parsed_data = {}
  1030. -- 检查是否同时指定了 slave_id 和 raw_request
  1031. if config.slave_id and config.raw_request then
  1032. log.error("exmodbus", "禁止同时指定 slave_id 和 raw_request")
  1033. parsed_data.status = exmodbus_ref.STATUS_PARAM_INVALID
  1034. return parsed_data
  1035. end
  1036. if config.slave_id then
  1037. local request_frame, function_code, transaction_id = build_tcp_frame("write", config)
  1038. if not request_frame then
  1039. log.error("exmodbus", "构建 TCP 写入请求失败")
  1040. parsed_data.status = exmodbus_ref.STATUS_PARAM_INVALID
  1041. return parsed_data
  1042. end
  1043. -- 发送请求并等待响应;
  1044. local result, response = sendRequest_waitResponse(self, request_frame, config)
  1045. if not result then
  1046. parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
  1047. else
  1048. -- 解析响应数据;
  1049. parsed_data = parse_tcp_response(response, config, function_code, transaction_id)
  1050. end
  1051. elseif config.raw_request then
  1052. -- 发送请求并等待响应;
  1053. local result, response = sendRequest_waitResponse(self, config.raw_request, config)
  1054. if not result then
  1055. parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
  1056. else
  1057. -- 直接返回响应结果和原始响应数据;
  1058. parsed_data.status = exmodbus_ref.STATUS_SUCCESS
  1059. parsed_data.raw_response = response
  1060. end
  1061. end
  1062. return parsed_data
  1063. end
  1064. -- 读函数(主站使用)
  1065. function modbus:read(config)
  1066. return exmodbus_ref.enqueue_request(self, config, true)
  1067. end
  1068. -- 写函数(主站使用)
  1069. function modbus:write(config)
  1070. return exmodbus_ref.enqueue_request(self, config, false)
  1071. end
  1072. -- 注册从站请求处理回调函数;
  1073. function modbus:on(callback)
  1074. if type(callback) ~= "function" then
  1075. log.error("exmodbus", "on(callback) 的参数必须是一个函数")
  1076. return false
  1077. end
  1078. self.slaveHandler = callback
  1079. log.info("exmodbus", "已注册从站请求处理回调函数")
  1080. return true
  1081. end
  1082. return { create = create }