excloud.lua 83 KB

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