excloud.lua 84 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407
  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. 7. excloud.start_heartbeat(interval, custom_data) - 启动自动心跳机制,定期向云平台发送心跳消息;
  26. 8. excloud.stop_heartbeat() -停止自动心跳机制;
  27. 9. excloud.upload_image(file_path, file_name) -上传图片文件到云平台;注意,需要启用getip服务
  28. 10. excloud.upload_audio(file_path, file_name) -上传音频文件到云平台;注意,需要启用getip服务
  29. 11. excloud.get_server_info() - 获取getip获取的服务器信息
  30. 12. excloud.mtn_log(tag, ...) - 记录运维日志;
  31. ]]
  32. local excloud = {}
  33. local httpplus = require "httpplus"
  34. local exmtn = require "exmtn"
  35. local config = {
  36. device_type = 1, -- 默认设备类型: 4G
  37. device_id = "", -- 设备ID
  38. protocol_version = 1, -- 协议版本
  39. transport = "", -- 传输协议: tcp/mqtt
  40. host = "", -- 服务器地址
  41. port = nil, -- 服务器端口
  42. auth_key = nil, -- 用户鉴权密钥
  43. keepalive = 300, -- mqtt心跳
  44. auto_reconnect = true, -- 是否自动重连
  45. reconnect_interval = 10, -- 重连间隔(秒)
  46. max_reconnect = 3, -- 最大重连次数
  47. timeout = 30, -- 连接超时时间(秒)
  48. qos = 0, -- MQTT QoS等级
  49. retain = 0, -- MQTT retain标志
  50. clean_session = true, -- MQTT clean session标志
  51. ssl = false, -- 是否使用SSL
  52. username = nil, -- MQTT用户名
  53. password = nil, -- MQTT密码
  54. udp_auth_key = nil, -- UDP鉴权密钥
  55. -- 新增socket配置参数
  56. local_port = nil, -- 本地端口号,nil表示自动分配
  57. keep_idle = nil, -- TCP keepalive idle时间(秒)
  58. keep_interval = nil, -- TCP keepalive 探测间隔(秒)
  59. keep_cnt = nil, -- TCP keepalive 探测次数
  60. server_cert = nil, -- 服务器CA证书数据
  61. client_cert = nil, -- 客户端证书数据
  62. client_key = nil, -- 客户端私钥数据
  63. client_password = nil, -- 客户端私钥口令
  64. use_getip = true, -- 是否使用getip服务发现,默认为true
  65. -- MQTT扩展参数
  66. -- mqtt_rx_size = 32 * 1024, -- MQTT接收缓冲区大小,默认32K
  67. -- mqtt_conn_timeout = 30, -- MQTT连接超时时间
  68. -- mqtt_ipv6 = false, -- 是否使用IPv6连接
  69. -- getip相关配置
  70. getip_url = "https://gps.openluat.com/iam/iot/getip", -- 根据协议修正URL
  71. current_conninfo = {}, -- 当前连接信息
  72. current_imginfo = nil, -- 当前图片上传信息
  73. current_audinfo = nil, -- 当前音频上传信息
  74. current_mtninfo = nil, -- 新增:运维日志上传信息
  75. getip_retry_count = 0, -- getip重试次数
  76. max_getip_retry = 3, -- 最大getip重试次数
  77. -- 虚拟设备相关配置
  78. virtual_phone_number = nil, -- 手机号
  79. virtual_serial_num = 0, -- 序列号(0-999)
  80. -- 运维日志配置
  81. mtn_log_enabled = false, -- 是否启用运维日志
  82. aircloud_mtn_log_enabled = false, -- 是否启用aircloud运维日志:true-开启,false-关闭;开启后设备认证/重连等关键事件会自动记录到运维日志文件,便于云端统一收集分析
  83. mtn_log_blocks = 1, -- 每个文件的块数
  84. mtn_log_write_way = exmtn.CACHE_WRITE, -- 写入方式
  85. }
  86. local callback_func = nil -- 回调函数
  87. local is_open = false -- 服务是否开启
  88. local is_connected = false -- 是否已连接
  89. local is_authenticated = false -- 是否已鉴权
  90. local sequence_num = 1 -- 流水号
  91. local connection = nil -- 连接对象
  92. local device_id_binary = nil -- 二进制格式的设备ID
  93. local reconnect_timer = nil -- 重连定时器
  94. local reconnect_count = 0 -- 重连次数
  95. local pending_messages = {} -- 待发送消息队列
  96. local rxbuff = nil -- 接收缓冲区
  97. local connect_timeout_timer = nil -- 连接超时定时器
  98. local heartbeat_timer = nil -- 心跳定时器
  99. local heartbeat_interval = 300 -- 心跳间隔(秒),默认5分钟
  100. local heartbeat_data = {} -- 心跳数据,默认空表
  101. local is_heartbeat_running = false -- 心跳是否正在运行
  102. -- 数据类型定义
  103. local DATA_TYPES = {
  104. INTEGER = 0x0, -- 整数
  105. FLOAT = 0x1, -- 浮点数
  106. BOOLEAN = 0x2, -- 布尔值
  107. ASCII = 0x3, -- ASCII字符串
  108. BINARY = 0x4, -- 二进制数据
  109. UNICODE = 0x5 -- Unicode字符串
  110. }
  111. -- 字段含义定义
  112. local FIELD_MEANINGS = {
  113. -- 控制信令类型 (16-255)
  114. AUTH_REQUEST = 16, -- 鉴权请求
  115. AUTH_RESPONSE = 17, -- 鉴权回复
  116. REPORT_RESPONSE = 18, -- 上报回应
  117. CONTROL_COMMAND = 19, -- 控制命令
  118. CONTROL_RESPONSE = 20, -- 控制回应
  119. IRTU_DOWN = 21, -- iRTU下行命令
  120. IRTU_UP = 22, -- iRTU上行回复
  121. -- 文件上传控制信令 (23-24)
  122. FILE_UPLOAD_START = 23, -- 文件上传开始通知
  123. FILE_UPLOAD_FINISH = 24, -- 文件上传完成通知
  124. -- 运维日志控制信令 (25-27)
  125. MTN_LOG_UPLOAD_REQ_SIGNAL = 25, -- 运维日志上传请求 - 下行(信令类型)
  126. MTN_LOG_UPLOAD_RESP_SIGNAL = 26, -- 运维日志上传响应 - 上行(信令类型)
  127. MTN_LOG_UPLOAD_STATUS_SIGNAL = 27, -- 运维日志上传状态 - 上行(信令类型)
  128. -- 传感类 (256-511)
  129. TEMPERATURE = 256, -- 温度
  130. HUMIDITY = 257, -- 湿度
  131. PARTICULATE = 258, -- 颗粒数
  132. ACIDITY = 259, -- 酸度
  133. ALKALINITY = 260, -- 碱度
  134. ALTITUDE = 261, -- 海拔
  135. WATER_LEVEL = 262, -- 水位
  136. ENV_TEMPERATURE = 263, -- CPU温度/环境温度
  137. POWER_METERING = 264, -- 电量计量
  138. -- 资产管理类 (512-767)
  139. GNSS_LONGITUDE = 512, -- GNSS经度
  140. GNSS_LATITUDE = 513, -- GNSS纬度
  141. SPEED = 514, -- 行驶速度
  142. GNSS_CN = 515, -- 最强的4颗GNSS卫星的CN
  143. SATELLITES_TOTAL = 516, -- 搜到的所有卫星数
  144. SATELLITES_VISIBLE = 517, -- 可见卫星数
  145. HEADING = 518, -- 航向角
  146. LOCATION_METHOD = 519, -- 基站定位/GNSS定位标识
  147. GNSS_INFO = 520, -- GNSS芯片型号和固件版本号
  148. DIRECTION = 521, -- 方向
  149. -- 设备参数类 (768-1023)
  150. HEIGHT = 768, -- 高度
  151. WIDTH = 769, -- 宽度
  152. ROTATION_SPEED = 770, -- 转速
  153. BATTERY_LEVEL = 771, -- 电量(mV)
  154. SERVING_CELL = 772, -- 驻留频段
  155. CELL_INFO = 773, -- 驻留小区和邻区
  156. COMPONENT_MODEL = 774, -- 元器件型号
  157. GPIO_LEVEL = 775, -- GPIO高低电平
  158. BOOT_REASON = 776, -- 开机原因
  159. BOOT_COUNT = 777, -- 开机次数
  160. SLEEP_MODE = 778, -- 休眠模式
  161. WAKE_INTERVAL = 779, -- 定时唤醒间隔
  162. NETWORK_IP_TYPE = 780, -- 设备入网的IP类型
  163. NETWORK_TYPE = 781, -- 当前联网方式
  164. SIGNAL_STRENGTH_4G = 782, --4G信号强度
  165. SIM_ICCID = 783, -- SIM卡ICCID
  166. -- 文件上传业务字段 (784-787)
  167. FILE_UPLOAD_TYPE = 784, -- 文件上传类型(1:图片, 2:音频)
  168. FILE_NAME = 785, -- 文件名称
  169. FILE_SIZE = 786, -- 文件大小
  170. UPLOAD_RESULT_STATUS = 787, -- 上传结果状态
  171. -- 运维日志业务字段 (788-792)
  172. MTN_LOG_FILE_INDEX = 788, -- 运维日志文件序号
  173. MTN_LOG_FILE_TOTAL = 789, -- 运维日志文件总数
  174. MTN_LOG_FILE_SIZE = 790, -- 运维日志文件大小
  175. MTN_LOG_UPLOAD_STATUS_FIELD = 791, -- 运维日志上传状态
  176. MTN_LOG_FILE_NAME = 792, -- 运维日志文件名称
  177. -- 工牌设备参数字段 (793-797) - 新增
  178. BADGE_TOTAL_DISK = 793, -- 工牌总磁盘空间
  179. BADGE_AVAILABLE_DISK = 794, -- 工牌剩余磁盘空间
  180. BADGE_TOTAL_MEM = 795, -- 工牌总内存
  181. BADGE_AVAILABLE_MEM = 796, -- 工牌剩余内存
  182. BADGE_RECORD_COUNT = 797, -- 工牌录音数量
  183. -- 软件数据类 (1024-1279)
  184. LUA_CORE_ERROR = 1024, -- Lua核心库错误上报
  185. LUA_EXT_ERROR = 1025, -- Lua扩展卡错误上报
  186. LUA_APP_ERROR = 1026, -- Lua业务错误上报
  187. FIRMWARE_VERSION = 1027, -- 固件版本号
  188. SMS_FORWARD = 1028, -- SMS转发
  189. CALL_FORWARD = 1029, -- 来电转发
  190. -- 设备无关数据类 (1280-1535)
  191. TIMESTAMP = 1280, -- 时间
  192. RANDOM_DATA = 1281 -- 无意义数据
  193. }
  194. -- 运维日志上传状态
  195. local MTN_LOG_STATUS = {
  196. START = 0, -- 开始上传
  197. SUCCESS = 1, -- 上传成功
  198. FAILED = 2 -- 上传失败
  199. }
  200. -- 将数字转换为大端字节序列
  201. local function to_big_endian(num, bytes)
  202. local result = {}
  203. for i = bytes, 1, -1 do
  204. result[i] = string.char(num % 256)
  205. num = math.floor(num / 256)
  206. end
  207. return table.concat(result)
  208. end
  209. -- 从大端字节序列转换为数字
  210. local function from_big_endian(data, start, length)
  211. local value = 0
  212. for i = start, start + length - 1 do
  213. value = value * 256 + data:byte(i)
  214. end
  215. -- log.info("[excloud]from_big_endian", value)
  216. return value
  217. end
  218. -- 将设备ID进行编码
  219. local function packDeviceInfo(deviceType, deviceId)
  220. -- 验证设备类型
  221. if deviceType ~= 1 and deviceType ~= 2 and deviceType ~= 9 then
  222. log.info("[excloud]设备类型错误: 4G设备应为1, WIFI设备应为2")
  223. end
  224. -- 设备类型字节
  225. local result = { string.char(deviceType) }
  226. -- 清理设备ID(移除非数字和字母字符,并转换为大写)
  227. local cleanId = deviceId:gsub("[^%w]", ""):upper()
  228. -- 处理不同类型的设备ID
  229. if deviceType == 1 then
  230. -- 4G设备 - IMEI处理
  231. -- 只取前14位数字,忽略第15位
  232. cleanId = cleanId:gsub("%D", ""):sub(1, 14)
  233. -- 确保长度为14位(不足时前面补0)
  234. if #cleanId < 14 then
  235. cleanId = string.rep("0", 14 - #cleanId) .. cleanId
  236. end
  237. -- 转换为BCD格式的字节
  238. for i = 1, 14, 2 do
  239. local byte = (tonumber(cleanId:sub(i, i)) * 16) + tonumber(cleanId:sub(i + 1, i + 1))
  240. table.insert(result, string.char(byte))
  241. end
  242. elseif deviceType == 2 then
  243. -- WIFI设备 - MAC地址处理
  244. -- 移除非十六进制字符
  245. cleanId = cleanId:gsub("[^0-9A-Fa-f]", "")
  246. -- 确保长度为12个十六进制字符(6字节)
  247. if #cleanId < 12 then
  248. cleanId = string.rep("0", 12 - #cleanId) .. cleanId
  249. else
  250. cleanId = cleanId:sub(1, 12)
  251. end
  252. -- 转换为字节
  253. local bytes = {}
  254. for i = 1, 12, 2 do
  255. local byteStr = cleanId:sub(i, i + 1)
  256. table.insert(bytes, string.char(tonumber(byteStr, 16)))
  257. end
  258. -- 确保有7个字节(不足时前面补0)
  259. while #bytes < 7 do
  260. table.insert(bytes, 1, string.char(0))
  261. end
  262. -- 添加到结果中
  263. for _, byte in ipairs(bytes) do
  264. table.insert(result, byte)
  265. end
  266. elseif deviceType == 9 then
  267. -- 虚拟设备处理:11位手机号 + 3位序列号
  268. cleanId = cleanId:gsub("%D", ""):sub(1, 14)
  269. if #cleanId < 14 then
  270. cleanId = string.rep("0", 14 - #cleanId) .. cleanId
  271. end
  272. -- 转换为BCD格式的字节(每2位数字转换为1个字节)
  273. for i = 1, 14, 2 do
  274. local byte = (tonumber(cleanId:sub(i, i)) * 16) + tonumber(cleanId:sub(i + 1, i + 1))
  275. table.insert(result, string.char(byte))
  276. end
  277. else
  278. log.info("[excloud]未知设备类型 ")
  279. return deviceId
  280. end
  281. -- 返回8字节的二进制数据
  282. return table.concat(result)
  283. end
  284. -- 编码数据值
  285. local function encode_value(data_type, value)
  286. -- 添加参数类型检查
  287. if data_type == nil or value == nil then
  288. log.info("[excloud]Data type or value is nil")
  289. return ""
  290. end
  291. if data_type == DATA_TYPES.INTEGER then
  292. -- 验证value是否为数字
  293. if type(value) ~= "number" then
  294. log.info("[excloud]Integer value must be a number")
  295. return ""
  296. end
  297. return to_big_endian(math.floor(value), 4)
  298. elseif data_type == DATA_TYPES.FLOAT then
  299. -- 验证value是否为数字
  300. if type(value) ~= "number" then
  301. log.info("[excloud]Float value must be a number")
  302. return ""
  303. end
  304. -- 简化处理:将浮点数转换为整数,乘以1000以保留三位小数
  305. return to_big_endian(math.floor(value * 1000), 4)
  306. elseif data_type == DATA_TYPES.BOOLEAN then
  307. return value and "\1" or "\0"
  308. elseif data_type == DATA_TYPES.ASCII or data_type == DATA_TYPES.BINARY or data_type == DATA_TYPES.UNICODE then
  309. -- 确保value是字符串类型
  310. return tostring(value)
  311. else
  312. log.info("[excloud]Unsupported data type: " .. tostring(data_type))
  313. -- 返回空字符串而不是nil,避免后续处理出错
  314. return ""
  315. end
  316. end
  317. -- 解码数据值
  318. local function decode_value(data_type, value)
  319. if data_type == DATA_TYPES.INTEGER then
  320. return from_big_endian(value, 1, #value)
  321. elseif data_type == DATA_TYPES.FLOAT then
  322. -- 简化处理:将整数转换为浮点数(实际应使用IEEE 754格式)
  323. return from_big_endian(value, 1, #value) / 1000
  324. elseif data_type == DATA_TYPES.BOOLEAN then
  325. return value:byte(1) ~= 0
  326. elseif data_type == DATA_TYPES.ASCII then
  327. return value
  328. elseif data_type == DATA_TYPES.BINARY then
  329. return value
  330. elseif data_type == DATA_TYPES.UNICODE then
  331. return value
  332. else
  333. log.info("[excloud]Unsupported data type: " .. data_type)
  334. return nil
  335. end
  336. end
  337. -- 构建消息头
  338. -- @param need_reply boolean 是否需要服务器回复
  339. -- @param has_auth_key boolean 是否携带鉴权key
  340. -- @param data_length number 数据长度
  341. local function build_header(need_reply, is_udp_transport, data_length)
  342. sequence_num = sequence_num + 1
  343. if sequence_num > 65535 then
  344. sequence_num = 1
  345. end
  346. -- 消息标识字段
  347. local flags = config.protocol_version -- bit0-3: 协议版本号
  348. if need_reply then
  349. flags = flags + 16 -- bit4: 是否需要回复
  350. end
  351. if is_udp_transport then
  352. flags = flags + 32 -- bit5: 是否是UDP承载
  353. end
  354. log.info("[excloud]构建消息头", device_id_binary, to_big_endian(sequence_num, 2), to_big_endian(data_length, 2),
  355. to_big_endian(flags, 4))
  356. return device_id_binary ..
  357. to_big_endian(sequence_num, 2) ..
  358. to_big_endian(data_length, 2) ..
  359. to_big_endian(flags, 4)
  360. end
  361. -- 构建TLV字段
  362. local function build_tlv(field_meaning, data_type, value)
  363. if field_meaning == nil or data_type == nil or value == nil then
  364. log.info("[excloud]构建tlv参数不能为空")
  365. return false
  366. end
  367. local value_encoded = encode_value(data_type, value)
  368. if value_encoded == nil then
  369. log.info("[excloud]构建tlv打包数据时长度为0")
  370. -- 添加空字符串作为默认值,避免后续获取长度时出错
  371. value_encoded = ""
  372. end
  373. local length = #value_encoded
  374. -- 字段类型(字段含义 + 数据类型)
  375. local head = (field_meaning & 0x0FFF) | (data_type << 12) -- 2 字节头
  376. return true, to_big_endian(head, 2) ..
  377. to_big_endian(length, 2) ..
  378. value_encoded
  379. end
  380. -- 解析消息头
  381. local function parse_header(header)
  382. if #header < 16 then
  383. log.info("[excloud]消息头解析失败", "Header too short")
  384. return nil, "Header too short"
  385. end
  386. local device_id = header:sub(1, 8)
  387. local seq_num = from_big_endian(header, 9, 2)
  388. local msg_length = from_big_endian(header, 11, 2)
  389. local flags = from_big_endian(header, 13, 4)
  390. -- 提取标志位
  391. local protocol_version = flags % 16
  392. local need_reply = (flags % 32) >= 16
  393. local is_udp_transport = (flags % 64) >= 32
  394. -- 打印解析结果,方便调试
  395. -- log.info("[excloud]消息头解析结果",
  396. -- string.format(
  397. -- "device_id: %s, sequence_num: %d, msg_length: %d, protocol_version: %d, need_reply: %s, is_udp_transport: %s",
  398. -- string.toHex(device_id), seq_num, msg_length, protocol_version,
  399. -- tostring(need_reply), tostring(is_udp_transport)))
  400. return {
  401. device_id = string.toHex(device_id),
  402. sequence_num = seq_num,
  403. msg_length = msg_length,
  404. protocol_version = protocol_version,
  405. need_reply = need_reply,
  406. is_udp_transport = is_udp_transport
  407. }
  408. end
  409. -- 工具函数:解析TLV
  410. local function parse_tlv(data, startPos)
  411. -- 检查数据是否足够解析TLV的T的长度。
  412. if #data < startPos + 3 then
  413. return nil, startPos, "TLV data too short"
  414. end
  415. local fieldType = from_big_endian(data, startPos, 2)
  416. local length = from_big_endian(data, startPos + 2, 2)
  417. -- 提取原始字节值
  418. local value = data:sub(startPos + 4, startPos + 4 + length - 1)
  419. --解析TLV字段中的T
  420. -- bit0-11: 字段含义
  421. -- bit12-15: 数据类型
  422. local field_meaning = fieldType & 0x0FFF -- 取低12位作为字段含义
  423. local data_type = fieldType >> 12 -- 取高4位作为数据类型
  424. local decoded_value = decode_value(data_type, value)
  425. -- log.info("[excloud]消息体解析结果", field_meaning, data_type, decoded_value)
  426. return {
  427. field = field_meaning,
  428. type = data_type,
  429. value = decoded_value,
  430. length = length, --数据长度
  431. }, startPos + 4 + length
  432. end
  433. -- 解析完整消息
  434. local function parse_message(data)
  435. local header, err = parse_header(data:sub(1, 16))
  436. if not header then
  437. return nil, err
  438. end
  439. local auth_key = nil
  440. local body_start = 17
  441. -- 如果是UDP传输,解析认证key
  442. if header.is_udp_transport then
  443. if #data >= body_start + 64 - 1 then
  444. auth_key = data:sub(body_start, body_start + 64 - 1)
  445. body_start = body_start + 64
  446. else
  447. return nil, "Incomplete UDP authentication key"
  448. end
  449. end
  450. -- 解析TLV字段
  451. local tlvs = {}
  452. local pos = body_start
  453. local end_pos = 16 + (header.msg_length)
  454. if #data < end_pos then
  455. return nil, "Message incomplete"
  456. end
  457. while pos < end_pos do
  458. local tlv, new_pos, err = parse_tlv(data, pos)
  459. if not tlv then
  460. return nil, "Failed to parse TLV at position " .. err
  461. end
  462. table.insert(tlvs, tlv)
  463. -- 更新解析位置为解析完当前TLV字段后的新位置,以便继续解析后续的TLV字段
  464. pos = new_pos
  465. end
  466. return {
  467. header = header,
  468. auth_key = auth_key,
  469. tlvs = tlvs
  470. }
  471. end
  472. -- 发送鉴权请求
  473. local function send_auth_request()
  474. if not config.auth_key then
  475. return false, "No auth key configured"
  476. end
  477. local auth_data
  478. --设备实测时打开
  479. if config.device_type == 1 then
  480. auth_data = config.auth_key .. "-" .. mobile.imei() .. "-" .. mobile.muid()
  481. elseif config.device_type == 2 then
  482. auth_data = config.auth_key .. "-" .. wlan.getMac(nil, true) .. "-" .. mcu.unique_id():toHex()
  483. elseif config.device_type == 9 then --虚拟设备
  484. auth_data = config.auth_key .. "-" .. config.device_id
  485. else
  486. auth_data = config.auth_key .. "-"
  487. end
  488. local message = {
  489. {
  490. field_meaning = FIELD_MEANINGS.AUTH_REQUEST,
  491. data_type = DATA_TYPES.ASCII,
  492. value = auth_data
  493. }
  494. }
  495. -- log.info("[excloud]send auth request", message,message[1].value,message[1].data_type,message[1].field_meaning)
  496. return excloud.send(message, true, true) -- 鉴权消息需要设置 is_auth_msg 为 true
  497. end
  498. -- 处理认证响应
  499. -- local function handle_auth_response(tlvs)
  500. -- for _, tlv in ipairs(tlvs) do
  501. -- if tlv.field_meaning == FIELD_MEANINGS.AUTH_RESPONSE then
  502. -- local success = (tlv.value == "OK" or tlv.value == "SUCCESS")
  503. -- is_authenticated = true
  504. -- -- 记录认证结果到运维日志
  505. -- if config.aircloud_mtn_log_enabled then
  506. -- exmtn.log("info", "aircloud","auth", "认证结果", "success", success, "message", tlv.value)
  507. -- end
  508. -- if callback_func then
  509. -- callback_func("auth_result", {
  510. -- success = success,
  511. -- message = tlv.value
  512. -- })
  513. -- end
  514. -- -- 认证成功,发送待处理消息
  515. -- if success then
  516. -- for _, msg in ipairs(pending_messages) do
  517. -- excloud.send(msg.data, msg.need_reply)
  518. -- end
  519. -- pending_messages = {}
  520. -- end
  521. -- return success
  522. -- end
  523. -- end
  524. -- return false
  525. -- end
  526. -- 初始化运维日志模块
  527. local function init_mtn_log()
  528. if not config.mtn_log_enabled then
  529. log.info("[excloud]aircloud运维日志功能已禁用")
  530. return true
  531. end
  532. local ok, err = exmtn.init(config.mtn_log_blocks, config.mtn_log_write_way)
  533. if not ok then
  534. log.error("[excloud]运维日志初始化失败:", err)
  535. return false, err
  536. end
  537. log.info("[excloud]运维日志初始化成功")
  538. return true
  539. end
  540. -- 扫描运维日志文件
  541. local function scan_mtn_log_files()
  542. local log_files = {}
  543. -- 使用exmtn管理的文件信息
  544. for i = 1, 4 do -- exmtn管理4个日志文件
  545. local file_path = string.format("/hzmtn%d.trc", i)
  546. if io.exists(file_path) then
  547. local file_size = io.fileSize(file_path) or 0
  548. if file_size > 0 then
  549. table.insert(log_files, {
  550. name = string.format("hzmtn%d.trc", i),
  551. path = file_path,
  552. size = file_size,
  553. index = i
  554. })
  555. log.info("发现运维日志文件", "路径:", file_path, "大小:", file_size, "序号:", i)
  556. else
  557. log.info("运维日志文件为空", "路径:", file_path)
  558. end
  559. else
  560. log.info("运维日志文件不存在", "路径:", file_path)
  561. end
  562. end
  563. -- 按文件序号排序
  564. table.sort(log_files, function(a, b)
  565. return a.index < b.index
  566. end)
  567. log.info("扫描运维日志文件完成", "有效文件数量:", #log_files)
  568. return log_files
  569. end
  570. -- 构建运维日志响应TLV数据
  571. local function build_mtn_log_response_tlv(total_files, latest_index)
  572. local sub_tlvs = ""
  573. -- 文件总数
  574. local success, tlv_data = build_tlv(FIELD_MEANINGS.MTN_LOG_FILE_TOTAL, DATA_TYPES.INTEGER, total_files)
  575. if success then
  576. sub_tlvs = sub_tlvs .. tlv_data
  577. else
  578. log.error("构建文件总数TLV失败")
  579. return ""
  580. end
  581. -- 当前最新文件序号
  582. success, tlv_data = build_tlv(FIELD_MEANINGS.MTN_LOG_FILE_INDEX, DATA_TYPES.INTEGER, latest_index)
  583. if success then
  584. sub_tlvs = sub_tlvs .. tlv_data
  585. else
  586. log.error("构建最新文件序号TLV失败")
  587. return ""
  588. end
  589. log.info("构建运维日志响应TLV", "文件总数:", total_files, "最新序号:", latest_index)
  590. return sub_tlvs
  591. end
  592. -- 发送运维日志上传状态
  593. local function send_mtn_log_status(status, file_index, file_name, file_size)
  594. local sub_tlvs = ""
  595. -- 上传状态(必须)
  596. local success, tlv_data = build_tlv(FIELD_MEANINGS.MTN_LOG_UPLOAD_STATUS_FIELD, DATA_TYPES.INTEGER, status)
  597. if success then
  598. sub_tlvs = sub_tlvs .. tlv_data
  599. else
  600. log.error("构建运维日志上传状态TLV失败")
  601. return false, "构建状态TLV失败"
  602. end
  603. -- 文件序号
  604. success, tlv_data = build_tlv(FIELD_MEANINGS.MTN_LOG_FILE_INDEX, DATA_TYPES.INTEGER, file_index)
  605. if success then
  606. sub_tlvs = sub_tlvs .. tlv_data
  607. else
  608. log.error("构建文件序号TLV失败")
  609. return false, "构建文件序号TLV失败"
  610. end
  611. -- 根据状态添加不同的字段
  612. if status == MTN_LOG_STATUS.START then
  613. -- 开始上传:包含文件名
  614. if file_name then
  615. success, tlv_data = build_tlv(FIELD_MEANINGS.MTN_LOG_FILE_NAME, DATA_TYPES.ASCII, file_name)
  616. if success then
  617. sub_tlvs = sub_tlvs .. tlv_data
  618. else
  619. log.warn("构建文件名TLV失败,但继续发送状态")
  620. end
  621. else
  622. log.warn("开始上传状态缺少文件名")
  623. end
  624. elseif status == MTN_LOG_STATUS.SUCCESS then
  625. -- 上传成功:包含文件大小
  626. if file_size then
  627. success, tlv_data = build_tlv(FIELD_MEANINGS.MTN_LOG_FILE_SIZE, DATA_TYPES.INTEGER, file_size)
  628. if success then
  629. sub_tlvs = sub_tlvs .. tlv_data
  630. else
  631. log.warn("构建文件大小TLV失败,但继续发送状态")
  632. end
  633. else
  634. log.warn("上传成功状态缺少文件大小")
  635. end
  636. end
  637. -- 上传失败:只需要状态和序号
  638. -- 发送状态消息
  639. local ok, err_msg = excloud.send({
  640. {
  641. field_meaning = FIELD_MEANINGS.MTN_LOG_UPLOAD_STATUS_SIGNAL,
  642. data_type = DATA_TYPES.BINARY,
  643. value = sub_tlvs
  644. }
  645. }, false)
  646. if not ok then
  647. log.error("发送运维日志上传状态失败: " .. (err_msg or "未知错误"))
  648. return false, err_msg
  649. end
  650. log.info("运维日志上传状态发送成功",
  651. "状态:", status,
  652. "文件序号:", file_index,
  653. "文件名:", file_name or "N/A",
  654. "文件大小:", file_size or "N/A")
  655. return true
  656. end
  657. -- 上传运维日志文件
  658. local function upload_mtn_log_files(log_files)
  659. -- 使用协程执行上传,避免阻塞主线程
  660. sys.taskInit(function()
  661. local total_files = #log_files
  662. local success_count = 0
  663. local failed_count = 0
  664. -- -- 通知开始上传
  665. -- if callback_func then
  666. -- callback_func("mtn_log_upload_start", {
  667. -- file_count = total_files
  668. -- })
  669. -- end
  670. for i, log_file in ipairs(log_files) do
  671. -- 发送开始上传状态
  672. send_mtn_log_status(MTN_LOG_STATUS.START, log_file.index, log_file.name, nil)
  673. -- 通知上传进度
  674. -- if callback_func then
  675. -- callback_func("mtn_log_upload_progress", {
  676. -- current_file = i,
  677. -- total_files = total_files,
  678. -- file_name = log_file.name,
  679. -- file_size = log_file.size,
  680. -- status = "start"
  681. -- })
  682. -- end
  683. log.info("[excloud]开始上传运维日志文件", "文件:", log_file.name, "大小:", log_file.size)
  684. -- 上传文件
  685. local success, err_msg = excloud.upload_mtnlog(log_file.path, log_file.name)
  686. if success then
  687. -- 发送上传成功状态
  688. send_mtn_log_status(MTN_LOG_STATUS.SUCCESS, log_file.index, nil, log_file.size)
  689. success_count = success_count + 1
  690. log.info("运维日志文件上传成功", "文件:", log_file.name, "大小:", log_file.size)
  691. -- 记录上传成功的运维日志
  692. if config.aircloud_mtn_log_enabled then
  693. exmtn.log("info", "aircloud", "mtn_upload", "文件上传成功", "file", log_file.name, "size", log_file.size)
  694. end
  695. else
  696. -- 发送上传失败状态
  697. send_mtn_log_status(MTN_LOG_STATUS.FAILED, log_file.index, nil, nil)
  698. failed_count = failed_count + 1
  699. log.error("运维日志文件上传失败", "文件:", log_file.name, "错误:", err_msg)
  700. -- 记录上传失败的运维日志
  701. if config.aircloud_mtn_log_enabled then
  702. exmtn.log("info", "aircloud", "mtn_upload_error", "文件上传失败", "file", log_file.name, "error", err_msg)
  703. end
  704. end
  705. -- 通知上传进度
  706. if callback_func then
  707. callback_func("mtn_log_upload_progress", {
  708. current_file = i,
  709. total_files = total_files,
  710. file_name = log_file.name,
  711. file_size = log_file.size,
  712. status = success and "success" or "failed",
  713. error_msg = err_msg
  714. })
  715. end
  716. -- 文件间延迟,避免同时上传多个文件
  717. if i < total_files then
  718. sys.wait(2000)
  719. end
  720. end
  721. log.info("运维日志上传完成", "成功:", success_count, "失败:", failed_count, "总计:", total_files)
  722. -- 记录上传完成日志
  723. if config.aircloud_mtn_log_enabled then
  724. exmtn.log("info", "aircloud", "mtn_upload", "运维日志上传完成", "success", success_count, "failed", failed_count,
  725. "total", total_files)
  726. end
  727. -- 通知上传完成
  728. if callback_func then
  729. callback_func("mtn_log_upload_complete", {
  730. success_count = success_count,
  731. failed_count = failed_count,
  732. total_files = total_files
  733. })
  734. end
  735. end)
  736. end
  737. -- 处理运维日志上传请求
  738. local function handle_mtn_log_upload_request()
  739. local log_files = scan_mtn_log_files()
  740. local total_files = #log_files
  741. local latest_index = total_files > 0 and log_files[#log_files].index or 0
  742. if config.aircloud_mtn_log_enabled then
  743. exmtn.log("info", "aircloud", "cloud_cmd", "收到运维日志上传请求", "file_count", total_files)
  744. end
  745. log.info("开始处理运维日志上传请求", "文件总数:", total_files, "最新序号:", latest_index)
  746. -- 发送运维日志上传响应(信令26)- 在开始上传前发送
  747. local response_ok, err_msg = excloud.send({
  748. {
  749. field_meaning = FIELD_MEANINGS.MTN_LOG_UPLOAD_RESP_SIGNAL, -- 使用信令类型
  750. data_type = DATA_TYPES.BINARY,
  751. value = build_mtn_log_response_tlv(total_files, latest_index)
  752. }
  753. }, false)
  754. if not response_ok then
  755. log.error("发送运维日志上传响应失败: " .. err_msg)
  756. return
  757. end
  758. log.info("运维日志上传响应已发送", "文件总数:", total_files, "最新序号:", latest_index)
  759. -- 开始上传日志文件
  760. if total_files > 0 then
  761. sys.timerStart(function()
  762. upload_mtn_log_files(log_files)
  763. end, 100)
  764. else
  765. log.info("没有运维日志文件需要上传")
  766. if callback_func then
  767. callback_func("mtn_log_upload_complete", {
  768. success_count = 0,
  769. failed_count = 0,
  770. total_files = 0
  771. })
  772. end
  773. end
  774. end
  775. -- 接收消息解析处理
  776. local function parse_data(data)
  777. local message, err = parse_message(data)
  778. if not message then
  779. log.info("[excloud]Failed to parse message: " .. err)
  780. return
  781. end
  782. -- 处理运维日志上传请求(信令25)
  783. for _, tlv in ipairs(message.tlvs) do
  784. if tlv.field == FIELD_MEANINGS.MTN_LOG_UPLOAD_REQ_SIGNAL then
  785. log.info("[excloud]收到运维日志上传请求")
  786. handle_mtn_log_upload_request()
  787. return
  788. end
  789. end
  790. -- 处理认证响应
  791. -- if not is_authenticated then
  792. -- for _, tlv in ipairs(message.tlvs) do
  793. -- if tlv.field == FIELD_MEANINGS.AUTH_RESPONSE then
  794. -- handle_auth_response(message.tlvs)
  795. -- return
  796. -- end
  797. -- end
  798. --end
  799. --数据返回给回调
  800. if callback_func then
  801. callback_func("message", message)
  802. end
  803. -- -- 如果需要回复,发送确认AAA
  804. -- if message.header.need_reply then
  805. -- local response = {
  806. -- {
  807. -- field_meaning = FIELD_MEANINGS.REPORT_RESPONSE,
  808. -- data_type = DATA_TYPES.ASCII,
  809. -- value = "ACK"
  810. -- }
  811. -- }
  812. -- excloud.send(response, false)
  813. -- end
  814. end
  815. function excloud.getip(getip_type)
  816. getip_type = getip_type or 3 -- 默认使用AirCloud TCP协议
  817. -- 添加参数验证
  818. if not config.auth_key or not config.device_id then
  819. return false, "缺少必要的认证参数: auth_key 或 device_id"
  820. end
  821. local key = config.auth_key .. "-" .. config.device_id
  822. log.info("[excloud]excloud.getip", "类型:", getip_type, "key:", key)
  823. -- 执行HTTP请求
  824. local code, response = httpplus.request(
  825. {
  826. method = "POST",
  827. url = config.getip_url,
  828. forms = { key = key, type = getip_type }
  829. })
  830. log.info("[excloud]excloud.getip响应", "HTTP Code:", code, "Body:", response.body:query())
  831. -- 添加对HTTP响应为空值的处理
  832. if not response or not response.body then
  833. log.error("[excloud]getip请求失败", "HTTP响应为空")
  834. return false, "HTTP响应为空"
  835. end
  836. local response_body = response.body:query()
  837. if not response_body or response_body == "" then
  838. log.error("[excloud]getip请求失败", "响应体为空")
  839. return false, "响应体为空"
  840. end
  841. log.info("[excloud]excloud.getip响应", "HTTP Code:", code, "Body:", response_body)
  842. -- 处理HTTP错误码
  843. if code ~= 200 then
  844. log.info("[excloud]getip请求失败", "HTTP Code:", code)
  845. return false, "HTTP请求失败: " .. tostring(code)
  846. end
  847. -- 解析JSON响应,添加对解析失败的处理
  848. local response_json = json.decode(response_body)
  849. if not response_json then
  850. return false, "JSON解析失败: " .. tostring(err)
  851. end
  852. -- 检查服务器返回状态
  853. if not response_json.msg then
  854. log.error("[excloud]getip响应格式错误", "缺少msg字段")
  855. return false, "服务器响应格式错误: 缺少msg字段"
  856. end
  857. if response_json.msg ~= "ok" then
  858. log.error("[excloud]服务器返回错误", "消息:", response_json.msg)
  859. return false, "服务器返回错误: " .. tostring(response_json.msg)
  860. end
  861. if getip_type >= 3 and getip_type <= 5 then
  862. -- AirCloud业务
  863. if response_json.conninfo then
  864. config.current_conninfo = response_json.conninfo
  865. -- 根据连接类型处理不同的连接信息
  866. if getip_type == 5 then -- MQTT连接
  867. log.info("[excloud]获取到MQTT连接信息",
  868. "host:", response_json.conninfo.ssl,
  869. "port:", response_json.conninfo.port,
  870. "username:", response_json.conninfo.username,
  871. "password:", response_json.conninfo.password)
  872. log.info("[excloud]实际MQTT连接将使用设备信息:",
  873. "client_id:", mobile.imei(),
  874. "username:", mobile.imei(),
  875. "password:", mobile.muid())
  876. else -- TCP/UDP连接
  877. log.info("[excloud]获取到TCP/UDP连接信息",
  878. "host:", response_json.conninfo.ipv4,
  879. "port:", response_json.conninfo.port)
  880. end
  881. else
  882. log.warn("[excloud]未获取到连接信息")
  883. end
  884. if response_json.imginfo then
  885. config.current_imginfo = response_json.imginfo
  886. log.info("[excloud]获取到图片上传信息")
  887. else
  888. log.warn("[excloud]未获取到图片上传信息")
  889. end
  890. if response_json.audinfo then
  891. config.current_audinfo = response_json.audinfo
  892. log.info("[excloud]获取到音频上传信息")
  893. else
  894. log.warn("[excloud]未获取到音频上传信息")
  895. end
  896. -- 新增:运维日志上传信息 (mtninfo)
  897. if response_json.mtninfo then
  898. config.current_mtninfo = response_json.mtninfo
  899. log.info("[excloud]获取到运维日志上传信息")
  900. else
  901. log.warn("[excloud]未获取到运维日志上传信息")
  902. end
  903. end
  904. -- 如果获取到连接信息,自动更新配置
  905. if config.current_conninfo then
  906. -- 根据连接类型设置不同的主机字段
  907. if getip_type == 5 then -- MQTT连接
  908. if config.current_conninfo.ssl then
  909. config.host = config.current_conninfo.ssl
  910. else
  911. log.warn("[excloud]MQTT连接信息中缺少SSL域名")
  912. end
  913. else -- TCP/UDP连接
  914. if config.current_conninfo.ipv4 then
  915. config.host = config.current_conninfo.ipv4
  916. else
  917. log.warn("[excloud]TCP/UDP连接信息中缺少IP地址")
  918. end
  919. end
  920. if config.current_conninfo.port then
  921. config.port = config.current_conninfo.port
  922. else
  923. log.warn("[excloud]连接信息中缺少端口号")
  924. end
  925. -- 更新MQTT认证信息
  926. if getip_type == 5 then
  927. if config.current_conninfo.username then
  928. config.username = config.current_conninfo.username
  929. end
  930. if config.current_conninfo.password then
  931. config.password = config.current_conninfo.password
  932. end
  933. end
  934. log.info("[excloud]excloud.getip", "更新配置:", config.host, config.port)
  935. else
  936. log.warn("[excloud]未获取到有效的连接信息,将使用原有配置")
  937. end
  938. return true, config.current_conninfo
  939. end
  940. -- 带重试的getip请求
  941. function excloud.getip_with_retry(getip_type)
  942. local retry_count = 0
  943. local max_retry = config.max_getip_retry or 3
  944. local success, result
  945. while retry_count < max_retry do
  946. success, result = excloud.getip(getip_type)
  947. if success and result then
  948. log.info("[excloud]excloud.getip", "成功:", success, "结果:", json.encode(result))
  949. config.getip_retry_count = 0
  950. return true, result
  951. end
  952. retry_count = retry_count + 1
  953. config.getip_retry_count = retry_count
  954. log.warn("excloud.getip重试", "次数:", retry_count, "错误:", result)
  955. if retry_count < max_retry then
  956. sys.wait(5000) -- 等待5秒后重试
  957. end
  958. end
  959. return false, "getip请求失败,已达最大重试次数: " .. (result or "未知错误")
  960. end
  961. -- 发送文件上传开始通知
  962. local function send_file_upload_start(file_type, file_name, file_size)
  963. -- 构建子TLV数据
  964. -- local sub_tlvs = ""
  965. -- 文件上传类型子TLV
  966. -- local success, tlv_data = build_tlv(FIELD_MEANINGS.FILE_UPLOAD_TYPE, DATA_TYPES.INTEGER, file_type)
  967. -- if success then
  968. -- sub_tlvs = sub_tlvs .. tlv_data
  969. -- end
  970. -- 文件名称子TLV
  971. -- success, tlv_data = build_tlv(FIELD_MEANINGS.FILE_NAME, DATA_TYPES.ASCII, file_name)
  972. -- if success then
  973. -- sub_tlvs = sub_tlvs .. tlv_data
  974. -- end
  975. -- 文件大小子TLV
  976. -- success, tlv_data = build_tlv(FIELD_MEANINGS.FILE_SIZE, DATA_TYPES.INTEGER, file_size)
  977. -- if success then
  978. -- sub_tlvs = sub_tlvs .. tlv_data
  979. -- end
  980. local sub_tlvs = 0
  981. -- 主TLV(文件上传开始通知)
  982. local message = {
  983. {
  984. field_meaning = FIELD_MEANINGS.FILE_UPLOAD_START,
  985. data_type = DATA_TYPES.INTEGER,
  986. value = sub_tlvs -- 子TLV数据作为二进制值
  987. },
  988. {
  989. field_meaning = FIELD_MEANINGS.FILE_UPLOAD_TYPE,
  990. data_type = DATA_TYPES.INTEGER,
  991. value = file_type
  992. },
  993. {
  994. field_meaning = FIELD_MEANINGS.FILE_NAME,
  995. data_type = DATA_TYPES.ASCII,
  996. value = file_name
  997. },
  998. {
  999. field_meaning = FIELD_MEANINGS.FILE_SIZE,
  1000. data_type = DATA_TYPES.INTEGER,
  1001. value = file_size
  1002. }
  1003. }
  1004. return excloud.send(message, false)
  1005. end
  1006. -- 发送文件上传完成通知
  1007. local function send_file_upload_finish(file_type, file_name, file_success)
  1008. -- 构建子TLV数据
  1009. -- local sub_tlvs = ""
  1010. -- -- 文件上传类型子TLV
  1011. -- local success, tlv_data = build_tlv(FIELD_MEANINGS.FILE_UPLOAD_TYPE, DATA_TYPES.INTEGER, file_type)
  1012. -- if success then
  1013. -- sub_tlvs = sub_tlvs .. tlv_data
  1014. -- end
  1015. -- 文件名称子TLV
  1016. -- success, tlv_data = build_tlv(FIELD_MEANINGS.FILE_NAME, DATA_TYPES.ASCII, file_name)
  1017. -- if success then
  1018. -- sub_tlvs = sub_tlvs .. tlv_data
  1019. -- end
  1020. -- 上传结果状态子TLV
  1021. -- success, tlv_data = build_tlv(FIELD_MEANINGS.UPLOAD_RESULT_STATUS, DATA_TYPES.INTEGER, file_success and 0 or 1)
  1022. -- if success then
  1023. -- sub_tlvs = sub_tlvs .. tlv_data
  1024. -- end
  1025. local sub_tlvs = 0
  1026. -- 主TLV(文件上传完成通知)
  1027. local message = {
  1028. {
  1029. field_meaning = FIELD_MEANINGS.FILE_UPLOAD_FINISH,
  1030. data_type = DATA_TYPES.INTEGER,
  1031. value = sub_tlvs -- 子TLV数据作为二进制值
  1032. },
  1033. {
  1034. field_meaning = FIELD_MEANINGS.FILE_UPLOAD_TYPE,
  1035. data_type = DATA_TYPES.INTEGER,
  1036. value = file_type
  1037. },
  1038. {
  1039. field_meaning = FIELD_MEANINGS.FILE_NAME,
  1040. data_type = DATA_TYPES.ASCII,
  1041. value = file_name
  1042. },
  1043. {
  1044. field_meaning = FIELD_MEANINGS.UPLOAD_RESULT_STATUS,
  1045. data_type = DATA_TYPES.INTEGER,
  1046. value = file_success and 0 or 1
  1047. }
  1048. }
  1049. return excloud.send(message, false)
  1050. end
  1051. local function upload_file(file_type, file_path, file_name)
  1052. local upload_info
  1053. if file_type == 1 then
  1054. upload_info = config.current_imginfo
  1055. elseif file_type == 2 then
  1056. upload_info = config.current_audinfo
  1057. elseif file_type == 3 then
  1058. upload_info = config.current_mtninfo -- 新增:运维日志上传
  1059. else
  1060. return false, "不支持的文件类型"
  1061. end
  1062. if not upload_info then
  1063. return false, "未获取到上传配置信息,请先执行getip"
  1064. end
  1065. if not upload_info.url then
  1066. return false, "上传URL为空"
  1067. end
  1068. local file_size = io.fileSize(file_path)
  1069. if not file_size or file_size == 0 then
  1070. return false, "文件不存在或为空"
  1071. end
  1072. log.info("[excloud]开始文件上传", "类型:", file_type, "文件:", file_name, "大小:", file_size)
  1073. -- 发送上传开始通知(运维日志不需要)
  1074. if file_type ~= 3 then
  1075. local ok, err = send_file_upload_start(file_type, file_name, file_size)
  1076. if not ok then
  1077. log.warn("发送上传开始通知失败", err)
  1078. end
  1079. end
  1080. -- 执行HTTP请求
  1081. local code, response = httpplus.request(
  1082. {
  1083. method = "POST",
  1084. url = upload_info.url,
  1085. forms = { ["key"] = upload_info.data_param.key },
  1086. files = { [upload_info.data_key or "f"] = file_path }
  1087. })
  1088. -- 检查响应是否为nil
  1089. if not response then
  1090. log.error("[excloud]HTTP请求返回空响应")
  1091. -- 运维日志不需要发送上传完成通知
  1092. if file_type ~= 3 then
  1093. send_file_upload_finish(file_type, timestamped_filename, false)
  1094. end
  1095. return false, "HTTP请求失败: 空响应"
  1096. end
  1097. log.info("[excloud]excloud.getip文件上传响应", "HTTP Code:", code, "Body:", response.body:query(), "Body:",
  1098. json.encode(response))
  1099. local upload_success = false
  1100. local result_msg = ""
  1101. if code == 200 then
  1102. local resp_data, err = json.decode(response.body:query())
  1103. if resp_data and resp_data.code == 0 then
  1104. upload_success = true
  1105. result_msg = "上传成功"
  1106. log.info("[excloud]文件上传成功", "URL:", resp_data.value and resp_data.value.uri or "未知")
  1107. else
  1108. result_msg = "服务器返回错误: " .. (resp_data and tostring(resp_data.code) or "未知")
  1109. log.error("文件上传失败", result_msg, "响应:", resp_body)
  1110. end
  1111. else
  1112. result_msg = "HTTP请求失败: " .. tostring(code)
  1113. log.error("文件上传HTTP请求失败", result_msg)
  1114. end
  1115. -- 发送上传完成通知(运维日志不需要)
  1116. if file_type ~= 3 then
  1117. local notify_ok, notify_err = send_file_upload_finish(file_type, file_name, upload_success)
  1118. if not notify_ok then
  1119. log.warn("发送上传完成通知失败", notify_err)
  1120. end
  1121. end
  1122. return upload_success, result_msg
  1123. end
  1124. -- 运维日志上传接口
  1125. function excloud.upload_mtnlog(file_path, file_name)
  1126. -- 判断是否是手动填写IP,不是getip的话,不允许上传文件
  1127. if not config.use_getip then
  1128. log.warn("[excloud]手动填写IP时不允许上传文件")
  1129. return false, "手动填写IP时不允许上传文件"
  1130. end
  1131. file_name = file_name or "mtn_log_" .. os.time() .. ".trc"
  1132. -- 检查文件是否存在
  1133. if not io.exists(file_path) then
  1134. return false, "文件不存在: " .. file_path
  1135. end
  1136. -- 如果没有文件上传配置,先获取
  1137. if not config.current_mtninfo then
  1138. log.info("[excloud]获取运维日志上传配置...")
  1139. local getip_type = config.transport == "tcp" and 3 or
  1140. config.transport == "udp" and 4 or
  1141. config.transport == "mqtt" and 5 or 3
  1142. local ok, err = excloud.getip_with_retry(getip_type)
  1143. if not ok then
  1144. return false, "获取运维日志上传配置失败: " .. err
  1145. end
  1146. end
  1147. return upload_file(3, file_path, file_name) -- 文件类型为3
  1148. end
  1149. -- 图片上传接口
  1150. function excloud.upload_image(file_path, file_name)
  1151. -- 判断是否是手动填写IP,不是getip的话,不允许上传文件
  1152. if not config.use_getip then
  1153. log.warn("[excloud]手动填写IP时不允许上传图片文件")
  1154. return false, "手动填写IP时不允许上传图片文件"
  1155. end
  1156. if not io.exists(file_path) then
  1157. return false, "文件不存在: " .. file_path
  1158. end
  1159. -- 没有连接时直接退出
  1160. if not is_connected then
  1161. return false, "没有连接到服务器"
  1162. end
  1163. file_name = file_name or "image_" .. os.time() .. ".jpg"
  1164. -- 如果没有图片上传配置,先获取
  1165. if not config.current_imginfo then
  1166. log.info("[excloud]excloud.upload_image", "获取图片上传配置...")
  1167. local getip_type = config.transport == "tcp" and 3 or
  1168. config.transport == "udp" and 4 or
  1169. config.transport == "mqtt" and 5 or 3
  1170. local ok, err = excloud.getip_with_retry(getip_type)
  1171. if not ok then
  1172. return false, "获取图片上传配置失败: " .. err
  1173. end
  1174. end
  1175. return upload_file(1, file_path, file_name)
  1176. end
  1177. -- 音频上传接口
  1178. function excloud.upload_audio(file_path, file_name)
  1179. -- 判断是否是手动填写IP,不是getip的话,不允许上传文件
  1180. if not config.use_getip then
  1181. log.warn("[excloud]手动填写IP时不允许上传音频文件")
  1182. return false, "手动填写IP时不允许上传音频文件"
  1183. end
  1184. if not io.exists(file_path) then
  1185. return false, "文件不存在: " .. file_path
  1186. end
  1187. -- 没有连接时直接退出
  1188. if not is_connected then
  1189. return false, "没有连接到服务器"
  1190. end
  1191. file_name = file_name or "audio_" .. os.time() .. ".mp3"
  1192. -- 如果没有音频上传配置,先获取
  1193. if not config.current_audinfo then
  1194. log.info("[excloud]excloud.upload_audio", "获取音频上传配置...")
  1195. local getip_type = config.transport == "tcp" and 3 or
  1196. config.transport == "udp" and 4 or
  1197. config.transport == "mqtt" and 5 or 3
  1198. local ok, err = excloud.getip_with_retry(getip_type)
  1199. if not ok then
  1200. return false, "获取音频上传配置失败: " .. err
  1201. end
  1202. end
  1203. return upload_file(2, file_path, file_name)
  1204. end
  1205. -- 记录运维日志
  1206. --[[
  1207. 输出运维日志并写入文件
  1208. @api excloud.mtn_log(level, tag, ...)
  1209. @string level 日志级别,必须是 "info", "warn", 或 "error"
  1210. @string tag 日志标识,必须是字符串
  1211. @... 需打印的参数
  1212. @return boolean 成功返回true,失败返回false
  1213. @usage
  1214. excloud.mtn_log("info", "message", 123)
  1215. excloud.mtn_log("warn", "message", 456)
  1216. excloud.mtn_log("error", "message", 789)
  1217. ]]
  1218. function excloud.mtn_log(level, tag, ...)
  1219. if not config.mtn_log_enabled then
  1220. return false, "运维日志功能已禁用" -- 禁用时返回失败
  1221. end
  1222. exmtn.log(level, tag, ...)
  1223. return true
  1224. end
  1225. -- 获取运维日志状态
  1226. function excloud.get_mtn_log_status()
  1227. if not config.mtn_log_enabled then
  1228. return {
  1229. enabled = false,
  1230. message = "运维日志功能已禁用"
  1231. }
  1232. end
  1233. local config_info = exmtn.get_config()
  1234. local log_files = scan_mtn_log_files()
  1235. local total_size = 0
  1236. for _, file in ipairs(log_files) do
  1237. total_size = total_size + file.size
  1238. end
  1239. return {
  1240. enabled = true,
  1241. config = config_info,
  1242. file_count = #log_files,
  1243. total_size = total_size,
  1244. files = log_files,
  1245. last_error = exmtn.get_last_error()
  1246. }
  1247. end
  1248. -- 重连
  1249. local function schedule_reconnect()
  1250. -- 检查是否已经关闭服务
  1251. if not is_open then
  1252. log.info("[excloud]服务已关闭,停止重连")
  1253. return
  1254. end
  1255. -- 检查是否达到最大重连次数
  1256. if reconnect_count >= config.max_reconnect then
  1257. log.info("[excloud]到达最大重连次数 " .. reconnect_count .. "/" .. config.max_reconnect)
  1258. -- 使用协程执行复杂的重连逻辑
  1259. sys.taskInit(function()
  1260. -- 执行紧急内存清理
  1261. collectgarbage("collect")
  1262. pending_messages = {}
  1263. -- 根据use_getip决定是否重新获取服务器信息
  1264. if config.use_getip then
  1265. log.info("[excloud]TCP连接多次失败,重新获取服务器信息...")
  1266. local getip_type = config.transport == "tcp" and 3 or
  1267. config.transport == "udp" and 4 or
  1268. config.transport == "mqtt" and 5 or 3
  1269. -- 清除当前连接信息,强制重新获取
  1270. config.current_conninfo = nil
  1271. local ok, result = excloud.getip_with_retry(getip_type)
  1272. if ok then
  1273. log.info("[excloud]重新获取服务器信息成功,重置重连计数",
  1274. "host:", config.host,
  1275. "port:", config.port,
  1276. "transport:", config.transport)
  1277. -- 重置重连计数
  1278. reconnect_count = 0
  1279. -- 使用新的服务器信息重新连接(先关闭再打开)
  1280. excloud.close() -- 确保完全关闭
  1281. sys.wait(200) -- 在协程中可以安全使用 wait
  1282. excloud.open() -- 重新打开
  1283. else
  1284. log.error("[excloud]重新获取服务器信息失败,停止重连")
  1285. if callback_func then
  1286. callback_func("reconnect_failed", {
  1287. count = reconnect_count,
  1288. max_reconnect = config.max_reconnect,
  1289. getip_failed = true
  1290. })
  1291. end
  1292. -- 彻底停止重连
  1293. is_open = false
  1294. end
  1295. else
  1296. -- 不使用getip,直接停止重连
  1297. log.info("[excloud]达到最大重连次数,停止重连")
  1298. if callback_func then
  1299. callback_func("reconnect_failed", {
  1300. count = reconnect_count,
  1301. max_reconnect = config.max_reconnect
  1302. })
  1303. end
  1304. is_open = false
  1305. end
  1306. end)
  1307. return
  1308. end
  1309. -- 增加重连计数
  1310. reconnect_count = reconnect_count + 1
  1311. log.info("[excloud]安排第 " ..
  1312. reconnect_count .. "/" .. config.max_reconnect .. " 次重连,等待 " .. config.reconnect_interval .. " 秒")
  1313. -- 使用定时器安排重连,在协程中执行
  1314. reconnect_timer = sys.timerStart(function()
  1315. sys.taskInit(function()
  1316. log.info("[excloud]执行第 " .. reconnect_count .. "/" .. config.max_reconnect .. " 次重连")
  1317. -- 在重连前检查服务状态
  1318. if not is_open then
  1319. log.info("[excloud]服务已关闭,取消重连")
  1320. return
  1321. end
  1322. -- 先执行内存清理
  1323. collectgarbage("collect")
  1324. -- 如果连接对象存在但连接已断开,先清理
  1325. if connection and not is_connected then
  1326. log.info("[excloud]清理残留的连接对象")
  1327. if config.transport == "tcp" then
  1328. socket.close(connection)
  1329. socket.release(connection)
  1330. elseif config.transport == "mqtt" then
  1331. connection:disconnect()
  1332. connection:close()
  1333. end
  1334. connection = nil
  1335. sys.wait(50) -- 在协程中安全等待
  1336. end
  1337. -- 重置连接状态但保持服务开启状态
  1338. is_connected = false
  1339. is_authenticated = false
  1340. -- 执行重连
  1341. local success, err = excloud.open()
  1342. if not success then
  1343. log.error("[excloud]重连失败:", err)
  1344. -- 重连失败会再次触发schedule_reconnect
  1345. else
  1346. log.info("[excloud]重连操作已发起")
  1347. end
  1348. end)
  1349. end, config.reconnect_interval * 1000)
  1350. end
  1351. -- TCP socket事件回调函数
  1352. local function tcp_socket_callback(netc, event, param)
  1353. log.info("[excloud]socket cb", netc, event, param)
  1354. -- 取消连接超时定时器
  1355. if connect_timeout_timer then
  1356. sys.timerStop(connect_timeout_timer)
  1357. connect_timeout_timer = nil
  1358. end
  1359. -- 记录连接状态变化的运维日志
  1360. if config.aircloud_mtn_log_enabled then
  1361. if event == socket.LINK then
  1362. exmtn.log("info", "aircloud", "net_conn", "网络连接成功")
  1363. elseif event == socket.ON_LINE then
  1364. exmtn.log("info", "aircloud", "net_conn", "TCP连接成功", "host", config.host, "port", config.port)
  1365. elseif event == socket.CLOSED then
  1366. exmtn.log("info", "aircloud", "net_conn", "TCP连接断开", "param", param)
  1367. end
  1368. end
  1369. if param ~= 0 then
  1370. log.info("[excloud]socket", "连接断开")
  1371. is_connected = false
  1372. is_authenticated = false
  1373. if callback_func then
  1374. callback_func("disconnect", {})
  1375. end
  1376. -- 连接断开,释放资源
  1377. socket.release(connection)
  1378. connection = nil
  1379. -- 尝试重连
  1380. if config.auto_reconnect and is_open then
  1381. -- is_open = false
  1382. schedule_reconnect()
  1383. end
  1384. return
  1385. end
  1386. if event == socket.LINK then
  1387. -- 网络连接成功
  1388. log.info("[excloud]socket", "网络连接成功")
  1389. elseif event == socket.ON_LINE then
  1390. -- TCP连接成功
  1391. log.info("[excloud]socket", "TCP连接成功")
  1392. is_connected = true
  1393. -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
  1394. reconnect_count = 0
  1395. if callback_func then
  1396. callback_func("connect_result", { success = true })
  1397. end
  1398. -- 发送认证请求
  1399. send_auth_request()
  1400. elseif event == socket.EVENT then
  1401. -- 有数据到达
  1402. socket.rx(netc, rxbuff)
  1403. if rxbuff:used() > 0 then
  1404. local data = rxbuff:query()
  1405. log.info("[excloud]socket", "收到数据", #data, "字节", data:toHex())
  1406. -- 处理接收到的数据
  1407. parse_data(data)
  1408. end
  1409. -- -- 清空缓冲区
  1410. rxbuff:del()
  1411. elseif event == socket.TX_OK then
  1412. socket.wait(netc)
  1413. log.info("[excloud]socket", "发送完成")
  1414. elseif event == socket.CLOSED then
  1415. -- 连接错误或关闭
  1416. socket.release(connection)
  1417. connection = nil
  1418. log.info("[excloud]socket", "主动断开链接")
  1419. end
  1420. end
  1421. -- mqtt client的事件回调函数
  1422. local function mqtt_client_event_cbfunc(connected, event, data, payload, metas)
  1423. log.info("[excloud]mqtt_client_event_cbfunc", event, data, payload, json.encode(metas))
  1424. -- 取消连接超时定时器
  1425. if connect_timeout_timer then
  1426. sys.timerStop(connect_timeout_timer)
  1427. connect_timeout_timer = nil
  1428. end
  1429. -- 记录MQTT状态变化的运维日志
  1430. if config.aircloud_mtn_log_enabled then
  1431. if event == "conack" then
  1432. exmtn.log("info", "aircloud", "mqtt_conn", "MQTT连接成功", "host", config.host)
  1433. elseif event == "disconnect" then
  1434. exmtn.log("info", "aircloud", "mqtt_conn", "MQTT连接断开")
  1435. elseif event == "error" then
  1436. exmtn.log("info", "aircloud", "mqtt_error", "MQTT错误", "type", data, "code", payload)
  1437. end
  1438. end
  1439. -- mqtt连接成功
  1440. if event == "conack" then
  1441. is_connected = true
  1442. log.info("[excloud]MQTT connected")
  1443. -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
  1444. reconnect_count = 0
  1445. -- 订阅主题
  1446. local device_id_hex = string.toHex(device_id_binary)
  1447. local auth_topic = "/AirCloud/down/" .. device_id_hex .. "/auth"
  1448. local all_topic = "/AirCloud/down/" .. device_id_hex .. "/all"
  1449. log.info("[excloud]mqtt_client_event_cbfunc", "订阅主题", auth_topic, all_topic)
  1450. connection:subscribe(auth_topic, 0)
  1451. connection:subscribe(all_topic, 0)
  1452. if callback_func then
  1453. callback_func("connect_result", { success = true })
  1454. end
  1455. -- 发送认证请求
  1456. send_auth_request()
  1457. -- 订阅成功
  1458. elseif event == "suback" then
  1459. -- 取消订阅成功
  1460. elseif event == "unsuback" then
  1461. -- 接收到服务器下发的publish数据
  1462. -- data:string类型,表示topic
  1463. -- payload:string类型,表示payload
  1464. -- metas:table类型,数据内容如下
  1465. -- {
  1466. -- qos: number类型,取值范围0,1,2
  1467. -- retain:number类型,取值范围0,1
  1468. -- dup:number类型,取值范围0,1
  1469. -- message_id: number类型
  1470. -- }
  1471. elseif event == "recv" then
  1472. log.info("[excloud]接收到MQTT消息",
  1473. "主题:", data,
  1474. "数据长度:", #payload,
  1475. "QoS:", metas and metas.qos or "unknown",
  1476. "消息ID:", metas and metas.message_id or "unknown")
  1477. -- 对接收到的publish数据处理
  1478. parse_data(payload)
  1479. -- 发送成功publish数据
  1480. -- data:number类型,表示message id
  1481. elseif event == "sent" then
  1482. -- 服务器断开mqtt连接
  1483. elseif event == "disconnect" then
  1484. is_connected = false
  1485. is_authenticated = false
  1486. log.info("[excloud]MQTT disconnected")
  1487. if callback_func then
  1488. callback_func("disconnect", {})
  1489. end
  1490. -- 尝试重连
  1491. if config.auto_reconnect and is_open then
  1492. -- is_open = false
  1493. schedule_reconnect()
  1494. end
  1495. -- 收到服务器的心跳应答
  1496. elseif event == "pong" then
  1497. -- 严重异常,本地会主动断开连接
  1498. -- data:string类型,表示具体的异常,有以下几种:
  1499. -- "connect":tcp连接失败
  1500. -- "tx":数据发送失败
  1501. -- "conack":mqtt connect后,服务器应答CONNACK鉴权失败,失败码为payload(number类型)
  1502. -- "other":其他异常
  1503. elseif event == "error" then
  1504. is_connected = false
  1505. is_authenticated = false
  1506. local error_msg = "Unknown MQTT error"
  1507. if data == "connect" then
  1508. error_msg = "TCP connection failed"
  1509. -- 连接失败,应该考虑重新获取服务器信息
  1510. if reconnect_count >= config.max_reconnect and config.use_getip then
  1511. log.info("[excloud]MQTT连接多次失败,需要重新获取服务器信息")
  1512. config.current_conninfo = nil
  1513. end
  1514. elseif data == "tx" then
  1515. error_msg = "Data transmission failed"
  1516. elseif data == "conack" then
  1517. error_msg = "MQTT authentication failed with code: " .. tostring(payload)
  1518. else
  1519. error_msg = "Other MQTT error: " .. tostring(data)
  1520. end
  1521. log.info("[excloud]MQTT error: " .. error_msg)
  1522. if callback_func then
  1523. callback_func("disconnect", { error = error_msg })
  1524. end
  1525. -- 安全释放连接资源
  1526. if connection then
  1527. connection:disconnect()
  1528. connection:close()
  1529. connection = nil
  1530. end
  1531. -- 尝试重连
  1532. if config.auto_reconnect and is_open then
  1533. -- is_open = false
  1534. schedule_reconnect()
  1535. end
  1536. end
  1537. end
  1538. -- 设置配置参数
  1539. function excloud.setup(params)
  1540. if is_open then
  1541. return false, "excloud is already open"
  1542. end
  1543. -- 合并配置参数
  1544. for k, v in pairs(params) do
  1545. config[k] = v
  1546. end
  1547. -- 验证必要参数
  1548. if not config.auth_key then
  1549. return false, "auth_key is required"
  1550. end
  1551. if config.device_type == 1 then
  1552. config.device_id = mobile.imei()
  1553. log.info("[excloud]4G设备", "IMEI:", config.device_id, "MUID:", mobile.muid())
  1554. elseif config.device_type == 2 then
  1555. config.device_id = wlan.getMac(nil, true)
  1556. --以太网设备
  1557. elseif config.device_type == 4 then
  1558. config.device_id = netdrv.mac(socket.LWIP_ETH)
  1559. elseif config.device_type == 9 then
  1560. -- 虚拟设备:验证手机号和序列号
  1561. if not config.virtual_phone_number then
  1562. return false, "虚拟设备需要配置 virtual_phone_number"
  1563. end
  1564. -- 验证手机号格式(11位数字)
  1565. local phone_clean = config.virtual_phone_number:gsub("%D", "")
  1566. if #phone_clean ~= 11 then
  1567. return false, "虚拟手机号必须为11位数字"
  1568. end
  1569. -- 设置默认序列号(如果未提供)
  1570. if config.virtual_serial_num == nil then
  1571. config.virtual_serial_num = 0
  1572. end
  1573. -- 序列号范围检查(0-999)
  1574. config.virtual_serial_num = config.virtual_serial_num % 1000
  1575. -- 生成设备ID:手机号 + 3位序列号
  1576. local serial_str = string.format("%03d", config.virtual_serial_num)
  1577. config.device_id = phone_clean .. serial_str
  1578. log.info("虚拟设备配置", "手机号:", config.virtual_phone_number, "序列号:", serial_str, "设备ID:", config.device_id)
  1579. else
  1580. log.info("[excloud]未知设备类型", config.device_type)
  1581. config.device_id = "unknown"
  1582. end
  1583. -- 打包设备id
  1584. device_id_binary = packDeviceInfo(config.device_type, config.device_id)
  1585. -- 初始化运维日志模块
  1586. local mtn_ok, mtn_err = init_mtn_log()
  1587. if not mtn_ok then
  1588. log.warn("[excloud]运维日志初始化失败,但继续excloud初始化:", mtn_err)
  1589. end
  1590. log.info("[excloud]excloud.setup", "初始化成功", "设备ID:", config.device_id)
  1591. return true
  1592. end
  1593. -- 注册回调函数
  1594. function excloud.on(cbfunc)
  1595. if type(cbfunc) ~= "function" then
  1596. return false, "Callback must be a function"
  1597. end
  1598. callback_func = cbfunc
  1599. return true
  1600. end
  1601. -- 开启excloud服务
  1602. function excloud.open()
  1603. -- 如果之前连接异常断开,但状态未重置,先清理
  1604. if is_open and not is_connected then
  1605. log.warn("[excloud]检测到状态不一致,先清理残留状态")
  1606. excloud.close()
  1607. end
  1608. -- 检查是否已打开
  1609. if is_open and is_connected then
  1610. return false, "excloud is already open and connected"
  1611. end
  1612. reconnect_count = 0
  1613. -- 判断是否初始化
  1614. if not device_id_binary then
  1615. return false, "excloud 没有初始化,请先调用setup"
  1616. end
  1617. -- 根据use_getip决定是否使用getip服务
  1618. if config.use_getip then
  1619. -- 使用getip服务发现
  1620. local getip_type
  1621. if config.transport == "tcp" then
  1622. getip_type = 3
  1623. elseif config.transport == "udp" then
  1624. getip_type = 4
  1625. elseif config.transport == "mqtt" then
  1626. getip_type = 5
  1627. else
  1628. return false, "不支持的传输协议: " .. config.transport
  1629. end
  1630. -- 获取服务器连接信息
  1631. if not config.current_conninfo or (config.transport ~= "mqtt" and not config.current_conninfo.ipv4) or
  1632. (config.transport == "mqtt" and not config.current_conninfo.ssl) then
  1633. log.info("[excloud]首次连接,获取服务器信息...")
  1634. local ok, result = excloud.getip_with_retry(getip_type)
  1635. if not ok then
  1636. return false, "获取服务器信息失败: " .. result
  1637. end
  1638. -- 更新连接配置
  1639. log.info("[excloud]服务器信息获取成功", "host:", config.host, "port:", config.port, "transport:", config.transport)
  1640. -- 保存文件上传信息
  1641. if result.imginfo then
  1642. config.current_imginfo = result.imginfo
  1643. end
  1644. if result.audinfo then
  1645. config.current_audinfo = result.audinfo
  1646. end
  1647. end
  1648. else
  1649. -- 不使用getip,直接使用用户配置的host和port
  1650. log.info("使用手动配置的服务器地址", config.host, config.port)
  1651. if not config.host or not config.port then
  1652. return false, "use_getip为false时,必须配置host和port"
  1653. end
  1654. end
  1655. -- 根据传输协议创建连接
  1656. if config.transport == "tcp" then
  1657. -- 创建接收缓冲区
  1658. rxbuff = zbuff.create(2048)
  1659. -- 创建TCP连接
  1660. log.info("[excloud]创建TCP连接")
  1661. connection = socket.create(nil, tcp_socket_callback)
  1662. if not connection then
  1663. return false, "Failed to create socket"
  1664. end
  1665. -- 准备SSL配置参数
  1666. local ssl_config = nil
  1667. if config.ssl then
  1668. if type(config.ssl) == "table" then
  1669. -- 使用详细的SSL配置
  1670. ssl_config = config.ssl
  1671. else
  1672. -- 简单的SSL启用
  1673. ssl_config = true
  1674. end
  1675. end
  1676. -- 配置socket参数
  1677. local config_success = socket.config(
  1678. connection,
  1679. config.local_port, -- 本地端口号
  1680. false, -- 是否是UDP,TCP连接为false
  1681. ssl_config and true or false, -- 是否是加密传输
  1682. config.keep_idle, -- keepalive idle时间
  1683. config.keep_interval, -- keepalive 探测间隔
  1684. config.keep_cnt, -- keepalive 探测次数
  1685. ssl_config and ssl_config.server_cert or nil, -- 服务器CA证书
  1686. ssl_config and ssl_config.client_cert or nil, -- 客户端证书
  1687. ssl_config and ssl_config.client_key or nil, -- 客户端私钥
  1688. ssl_config and ssl_config.client_password or nil -- 客户端私钥口令
  1689. )
  1690. if not config_success then
  1691. socket.release(connection)
  1692. connection = nil
  1693. return false, "Socket config failed"
  1694. end
  1695. socket.debug(connection, true)
  1696. -- 设置连接超时定时器
  1697. connect_timeout_timer = sys.timerStart(function()
  1698. if not is_connected then
  1699. log.error("TCP connection timeout")
  1700. if connection then
  1701. socket.close(connection)
  1702. socket.release(connection)
  1703. connection = nil
  1704. end
  1705. if callback_func then
  1706. callback_func("connect_result", { success = false, error = "Connection timeout" })
  1707. end
  1708. -- 尝试重连
  1709. if config.auto_reconnect and is_open then
  1710. -- is_open = false
  1711. schedule_reconnect()
  1712. end
  1713. end
  1714. end, config.timeout * 1000)
  1715. -- 连接到服务器
  1716. local ok, result = socket.connect(connection, config.host, config.port, config.mqtt_ipv6)
  1717. log.info("[excloud]TCP连接结果", ok, result)
  1718. if not ok then
  1719. --发生异常,强制close
  1720. socket.close(connection)
  1721. --释放资源
  1722. socket.release(connection)
  1723. connection = nil
  1724. if config.auto_reconnect then
  1725. is_open = false
  1726. schedule_reconnect()
  1727. end
  1728. return false, result
  1729. end
  1730. elseif config.transport == "mqtt" then
  1731. -- 准备MQTT SSL配置 - MQTT连接默认使用SSL加密
  1732. local ssl_config = true -- 最简单的SSL加密,不验证服务器证书
  1733. -- 如果有详细的SSL配置,使用详细配置
  1734. if config.ssl and type(config.ssl) == "table" then
  1735. ssl_config = config.ssl
  1736. end
  1737. -- 准备MQTT扩展参数
  1738. local mqtt_opts = {
  1739. rxSize = config.mqtt_rx_size or 32 * 1024, -- MQTT接收缓冲区大小,默认32K
  1740. conn_timeout = config.mqtt_conn_timeout or 30, -- MQTT连接超时时间,默认30秒
  1741. ipv6 = config.mqtt_ipv6 or false -- 是否使用IPv6连接,默认false
  1742. }
  1743. -- 创建MQTT客户端
  1744. connection = mqtt.create(nil, config.host, config.port, ssl_config, mqtt_opts)
  1745. if not connection then
  1746. return false, "Failed to create MQTT client"
  1747. end
  1748. -- 开启调试信息(可选)
  1749. if config.debug then
  1750. connection:debug(true)
  1751. end
  1752. -- 设置真实的MQTT认证信息
  1753. local client_id, username, password
  1754. if config.device_type == 1 then -- 4G设备
  1755. client_id = mobile.imei()
  1756. username = mobile.imei()
  1757. password = mobile.muid()
  1758. -- elseif config.device_type == 2 then -- WIFI设备
  1759. -- client_id = wlan.getMac(nil, true)
  1760. -- username = wlan.getMac(nil, true)
  1761. -- password = mobile.muid():toHex()
  1762. -- elseif config.device_type == 4 then -- 以太网设备
  1763. -- client_id = netdrv.mac(socket.LWIP_ETH)
  1764. -- username = netdrv.mac(socket.LWIP_ETH)
  1765. -- password = mobile.muid():toHex()
  1766. -- elseif config.device_type == 9 then -- 虚拟设备
  1767. -- -- 虚拟设备使用配置的设备ID
  1768. -- client_id = config.device_id
  1769. -- username = config.device_id
  1770. -- password = config.auth_key or config.device_id
  1771. else
  1772. return false, "MQTT connect failed, device_type not supported"
  1773. end
  1774. log.info("[excloud]MQTT认证信息",
  1775. "client_id:", client_id,
  1776. "username:", username,
  1777. "password:", password)
  1778. -- 设置认证信息(使用真实的设备信息,而不是getip返回的提示)
  1779. connection:auth(client_id, username, password, config.clean_session)
  1780. -- 设置保持连接间隔
  1781. connection:keepalive(config.keepalive or 240) -- 默认240秒
  1782. -- 设置遗嘱消息(如果需要)
  1783. if config.will_topic and config.will_payload then
  1784. local will_result = connection:will(
  1785. config.will_topic,
  1786. config.will_payload,
  1787. config.will_qos or 0,
  1788. config.will_retain or 0
  1789. )
  1790. if not will_result then
  1791. log.warn("[excloud]设置遗嘱消息失败")
  1792. end
  1793. end
  1794. -- 设置自动重连
  1795. if config.auto_reconnect then
  1796. connection:autoreconn(true, (config.reconnect_interval or 10) * 1000) -- 转换为毫秒
  1797. end
  1798. -- 注册事件回调
  1799. connection:on(mqtt_client_event_cbfunc)
  1800. -- 设置连接超时定时器
  1801. connect_timeout_timer = sys.timerStart(function()
  1802. if not is_connected then
  1803. log.error("MQTT connection timeout")
  1804. if connection then
  1805. connection:disconnect()
  1806. connection:close()
  1807. connection = nil
  1808. end
  1809. if callback_func then
  1810. callback_func("connect_result", { success = false, error = "Connection timeout" })
  1811. end
  1812. -- 尝试重连
  1813. if config.auto_reconnect and is_open then
  1814. -- is_open = false
  1815. schedule_reconnect()
  1816. end
  1817. end
  1818. end, config.timeout * 1000)
  1819. -- 连接到服务器
  1820. local ok = connection:connect()
  1821. if not ok then
  1822. --连接失败,释放资源
  1823. connection:close()
  1824. connection = nil
  1825. -- 发起连接失败,尝试重连
  1826. if config.auto_reconnect then
  1827. is_open = false
  1828. schedule_reconnect()
  1829. end
  1830. return false, "MQTT connect failed"
  1831. end
  1832. else
  1833. return false, "Unsupported transport: " .. config.transport
  1834. end
  1835. is_open = true
  1836. -- 记录服务启动日志
  1837. if config.aircloud_mtn_log_enabled then
  1838. exmtn.log("info", "aircloud", "system", "excloud服务启动", "transport", config.transport, "host", config.host, "port",
  1839. config.port)
  1840. end
  1841. log.info("[excloud]excloud service started")
  1842. return true
  1843. end
  1844. -- 发送数据
  1845. -- 发送消息到云端
  1846. -- @param data table 待发送的数据,每个元素是一个包含 field_meaning、data_type 和 value 的表
  1847. -- @param need_reply boolean 是否需要服务器回复,默认为 false
  1848. -- @param is_auth_msg boolean 是否是鉴权消息,默认为 false
  1849. function excloud.send(data, need_reply, is_auth_msg)
  1850. if not is_open then
  1851. return false, "excloud服务未开启"
  1852. end
  1853. if not is_connected then
  1854. return false, "未连接到服务器"
  1855. end
  1856. -- if not is_authenticated and not is_auth_msg then
  1857. -- return false, "设备未认证"
  1858. -- end
  1859. -- 检查参数是否为table
  1860. if type(data) ~= "table" then
  1861. return false, "data must be table"
  1862. end
  1863. if need_reply == nil then
  1864. need_reply = false
  1865. end
  1866. if is_auth_msg == nil then
  1867. is_auth_msg = false
  1868. end
  1869. -- 检查服务是否开启
  1870. if not is_open then
  1871. if callback_func then
  1872. callback_func("send_result", {
  1873. success = false,
  1874. error_msg = "excloud not open"
  1875. })
  1876. end
  1877. return false, "excloud not open"
  1878. end
  1879. -- 检查是否已连接
  1880. if not is_connected then
  1881. if callback_func then
  1882. callback_func("send_result", {
  1883. success = false,
  1884. error_msg = "excloud not connected"
  1885. })
  1886. end
  1887. return false, "excloud not connected"
  1888. end
  1889. -- 保存当前序列号用于回调
  1890. local current_sequence = sequence_num
  1891. -- 构建消息体
  1892. local message_body = ""
  1893. local parts = {}
  1894. for _, item in ipairs(data) do
  1895. log.info("[excloud]构建发送数据", item.field_meaning, item.data_type, item.value, message_body)
  1896. local success, tlv = build_tlv(item.field_meaning, item.data_type, item.value)
  1897. if not success then
  1898. return false, "excloud.send data is failed"
  1899. end
  1900. table.insert(parts, tlv)
  1901. -- message_body = message_body .. tlv
  1902. end
  1903. if #parts > 0 then
  1904. message_body = table.concat(parts)
  1905. parts = {}
  1906. else
  1907. log.warn("[excloud]没有有效的TLV数据可发送")
  1908. -- return false, "No valid TLV data to send"
  1909. end
  1910. -- 检查消息长度
  1911. local udp_auth_key = config.udp_auth_key and true or false
  1912. local total_length = #message_body + (udp_auth_key and 64 or 0)
  1913. log.info("[excloud]tlv发送数据长度4", total_length)
  1914. -- 构建消息头
  1915. local is_udp_transport = (config.transport == "udp")
  1916. local header = build_header(need_reply, is_udp_transport, total_length)
  1917. -- -- 添加鉴权key(如果是UDP的话)
  1918. local auth_key_part = ""
  1919. if config.transport == "udp" and udp_auth_key then
  1920. auth_key_part = config.udp_auth_key
  1921. if #auth_key_part < 64 then
  1922. auth_key_part = auth_key_part .. string.rep("\0", 64 - #auth_key_part)
  1923. elseif #auth_key_part > 64 then
  1924. auth_key_part = auth_key_part:sub(1, 64)
  1925. end
  1926. end
  1927. local full_message
  1928. -- 发送完整消息
  1929. if config.transport == "udp" then
  1930. full_message = header .. auth_key_part .. message_body
  1931. else
  1932. full_message = header .. message_body
  1933. end
  1934. log.info("[excloud]发送消息长度", #header, #message_body, #full_message, full_message:toHex())
  1935. local success, err_msg
  1936. if config.transport == "tcp" then
  1937. if not connection then
  1938. err_msg = "TCP connection not available"
  1939. success = false
  1940. else
  1941. success, err_msg = socket.tx(connection, full_message)
  1942. end
  1943. elseif config.transport == "mqtt" then
  1944. -- 根据是否为鉴权消息选择不同的topic
  1945. local topic
  1946. local device_id_hex = string.toHex(device_id_binary)
  1947. if is_auth_msg then
  1948. topic = "/AirCloud/up/" .. device_id_hex .. "/auth"
  1949. else
  1950. topic = "/AirCloud/up/" .. device_id_hex .. "/all"
  1951. end
  1952. log.info("[excloud]发布主题", topic, #full_message, full_message:toHex())
  1953. local message_id = connection:publish(topic, full_message, config.qos, config.retain)
  1954. if message_id then
  1955. success = true
  1956. if config.qos and config.qos > 0 then
  1957. log.info("[excloud]MQTT消息发布成功", "消息ID:", message_id)
  1958. else
  1959. log.info("[excloud]MQTT消息发布成功")
  1960. end
  1961. else
  1962. success = false
  1963. err_msg = "MQTT publish failed"
  1964. end
  1965. end
  1966. -- 通过回调返回发送结果
  1967. if callback_func then
  1968. callback_func("send_result", {
  1969. success = success,
  1970. error_msg = success and "Send successful" or err_msg,
  1971. sequence_num = current_sequence
  1972. })
  1973. end
  1974. collectgarbage("collect")
  1975. if success then
  1976. log.info("[excloud]数据发送成功", #full_message, "字节")
  1977. return true
  1978. else
  1979. log.error("数据发送失败", err_msg)
  1980. return false, err_msg
  1981. end
  1982. end
  1983. -- 关闭excloud服务
  1984. function excloud.close()
  1985. if not is_open then
  1986. return false, "excloud not open"
  1987. end
  1988. -- 停止所有定时器
  1989. if reconnect_timer then
  1990. sys.timerStop(reconnect_timer)
  1991. reconnect_timer = nil
  1992. end
  1993. if connect_timeout_timer then
  1994. sys.timerStop(connect_timeout_timer)
  1995. connect_timeout_timer = nil
  1996. end
  1997. -- 停止心跳
  1998. excloud.stop_heartbeat()
  1999. -- 关闭连接
  2000. if connection then
  2001. if config.transport == "tcp" then
  2002. socket.close(connection)
  2003. socket.release(connection)
  2004. elseif config.transport == "mqtt" then
  2005. -- 断开连接并释放资源
  2006. connection:disconnect()
  2007. connection:close()
  2008. end
  2009. connection = nil
  2010. end
  2011. -- 释放缓冲区
  2012. if rxbuff then
  2013. rxbuff = nil
  2014. end
  2015. -- 清空队列
  2016. pending_messages = {}
  2017. callback_func = nil
  2018. -- 记录服务关闭日志
  2019. if config.aircloud_mtn_log_enabled then
  2020. exmtn.log("info", "aircloud", "system", "excloud服务关闭")
  2021. end
  2022. -- 重置状态
  2023. is_open = false
  2024. is_connected = false
  2025. is_authenticated = false
  2026. pending_messages = {}
  2027. rxbuff = nil
  2028. reconnect_count = 0
  2029. is_heartbeat_running = false
  2030. collectgarbage("collect")
  2031. log.info("[excloud]excloud service stopped")
  2032. return true
  2033. end
  2034. -- 获取当前状态
  2035. function excloud.status()
  2036. return {
  2037. is_open = is_open,
  2038. is_connected = is_connected,
  2039. is_authenticated = is_authenticated,
  2040. sequence_num = sequence_num,
  2041. reconnect_count = reconnect_count,
  2042. pending_messages = #pending_messages,
  2043. }
  2044. end
  2045. -- 发送心跳消息
  2046. -- @param custom_data table 可选参数,自定义心跳内容
  2047. -- @param need_reply boolean 是否需要服务器回复,默认为false
  2048. -- @return boolean 是否发送成功
  2049. -- @return string 错误信息(如果失败)
  2050. function excloud.heartbeat(custom_data, need_reply)
  2051. -- 如果心跳数据未提供,则使用默认的心跳数据(空表)
  2052. local data = custom_data or heartbeat_data
  2053. -- 设置默认不需要回复
  2054. if need_reply == nil then
  2055. need_reply = false
  2056. end
  2057. -- 调用send函数发送心跳数据
  2058. return excloud.send(data, need_reply, false)
  2059. end
  2060. -- 启动自动心跳
  2061. -- @param interval number 心跳间隔(秒),默认300秒(5分钟)
  2062. -- @param custom_data table 自定义心跳内容,默认空表
  2063. -- @return boolean 是否启动成功
  2064. function excloud.start_heartbeat(interval, custom_data)
  2065. -- 停止现有的心跳定时器
  2066. if is_heartbeat_running then
  2067. excloud.stop_heartbeat()
  2068. end
  2069. -- 设置心跳间隔,默认5分钟
  2070. heartbeat_interval = interval or 300
  2071. -- 设置心跳数据
  2072. heartbeat_data = custom_data or {}
  2073. -- 创建并启动心跳定时器
  2074. heartbeat_timer = sys.timerLoopStart(function()
  2075. if is_open and is_connected then
  2076. local ok, err_msg = excloud.heartbeat()
  2077. if not ok then
  2078. log.info("[excloud]excloud", "心跳发送失败: " .. err_msg)
  2079. else
  2080. log.info("[excloud]excloud", "心跳发送成功")
  2081. end
  2082. end
  2083. end, heartbeat_interval * 1000) -- 转换为毫秒
  2084. is_heartbeat_running = true
  2085. log.info("[excloud]excloud", "自动心跳已启动,间隔 " .. heartbeat_interval .. " 秒")
  2086. return true
  2087. end
  2088. -- 停止自动心跳
  2089. -- @return boolean 是否停止成功
  2090. function excloud.stop_heartbeat()
  2091. if heartbeat_timer then
  2092. sys.timerStop(heartbeat_timer)
  2093. heartbeat_timer = nil
  2094. is_heartbeat_running = false
  2095. log.info("[excloud]excloud", "自动心跳已停止")
  2096. return true
  2097. end
  2098. return false
  2099. end
  2100. -- 获取当前服务器信息
  2101. function excloud.get_server_info()
  2102. return {
  2103. conninfo = config.current_conninfo,
  2104. imginfo = config.current_imginfo,
  2105. audinfo = config.current_audinfo,
  2106. mtninfo = config.current_mtninfo -- 新增:运维日志上传信息
  2107. }
  2108. end
  2109. -- 强制刷新服务器信息
  2110. -- function excloud.refresh_server_info()
  2111. -- config.current_conninfo = nil
  2112. -- config.current_imginfo = nil
  2113. -- config.current_audinfo = nil
  2114. -- return true
  2115. -- end
  2116. -- 导出常量
  2117. excloud.DATA_TYPES = DATA_TYPES
  2118. excloud.FIELD_MEANINGS = FIELD_MEANINGS
  2119. excloud.MTN_LOG_STATUS = MTN_LOG_STATUS
  2120. excloud.MTN_LOG_CACHE_WRITE = exmtn.CACHE_WRITE
  2121. excloud.MTN_LOG_ADD_WRITE = exmtn.ADD_WRITE
  2122. return excloud