excloud.lua 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215
  1. --[[
  2. @summary excloud扩展库
  3. @version 1.0
  4. @date 2025.09.22
  5. @author 孟伟
  6. @usage
  7. -- 应用场景
  8. 该扩展库适用于各种物联网设备(如4G/WiFi/以太网设备)与云端服务器进行数据交互的场景。
  9. 可用于设备状态上报、数据采集、远程控制等物联网应用。
  10. 实现的功能:
  11. 1. 支持多种设备类型(4G/WiFi/以太网)的接入认证
  12. 2. 提供TCP和MQTT两种传输协议选择
  13. 3. 实现设备与云端的双向通信(数据上报和命令下发)
  14. 4. 支持数据的TLV格式编解码
  15. 5. 提供自动重连机制,保证连接稳定性
  16. 6. 支持不同数据类型(整数、浮点数、布尔值、字符串、二进制等)的传输
  17. -- 用法实例
  18. 本扩展库对外提供了以下6个接口:
  19. 1. excloud.setup(params) - 设置配置参数
  20. 2. excloud.on(cbfunc) - 注册回调函数
  21. 3. excloud.open() - 开启excloud服务
  22. 4. excloud.send(data, need_reply, is_auth_msg) - 发送数据
  23. 5. excloud.close() - 关闭excloud服务
  24. 6. excloud.status() - 获取当前状态
  25. -- 示例:
  26. -- 导入excloud库
  27. local excloud = require("excloud")
  28. -- 注册回调函数
  29. excloud.on(function(event, data)
  30. log.info("用户回调函数", event, json.encode(data))
  31. if event == "connect_result" then
  32. if data.success then
  33. log.info("连接成功")
  34. else
  35. log.info("连接失败: " .. (data.error or "未知错误"))
  36. end
  37. elseif event == "auth_result" then
  38. if data.success then
  39. log.info("认证成功")
  40. else
  41. log.info("认证失败: " .. data.message)
  42. end
  43. elseif event == "message" then
  44. log.info("收到消息, 流水号: " .. data.header.sequence_num)
  45. -- 处理服务器下发的消息
  46. for _, tlv in ipairs(data.tlvs) do
  47. if tlv.field == excloud.FIELD_MEANINGS.CONTROL_COMMAND then
  48. log.info("收到控制命令: " .. tostring(tlv.value))
  49. -- 处理控制命令并发送响应
  50. local response_ok, err_msg = excloud.send({
  51. {
  52. field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
  53. data_type = excloud.DATA_TYPES.ASCII,
  54. value = "命令执行成功"
  55. }
  56. }, false)
  57. if not response_ok then
  58. log.info("发送控制响应失败: " .. err_msg)
  59. end
  60. end
  61. end
  62. elseif event == "disconnect" then
  63. log.warn("与服务器断开连接")
  64. elseif event == "reconnect_failed" then
  65. log.info("重连失败,已尝试 " .. data.count .. " 次")
  66. elseif event == "send_result" then
  67. if data.success then
  68. log.info("发送成功,流水号: " .. data.sequence_num)
  69. else
  70. log.info("发送失败: " .. data.error_msg)
  71. end
  72. end
  73. end)
  74. sys.taskInit(function()
  75. -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
  76. while not socket.adapter(socket.dft()) do
  77. log.warn("tcp_client_main_task_func", "wait IP_READY", socket.dft())
  78. -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
  79. -- 或者等待1秒超时退出阻塞等待状态;
  80. -- 注意:此处的1000毫秒超时不要修改的更长;
  81. -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
  82. -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
  83. -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
  84. sys.waitUntil("IP_READY", 1000)
  85. end
  86. sys.wait(1000)
  87. -- 配置excloud参数
  88. -- local ok, err_msg = excloud.setup({
  89. -- -- device_id = "862419074073247", -- 设备ID (IMEI前14位)
  90. -- device_type = 1, -- 设备类型: 4G
  91. -- host = "112.125.89.8", -- 服务器地址
  92. -- port = 33316, -- 服务器端口
  93. -- auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi", -- 鉴权密钥
  94. -- transport = "tcp", -- 使用TCP传输
  95. -- auto_reconnect = true, -- 自动重连
  96. -- reconnect_interval = 10, -- 重连间隔(秒)
  97. -- max_reconnect = 5, -- 最大重连次数
  98. -- timeout = 30, -- 超时时间(秒)
  99. -- })
  100. if not ok then
  101. log.info("初始化失败: " .. err_msg)
  102. return
  103. end
  104. log.info("excloud初始化成功")
  105. -- 开启excloud服务
  106. local ok, err_msg = excloud.open()
  107. if not ok then
  108. log.info("开启excloud服务失败: " .. err_msg)
  109. return
  110. end
  111. log.info("excloud服务已开启")
  112. -- 在主循环中定期上报数据
  113. while true do
  114. -- 每30秒上报一次数据
  115. sys.wait(30000)
  116. local ok, err_msg = excloud.send({
  117. {
  118. field_meaning = excloud.FIELD_MEANINGS.LOCATION_METHOD,
  119. data_type = excloud.DATA_TYPES.INTEGER,
  120. value = 22 -- 随机温度值
  121. },
  122. {
  123. field_meaning = excloud.FIELD_MEANINGS.HUMIDITY,
  124. data_type = excloud.DATA_TYPES.FLOAT,
  125. value = 33.2543 -- 随机湿度值
  126. }
  127. }, false) -- 不需要服务器回复
  128. if not ok then
  129. log.info("发送数据失败: " .. err_msg)
  130. else
  131. log.info("数据发送成功")
  132. end
  133. end
  134. end)
  135. ]]
  136. local excloud = {}
  137. local config = {
  138. device_type = 1, -- 默认设备类型: 4G
  139. device_id = "", -- 设备ID
  140. protocol_version = 1, -- 协议版本
  141. transport = "tcp", -- 传输协议: tcp/mqtt
  142. host = "cloud.luatos.com", -- 服务器地址
  143. port = 8900, -- 服务器端口
  144. auth_key = nil, -- 用户鉴权密钥
  145. keepalive = 300, -- mqtt心跳
  146. auto_reconnect = true, -- 是否自动重连
  147. reconnect_interval = 10, -- 重连间隔(秒)
  148. max_reconnect = 3, -- 最大重连次数
  149. timeout = 30, -- 连接超时时间(秒)
  150. qos = 0, -- MQTT QoS等级
  151. -- retain = false, -- MQTT retain标志
  152. clean_session = true, -- MQTT clean session标志
  153. ssl = false, -- 是否使用SSL
  154. username = nil, -- MQTT用户名
  155. password = nil, -- MQTT密码
  156. udp_auth_key = nil, -- UDP鉴权密钥
  157. -- 新增socket配置参数
  158. local_port = nil, -- 本地端口号,nil表示自动分配
  159. keep_idle = nil, -- TCP keepalive idle时间(秒)
  160. keep_interval = nil, -- TCP keepalive 探测间隔(秒)
  161. keep_cnt = nil, -- TCP keepalive 探测次数
  162. server_cert = nil, -- 服务器CA证书数据
  163. client_cert = nil, -- 客户端证书数据
  164. client_key = nil, -- 客户端私钥数据
  165. client_password = nil, -- 客户端私钥口令
  166. -- MQTT扩展参数
  167. -- mqtt_rx_size = 32 * 1024, -- MQTT接收缓冲区大小,默认32K
  168. -- mqtt_conn_timeout = 30, -- MQTT连接超时时间
  169. -- mqtt_ipv6 = false, -- 是否使用IPv6连接
  170. }
  171. local callback_func = nil -- 回调函数
  172. local is_open = false -- 服务是否开启
  173. local is_connected = false -- 是否已连接
  174. local is_authenticated = false -- 是否已鉴权
  175. local sequence_num = 0 -- 流水号
  176. local connection = nil -- 连接对象
  177. local device_id_binary = nil -- 二进制格式的设备ID
  178. local reconnect_timer = nil -- 重连定时器
  179. local reconnect_count = 0 -- 重连次数
  180. local pending_messages = {} -- 待发送消息队列
  181. local rxbuff = nil -- 接收缓冲区
  182. local connect_timeout_timer = nil -- 连接超时定时器
  183. -- 数据类型定义
  184. local DATA_TYPES = {
  185. INTEGER = 0x0, -- 整数
  186. FLOAT = 0x1, -- 浮点数
  187. BOOLEAN = 0x2, -- 布尔值
  188. ASCII = 0x3, -- ASCII字符串
  189. BINARY = 0x4, -- 二进制数据
  190. UNICODE = 0x5 -- Unicode字符串
  191. }
  192. -- 字段含义定义
  193. local FIELD_MEANINGS = {
  194. -- 控制信令类型 (16-255)
  195. AUTH_REQUEST = 16, -- 鉴权请求
  196. AUTH_RESPONSE = 17, -- 鉴权回复
  197. REPORT_RESPONSE = 18, -- 上报回应
  198. CONTROL_COMMAND = 19, -- 控制命令
  199. CONTROL_RESPONSE = 20, -- 控制回应
  200. IRTU_DOWN = 21, -- iRTU下行命令
  201. IRTU_UP = 22, -- iRTU上行回复
  202. -- 传感类 (256-511)
  203. TEMPERATURE = 256, -- 温度
  204. HUMIDITY = 257, -- 湿度
  205. PARTICULATE = 258, -- 颗粒数
  206. ACIDITY = 259, -- 酸度
  207. ALKALINITY = 260, -- 碱度
  208. ALTITUDE = 261, -- 海拔
  209. WATER_LEVEL = 262, -- 水位
  210. ENV_TEMPERATURE = 263, -- CPU温度/环境温度
  211. POWER_METERING = 264, -- 电量计量
  212. -- 资产管理类 (512-767)
  213. GNSS_LONGITUDE = 512, -- GNSS经度
  214. GNSS_LATITUDE = 513, -- GNSS纬度
  215. SPEED = 514, -- 行驶速度
  216. GNSS_CN = 515, -- 最强的4颗GNSS卫星的CN
  217. SATELLITES_TOTAL = 516, -- 搜到的所有卫星数
  218. SATELLITES_VISIBLE = 517, -- 可见卫星数
  219. HEADING = 518, -- 航向角
  220. LOCATION_METHOD = 519, -- 基站定位/GNSS定位标识
  221. GNSS_INFO = 520, -- GNSS芯片型号和固件版本号
  222. DIRECTION = 521, -- 方向
  223. -- 设备参数类 (768-1023)
  224. HEIGHT = 768, -- 高度
  225. WIDTH = 769, -- 宽度
  226. ROTATION_SPEED = 770, -- 转速
  227. BATTERY_LEVEL = 771, -- 电量(mV)
  228. SERVING_CELL = 772, -- 驻留频段
  229. CELL_INFO = 773, -- 驻留小区和邻区
  230. COMPONENT_MODEL = 774, -- 元器件型号
  231. GPIO_LEVEL = 775, -- GPIO高低电平
  232. BOOT_REASON = 776, -- 开机原因
  233. BOOT_COUNT = 777, -- 开机次数
  234. SLEEP_MODE = 778, -- 休眠模式
  235. WAKE_INTERVAL = 779, -- 定时唤醒间隔
  236. NETWORK_IP_TYPE = 780, -- 设备入网的IP类型
  237. NETWORK_TYPE = 781, -- 当前联网方式
  238. SIGNAL_STRENGTH_4G = 782, --4G信号强度
  239. SIM_ICCID = 783, -- SIM卡ICCID
  240. -- 软件数据类 (1024-1279)
  241. LUA_CORE_ERROR = 1024, -- Lua核心库错误上报
  242. LUA_EXT_ERROR = 1025, -- Lua扩展卡错误上报
  243. LUA_APP_ERROR = 1026, -- Lua业务错误上报
  244. FIRMWARE_VERSION = 1027, -- 固件版本号
  245. SMS_FORWARD = 1028, -- SMS转发
  246. CALL_FORWARD = 1029, -- 来电转发
  247. -- 设备无关数据类 (1280-1535)
  248. TIMESTAMP = 1280, -- 时间
  249. RANDOM_DATA = 1281 -- 无意义数据
  250. }
  251. -- 将数字转换为大端字节序列
  252. local function to_big_endian(num, bytes)
  253. local result = {}
  254. for i = bytes, 1, -1 do
  255. result[i] = string.char(num % 256)
  256. num = math.floor(num / 256)
  257. end
  258. return table.concat(result)
  259. end
  260. -- 从大端字节序列转换为数字
  261. local function from_big_endian(data, start, length)
  262. local value = 0
  263. for i = start, start + length - 1 do
  264. value = value * 256 + data:byte(i)
  265. end
  266. -- log.info("from_big_endian", value)
  267. return value
  268. end
  269. -- 将设备ID进行编码
  270. function packDeviceInfo(deviceType, deviceId)
  271. -- 验证设备类型
  272. if deviceType ~= 1 and deviceType ~= 2 then
  273. log.info("设备类型错误: 4G设备应为1, WIFI设备应为2")
  274. end
  275. -- 设备类型字节
  276. local result = { string.char(deviceType) }
  277. -- 清理设备ID(移除非数字和字母字符,并转换为大写)
  278. local cleanId = deviceId:gsub("[^%w]", ""):upper()
  279. -- 处理不同类型的设备ID
  280. if deviceType == 1 then
  281. -- 4G设备 - IMEI处理
  282. -- 只取前14位数字,忽略第15位
  283. cleanId = cleanId:gsub("%D", ""):sub(1, 14)
  284. -- 确保长度为14位(不足时前面补0)
  285. if #cleanId < 14 then
  286. cleanId = string.rep("0", 14 - #cleanId) .. cleanId
  287. end
  288. -- 转换为BCD格式的字节
  289. for i = 1, 14, 2 do
  290. local byte = (tonumber(cleanId:sub(i, i)) * 16) + tonumber(cleanId:sub(i + 1, i + 1))
  291. table.insert(result, string.char(byte))
  292. end
  293. elseif deviceType == 2 then
  294. -- WIFI设备 - MAC地址处理
  295. -- 移除非十六进制字符
  296. cleanId = cleanId:gsub("[^0-9A-Fa-f]", "")
  297. -- 确保长度为12个十六进制字符(6字节)
  298. if #cleanId < 12 then
  299. cleanId = string.rep("0", 12 - #cleanId) .. cleanId
  300. else
  301. cleanId = cleanId:sub(1, 12)
  302. end
  303. -- 转换为字节
  304. local bytes = {}
  305. for i = 1, 12, 2 do
  306. local byteStr = cleanId:sub(i, i + 1)
  307. table.insert(bytes, string.char(tonumber(byteStr, 16)))
  308. end
  309. -- 确保有7个字节(不足时前面补0)
  310. while #bytes < 7 do
  311. table.insert(bytes, 1, string.char(0))
  312. end
  313. -- 添加到结果中
  314. for _, byte in ipairs(bytes) do
  315. table.insert(result, byte)
  316. end
  317. else
  318. log.info("未知设备类型 ")
  319. return deviceId
  320. end
  321. -- 返回8字节的二进制数据
  322. return table.concat(result)
  323. end
  324. -- -- 编码数据值
  325. local function encode_value(data_type, value)
  326. -- 添加参数类型检查
  327. if data_type == nil or value == nil then
  328. log.info("Data type or value is nil")
  329. return ""
  330. end
  331. if data_type == DATA_TYPES.INTEGER then
  332. -- 验证value是否为数字
  333. if type(value) ~= "number" then
  334. log.info("Integer value must be a number")
  335. return ""
  336. end
  337. return to_big_endian(math.floor(value), 4)
  338. elseif data_type == DATA_TYPES.FLOAT then
  339. -- 验证value是否为数字
  340. if type(value) ~= "number" then
  341. log.info("Float value must be a number")
  342. return ""
  343. end
  344. -- 简化处理:将浮点数转换为整数,乘以1000以保留三位小数
  345. return to_big_endian(math.floor(value * 1000), 4)
  346. elseif data_type == DATA_TYPES.BOOLEAN then
  347. return value and "\1" or "\0"
  348. elseif data_type == DATA_TYPES.ASCII or data_type == DATA_TYPES.BINARY or data_type == DATA_TYPES.UNICODE then
  349. -- 确保value是字符串类型
  350. return tostring(value)
  351. else
  352. log.info("Unsupported data type: " .. tostring(data_type))
  353. -- 返回空字符串而不是nil,避免后续处理出错
  354. return ""
  355. end
  356. end
  357. -- 解码数据值
  358. local function decode_value(data_type, value)
  359. if data_type == DATA_TYPES.INTEGER then
  360. return from_big_endian(value, 1, #value)
  361. elseif data_type == DATA_TYPES.FLOAT then
  362. -- 简化处理:将整数转换为浮点数(实际应使用IEEE 754格式)
  363. return from_big_endian(value, 1, #value) / 1000
  364. elseif data_type == DATA_TYPES.BOOLEAN then
  365. return value:byte(1) ~= 0
  366. elseif data_type == DATA_TYPES.ASCII then
  367. return value
  368. elseif data_type == DATA_TYPES.BINARY then
  369. return value
  370. elseif data_type == DATA_TYPES.UNICODE then
  371. return value
  372. else
  373. log.info("Unsupported data type: " .. data_type)
  374. return nil
  375. end
  376. end
  377. -- 构建消息头
  378. -- @param need_reply boolean 是否需要服务器回复
  379. -- @param has_auth_key boolean 是否携带鉴权key
  380. -- @param data_length number 数据长度
  381. local function build_header(need_reply, is_udp_transport, data_length)
  382. sequence_num = (sequence_num + 1) % 65536
  383. -- 消息标识字段
  384. local flags = config.protocol_version -- bit0-3: 协议版本号
  385. if need_reply then
  386. flags = flags + 16 -- bit4: 是否需要回复
  387. end
  388. if is_udp_transport then
  389. flags = flags + 32 -- bit5: 是否是UDP承载
  390. end
  391. log.info("构建消息头", device_id_binary, to_big_endian(sequence_num, 2), to_big_endian(data_length, 2),
  392. to_big_endian(flags, 4))
  393. return device_id_binary ..
  394. to_big_endian(sequence_num, 2) ..
  395. to_big_endian(data_length, 2) ..
  396. to_big_endian(flags, 4)
  397. end
  398. -- 构建TLV字段
  399. local function build_tlv(field_meaning, data_type, value)
  400. if field_meaning == nil or data_type == nil or value == nil then
  401. log.info("构建tlv参数不能为空")
  402. return false
  403. end
  404. local value_encoded = encode_value(data_type, value)
  405. if value_encoded == nil then
  406. log.info("构建tlv打包数据时长度为0")
  407. -- 添加空字符串作为默认值,避免后续获取长度时出错
  408. value_encoded = ""
  409. end
  410. local length = #value_encoded
  411. -- 字段类型(字段含义 + 数据类型)
  412. local head = (field_meaning & 0x0FFF) | (data_type << 12) -- 2 字节头
  413. return true, to_big_endian(head, 2) ..
  414. to_big_endian(length, 2) ..
  415. value_encoded
  416. end
  417. -- 解析消息头
  418. local function parse_header(header)
  419. if #header < 16 then
  420. log.info("消息头解析失败", "Header too short")
  421. return nil, "Header too short"
  422. end
  423. local device_id = header:sub(1, 8)
  424. local seq_num = from_big_endian(header, 9, 2)
  425. local msg_length = from_big_endian(header, 11, 2)
  426. local flags = from_big_endian(header, 13, 4)
  427. -- 提取标志位
  428. local protocol_version = flags % 16
  429. local need_reply = (flags % 32) >= 16
  430. local is_udp_transport = (flags % 64) >= 32
  431. -- 打印解析结果,方便调试
  432. -- log.info("消息头解析结果",
  433. -- string.format(
  434. -- "device_id: %s, sequence_num: %d, msg_length: %d, protocol_version: %d, need_reply: %s, is_udp_transport: %s",
  435. -- string.toHex(device_id), seq_num, msg_length, protocol_version,
  436. -- tostring(need_reply), tostring(is_udp_transport)))
  437. return {
  438. device_id = string.toHex(device_id),
  439. sequence_num = seq_num,
  440. msg_length = msg_length,
  441. protocol_version = protocol_version,
  442. need_reply = need_reply,
  443. is_udp_transport = is_udp_transport
  444. }
  445. end
  446. -- 工具函数:解析TLV
  447. local function parse_tlv(data, startPos)
  448. -- 检查数据是否足够解析TLV的T的长度。
  449. if #data < startPos + 3 then
  450. return nil, startPos, "TLV data too short"
  451. end
  452. local fieldType = from_big_endian(data, startPos, 2)
  453. local length = from_big_endian(data, startPos + 2, 2)
  454. -- 提取原始字节值
  455. local value = data:sub(startPos + 4, startPos + 4 + length - 1)
  456. --解析TLV字段中的T
  457. -- bit0-11: 字段含义
  458. -- bit12-15: 数据类型
  459. local field_meaning = fieldType & 0x0FFF -- 取低12位作为字段含义
  460. local data_type = fieldType >> 12 -- 取高4位作为数据类型
  461. local decoded_value = decode_value(data_type, value)
  462. -- log.info("消息体解析结果", field_meaning, data_type, decoded_value)
  463. return {
  464. field = field_meaning,
  465. type = data_type,
  466. value = decoded_value,
  467. length = length, --数据长度
  468. }, startPos + 4 + length
  469. end
  470. -- 解析完整消息
  471. local function parse_message(data)
  472. local header, err = parse_header(data:sub(1, 16))
  473. if not header then
  474. return nil, err
  475. end
  476. local auth_key = nil
  477. local body_start = 17
  478. -- 如果是UDP传输,解析认证key
  479. if header.is_udp_transport then
  480. if #data >= body_start + 64 - 1 then
  481. auth_key = data:sub(body_start, body_start + 64 - 1)
  482. body_start = body_start + 64
  483. else
  484. return nil, "Incomplete UDP authentication key"
  485. end
  486. end
  487. -- 解析TLV字段
  488. local tlvs = {}
  489. local pos = body_start
  490. local end_pos = 16 + (header.msg_length)
  491. if #data < end_pos then
  492. return nil, "Message incomplete"
  493. end
  494. while pos < end_pos do
  495. local tlv, new_pos, err = parse_tlv(data, pos)
  496. if not tlv then
  497. return nil, "Failed to parse TLV at position " .. err
  498. end
  499. table.insert(tlvs, tlv)
  500. -- 更新解析位置为解析完当前TLV字段后的新位置,以便继续解析后续的TLV字段
  501. pos = new_pos
  502. end
  503. return {
  504. header = header,
  505. auth_key = auth_key,
  506. tlvs = tlvs
  507. }
  508. end
  509. -- 发送鉴权请求
  510. local function send_auth_request()
  511. if not config.auth_key then
  512. return false, "No auth key configured"
  513. end
  514. local auth_data
  515. -- auth_data = config.auth_key .. "-" .. config.device_id .. "-" .. "323B131815B0DFC9"
  516. --设备实测时打开
  517. if config.device_type == 1 then
  518. auth_data = config.auth_key .. "-" .. mobile.imei() .. "-" .. mobile.muid()
  519. elseif config.device_type == 2 then
  520. auth_data = config.auth_key .. "-" .. wlan.getMac(nil, true) .. "-" .. mobile.muid():toHex()
  521. else
  522. auth_data = config.auth_key .. "-"
  523. end
  524. local message = {
  525. {
  526. field_meaning = FIELD_MEANINGS.AUTH_REQUEST,
  527. data_type = DATA_TYPES.ASCII,
  528. value = auth_data
  529. }
  530. }
  531. -- log.info("send auth request", message,message[1].value,message[1].data_type,message[1].field_meaning)
  532. return excloud.send(message, true, true) -- 鉴权消息需要设置 is_auth_msg 为 true
  533. end
  534. -- 处理认证响应AAA
  535. local function handle_auth_response(tlvs)
  536. for _, tlv in ipairs(tlvs) do
  537. if tlv.field_meaning == FIELD_MEANINGS.AUTH_RESPONSE then
  538. local success = (tlv.value == "OK" or tlv.value == "SUCCESS")
  539. is_authenticated = success
  540. if callback_func then
  541. callback_func("auth_result", {
  542. success = success,
  543. message = tlv.value
  544. })
  545. end
  546. -- 认证成功,发送待处理消息
  547. if success then
  548. for _, msg in ipairs(pending_messages) do
  549. excloud.send(msg.data, msg.need_reply)
  550. end
  551. pending_messages = {}
  552. end
  553. return success
  554. end
  555. end
  556. return false
  557. end
  558. -- 接收消息解析处理
  559. local function parse_data(data)
  560. local message, err = parse_message(data)
  561. if not message then
  562. log.info("Failed to parse message: " .. err)
  563. return
  564. end
  565. --数据返回给回调
  566. if callback_func then
  567. callback_func("message", message)
  568. end
  569. -- -- 如果需要回复,发送确认AAA
  570. -- if message.header.need_reply then
  571. -- local response = {
  572. -- {
  573. -- field_meaning = FIELD_MEANINGS.REPORT_RESPONSE,
  574. -- data_type = DATA_TYPES.ASCII,
  575. -- value = "ACK"
  576. -- }
  577. -- }
  578. -- excloud.send(response, false)
  579. -- end
  580. end
  581. -- 重连 AAA
  582. local function schedule_reconnect()
  583. if reconnect_count >= config.max_reconnect then
  584. log.info("到达最大重连次数 " .. reconnect_count)
  585. if callback_func then
  586. callback_func("reconnect_failed", { count = reconnect_count })
  587. end
  588. return
  589. end
  590. reconnect_count = reconnect_count + 1
  591. log.info("重连次数 " .. reconnect_count)
  592. -- 使用定时器安排重连
  593. reconnect_timer = sys.timerStart(function()
  594. log.info("等待重连")
  595. excloud.open()
  596. end, config.reconnect_interval * 1000)
  597. end
  598. -- TCP socket事件回调函数
  599. local function tcp_socket_callback(netc, event, param)
  600. log.info("socket cb", netc, event, param)
  601. -- 取消连接超时定时器
  602. if connect_timeout_timer then
  603. sys.timerStop(connect_timeout_timer)
  604. connect_timeout_timer = nil
  605. end
  606. if param ~= 0 then
  607. log.info("socket", "连接断开")
  608. is_connected = false
  609. is_authenticated = false
  610. if callback_func then
  611. callback_func("disconnect", {})
  612. end
  613. -- 连接断开,释放资源
  614. socket.release(connection)
  615. connection = nil
  616. -- 尝试重连
  617. if config.auto_reconnect and is_open then
  618. is_open = false
  619. schedule_reconnect()
  620. end
  621. return
  622. end
  623. if event == socket.LINK then
  624. -- 网络连接成功
  625. log.info("socket", "网络连接成功")
  626. elseif event == socket.ON_LINE then
  627. -- TCP连接成功
  628. log.info("socket", "TCP连接成功")
  629. is_connected = true
  630. -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
  631. reconnect_count = 0
  632. if callback_func then
  633. callback_func("connect_result", { success = true })
  634. end
  635. -- 发送认证请求
  636. send_auth_request()
  637. elseif event == socket.EVENT then
  638. -- 有数据到达
  639. socket.rx(netc, rxbuff)
  640. if rxbuff:used() > 0 then
  641. local data = rxbuff:query()
  642. log.info("socket", "收到数据", #data, "字节", data:toHex())
  643. -- 处理接收到的数据
  644. parse_data(data)
  645. end
  646. -- 清空缓冲区
  647. rxbuff:del()
  648. elseif event == socket.TX_OK then
  649. socket.wait(netc)
  650. log.info("socket", "发送完成")
  651. elseif event == socket.CLOSED then
  652. -- 连接错误或关闭
  653. log.info("socket", "主动断开链接")
  654. end
  655. end
  656. -- mqtt client的事件回调函数
  657. local function mqtt_client_event_cbfunc(connected, event, data, payload, metas)
  658. log.info("mqtt_client_event_cbfunc", connected, event, data, payload, json.encode(metas))
  659. -- 取消连接超时定时器
  660. if connect_timeout_timer then
  661. sys.timerStop(connect_timeout_timer)
  662. connect_timeout_timer = nil
  663. end
  664. -- mqtt连接成功
  665. if event == "conack" then
  666. is_connected = true
  667. log.info("MQTT connected")
  668. -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
  669. reconnect_count = 0
  670. -- 订阅主题
  671. local auth_topic = "/AirCloud/down/" .. config.device_id .. "/auth"
  672. local all_topic = "/AirCloud/down/" .. config.device_id .. "/all"
  673. log.info("mqtt_client_event_cbfunc", "订阅主题", auth_topic, all_topic)
  674. connection:subscribe(auth_topic, 0)
  675. connection:subscribe(all_topic, 0)
  676. if callback_func then
  677. callback_func("connect_result", { success = true })
  678. end
  679. -- 发送认证请求
  680. send_auth_request()
  681. -- 订阅成功
  682. elseif event == "suback" then
  683. -- 取消订阅成功
  684. elseif event == "unsuback" then
  685. -- 接收到服务器下发的publish数据
  686. -- data:string类型,表示topic
  687. -- payload:string类型,表示payload
  688. -- metas:table类型,数据内容如下
  689. -- {
  690. -- qos: number类型,取值范围0,1,2
  691. -- retain:number类型,取值范围0,1
  692. -- dup:number类型,取值范围0,1
  693. -- message_id: number类型
  694. -- }
  695. elseif event == "recv" then
  696. -- 对接收到的publish数据处理
  697. parse_data(payload)
  698. -- 发送成功publish数据
  699. -- data:number类型,表示message id
  700. elseif event == "sent" then
  701. -- 服务器断开mqtt连接
  702. elseif event == "disconnect" then
  703. is_connected = false
  704. is_authenticated = false
  705. connection:disconnect()
  706. log.info("MQTT disconnected")
  707. if callback_func then
  708. callback_func("disconnect", {})
  709. end
  710. -- 尝试重连
  711. if config.auto_reconnect and is_open then
  712. is_open = false
  713. schedule_reconnect()
  714. end
  715. -- 收到服务器的心跳应答
  716. elseif event == "pong" then
  717. -- 严重异常,本地会主动断开连接
  718. -- data:string类型,表示具体的异常,有以下几种:
  719. -- "connect":tcp连接失败
  720. -- "tx":数据发送失败
  721. -- "conack":mqtt connect后,服务器应答CONNACK鉴权失败,失败码为payload(number类型)
  722. -- "other":其他异常
  723. elseif event == "error" then
  724. is_connected = false
  725. is_authenticated = false
  726. connection:disconnect()
  727. local error_msg = "Unknown MQTT error"
  728. if data == "connect" then
  729. error_msg = "TCP connection failed"
  730. elseif data == "tx" then
  731. error_msg = "Data transmission failed"
  732. elseif data == "conack" then
  733. error_msg = "MQTT authentication failed with code: " .. tostring(payload)
  734. end
  735. log.info("MQTT error: " .. error_msg)
  736. if callback_func then
  737. callback_func("disconnect", { error = error_msg })
  738. end
  739. -- 尝试重连
  740. if config.auto_reconnect and is_open then
  741. is_open = false
  742. schedule_reconnect()
  743. end
  744. end
  745. end
  746. -- 设置配置参数
  747. function excloud.setup(params)
  748. if is_open then
  749. return false, "excloud is already open"
  750. end
  751. -- 合并配置参数
  752. for k, v in pairs(params) do
  753. config[k] = v
  754. end
  755. -- 验证必要参数
  756. if config.device_type == 1 then
  757. config.device_id = mobile.imei()
  758. elseif config.device_type == 2 then
  759. config.device_id = wlan.getMac(nil, true)
  760. --以太网设备
  761. elseif config.device_type == 4 then
  762. config.device_id = netdrv.mac(socket.LWIP_ETH)
  763. else
  764. log.info("未知设备类型", config.device_type)
  765. config.device_id = "unknown"
  766. end
  767. -- 打包设备id
  768. device_id_binary = packDeviceInfo(config.device_type, config.device_id)
  769. return true
  770. end
  771. -- 注册回调函数
  772. function excloud.on(cbfunc)
  773. if type(cbfunc) ~= "function" then
  774. return false, "Callback must be a function"
  775. end
  776. callback_func = cbfunc
  777. return true
  778. end
  779. -- 开启excloud服务
  780. function excloud.open()
  781. -- 检查是否已打开
  782. if is_open then
  783. return false, "excloud is already open"
  784. end
  785. --判断是否初始化
  786. if not device_id_binary then
  787. return false, "excloud 没有初始化,请先调用setup"
  788. end
  789. -- 根据传输协议创建连接
  790. if config.transport == "tcp" then
  791. -- 创建接收缓冲区
  792. rxbuff = zbuff.create(2048)
  793. -- 创建TCP连接
  794. log.info("创建TCP连接")
  795. connection = socket.create(nil, tcp_socket_callback)
  796. if not connection then
  797. return false, "Failed to create socket"
  798. end
  799. -- 准备SSL配置参数
  800. local ssl_config = nil
  801. if config.ssl then
  802. if type(config.ssl) == "table" then
  803. -- 使用详细的SSL配置
  804. ssl_config = config.ssl
  805. else
  806. -- 简单的SSL启用
  807. ssl_config = true
  808. end
  809. end
  810. -- 配置socket参数
  811. local config_success = socket.config(
  812. connection,
  813. config.local_port, -- 本地端口号
  814. false, -- 是否是UDP,TCP连接为false
  815. ssl_config and true or false, -- 是否是加密传输
  816. config.keep_idle, -- keepalive idle时间
  817. config.keep_interval, -- keepalive 探测间隔
  818. config.keep_cnt, -- keepalive 探测次数
  819. ssl_config and ssl_config.server_cert or nil, -- 服务器CA证书
  820. ssl_config and ssl_config.client_cert or nil, -- 客户端证书
  821. ssl_config and ssl_config.client_key or nil, -- 客户端私钥
  822. ssl_config and ssl_config.client_password or nil -- 客户端私钥口令
  823. )
  824. if not config_success then
  825. socket.release(connection)
  826. connection = nil
  827. return false, "Socket config failed"
  828. end
  829. socket.debug(connection, true)
  830. -- 设置连接超时定时器
  831. connect_timeout_timer = sys.timerStart(function()
  832. if not is_connected then
  833. log.error("TCP connection timeout")
  834. if connection then
  835. socket.close(connection)
  836. socket.release(connection)
  837. connection = nil
  838. end
  839. if callback_func then
  840. callback_func("connect_result", { success = false, error = "Connection timeout" })
  841. end
  842. -- 尝试重连
  843. if config.auto_reconnect and is_open then
  844. is_open = false
  845. schedule_reconnect()
  846. end
  847. end
  848. end, config.timeout * 1000)
  849. -- 连接到服务器
  850. local ok, result = socket.connect(connection, config.host, config.port, config.mqtt_ipv6)
  851. log.info("TCP连接结果", ok, result)
  852. if not ok then
  853. --发生异常,强制close
  854. socket.close(connection)
  855. --释放资源
  856. socket.release(connection)
  857. connection = nil
  858. if config.auto_reconnect then
  859. is_open = false
  860. schedule_reconnect()
  861. end
  862. return false, result
  863. end
  864. elseif config.transport == "mqtt" then
  865. -- 准备MQTT SSL配置
  866. local ssl_config = nil
  867. if config.ssl then
  868. if type(config.ssl) == "table" then
  869. ssl_config = config.ssl
  870. else
  871. ssl_config = true -- 简单SSL启用
  872. end
  873. end
  874. local mqtt_opts = nil
  875. -- 准备MQTT扩展参数
  876. -- local mqtt_opts = {
  877. -- rxSize = config.mqtt_rx_size,
  878. -- conn_timeout = config.mqtt_conn_timeout,
  879. -- ipv6 = config.mqtt_ipv6
  880. -- }
  881. -- 创建MQTT客户端
  882. connection = mqtt.create(nil, config.host, config.port, ssl_config, mqtt_opts)
  883. if not connection then
  884. return false, "Failed to create MQTT client"
  885. end
  886. -- 设置认证信息
  887. connection:auth(config.device_id, config.username, config.password, config.clean_session)
  888. -- 注册事件回调
  889. connection:on(mqtt_client_event_cbfunc)
  890. -- 设置保持连接间隔
  891. connection:keepalive(config.keepalive)
  892. -- 设置连接超时定时器
  893. connect_timeout_timer = sys.timerStart(function()
  894. if not is_connected then
  895. log.error("MQTT connection timeout")
  896. if connection then
  897. connection:disconnect()
  898. end
  899. if callback_func then
  900. callback_func("connect_result", { success = false, error = "Connection timeout" })
  901. end
  902. -- 尝试重连
  903. if config.auto_reconnect and is_open then
  904. is_open = false
  905. schedule_reconnect()
  906. end
  907. end
  908. end, config.timeout * 1000)
  909. -- 连接到服务器
  910. local ok = connection:connect()
  911. if not ok then
  912. --连接失败,释放资源
  913. connection:disconnect()
  914. -- 发起连接失败,尝试重连
  915. if config.auto_reconnect then
  916. is_open = false
  917. schedule_reconnect()
  918. end
  919. return false, "MQTT connect failed"
  920. end
  921. else
  922. return false, "Unsupported transport: " .. config.transport
  923. end
  924. is_open = true
  925. reconnect_count = 0
  926. log.info("excloud service started")
  927. return true
  928. end
  929. -- 发送数据
  930. -- 发送消息到云端
  931. -- @param data table 待发送的数据,每个元素是一个包含 field_meaning、data_type 和 value 的表
  932. -- @param need_reply boolean 是否需要服务器回复,默认为 false
  933. -- @param is_auth_msg boolean 是否是鉴权消息,默认为 false
  934. function excloud.send(data, need_reply, is_auth_msg)
  935. -- 检查参数是否为table
  936. if type(data) ~= "table" then
  937. return false, "data must be table"
  938. end
  939. if need_reply == nil then
  940. return false, "need_reply cannot be nil"
  941. end
  942. if is_auth_msg == nil then
  943. is_auth_msg = false
  944. end
  945. -- 检查服务是否开启
  946. if not is_open then
  947. if callback_func then
  948. callback_func("send_result", {
  949. success = false,
  950. error_msg = "excloud not open"
  951. })
  952. end
  953. return false, "excloud not open"
  954. end
  955. -- 检查是否已连接
  956. if not is_connected then
  957. if callback_func then
  958. callback_func("send_result", {
  959. success = false,
  960. error_msg = "excloud not connected"
  961. })
  962. end
  963. return false, "excloud not connected"
  964. end
  965. -- 保存当前序列号用于回调
  966. local current_sequence = sequence_num
  967. local success
  968. -- 构建消息体
  969. local message_body = ""
  970. for _, item in ipairs(data) do
  971. log.info("发送数据333", item.field_meaning, item.data_type, item.value, message_body)
  972. local success, tlv = build_tlv(item.field_meaning, item.data_type, item.value)
  973. if not success then
  974. return false, "excloud.send data is failed"
  975. end
  976. message_body = message_body .. tlv
  977. end
  978. -- 检查消息长度
  979. local udp_auth_key = config.udp_auth_key and true or false
  980. local total_length = #message_body + (udp_auth_key and 64 or 0)
  981. log.info("tlv发送数据长度4", total_length)
  982. -- 构建消息头
  983. local is_udp_transport = (config.transport == "udp")
  984. local header = build_header(need_reply or false, is_udp_transport, total_length)
  985. -- -- 添加鉴权key(如果是UDP的话)
  986. local auth_key_part = ""
  987. if config.transport == "udp" and udp_auth_key then
  988. auth_key_part = config.udp_auth_key
  989. if #auth_key_part < 64 then
  990. auth_key_part = auth_key_part .. string.rep("\0", 64 - #auth_key_part)
  991. elseif #auth_key_part > 64 then
  992. auth_key_part = auth_key_part:sub(1, 64)
  993. end
  994. end
  995. local full_message
  996. -- 发送完整消息
  997. if config.transport == "udp" then
  998. full_message = header .. auth_key_part .. message_body
  999. else
  1000. full_message = header .. message_body
  1001. end
  1002. log.info("发送消息长度", #header, #message_body, #full_message, full_message:toHex())
  1003. local success, err_msg
  1004. if config.transport == "tcp" then
  1005. if not connection then
  1006. err_msg = "TCP connection not available"
  1007. success = false
  1008. else
  1009. success, err_msg = socket.tx(connection, full_message)
  1010. end
  1011. elseif config.transport == "mqtt" then
  1012. -- 根据是否为鉴权消息选择不同的topic
  1013. local topic
  1014. if is_auth_msg then
  1015. topic = "/AirCloud/up/" .. config.device_id .. "/auth"
  1016. else
  1017. topic = "/AirCloud/up/" .. config.device_id .. "/all"
  1018. end
  1019. log.info("发布主题", topic, #full_message, full_message:toHex())
  1020. success = connection:publish(topic, full_message, config.qos, config.retain)
  1021. end
  1022. -- 通过回调返回发送结果
  1023. if callback_func then
  1024. callback_func("send_result", {
  1025. success = success,
  1026. error_msg = success and "Send successful" or err_msg,
  1027. sequence_num = current_sequence
  1028. })
  1029. end
  1030. if success then
  1031. log.info("数据发送成功", #full_message, "字节")
  1032. return true
  1033. else
  1034. log.error("数据发送失败", err_msg)
  1035. return false, err_msg
  1036. end
  1037. end
  1038. -- 关闭excloud服务
  1039. function excloud.close()
  1040. if not is_open then
  1041. return false, "excloud not open"
  1042. end
  1043. -- 取消重连定时器
  1044. if reconnect_timer then
  1045. sys.timerStop(reconnect_timer)
  1046. reconnect_timer = nil
  1047. end
  1048. -- 关闭连接
  1049. if connection then
  1050. if config.transport == "tcp" then
  1051. socket.close(connection)
  1052. socket.release(connection)
  1053. elseif config.transport == "mqtt" then
  1054. connection:disconnect()
  1055. end
  1056. connection = nil
  1057. end
  1058. -- 重置状态
  1059. is_open = false
  1060. is_connected = false
  1061. is_authenticated = false
  1062. pending_messages = {}
  1063. rxbuff = nil
  1064. log.info("excloud service stopped")
  1065. return true
  1066. end
  1067. -- 获取当前状态
  1068. function excloud.status()
  1069. return {
  1070. is_open = is_open,
  1071. is_connected = is_connected,
  1072. sequence_num = sequence_num,
  1073. reconnect_count = reconnect_count,
  1074. pending_messages = #pending_messages,
  1075. }
  1076. end
  1077. -- 导出常量
  1078. excloud.DATA_TYPES = DATA_TYPES
  1079. excloud.FIELD_MEANINGS = FIELD_MEANINGS
  1080. return excloud