excloud.lua 88 KB

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