excloud.lua 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344
  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. collectgarbage("collect")
  1208. pending_messages = {}
  1209. -- 根据use_getip决定是否重新获取服务器信息
  1210. if config.use_getip then
  1211. log.info("[excloud]TCP连接多次失败,重新获取服务器信息...")
  1212. local getip_type = config.transport == "tcp" and 3 or
  1213. config.transport == "udp" and 4 or
  1214. config.transport == "mqtt" and 5 or 3
  1215. -- 清除当前连接信息,强制重新获取
  1216. config.current_conninfo = nil
  1217. local ok, result = excloud.getip_with_retry(getip_type)
  1218. if ok then
  1219. log.info("[excloud]重新获取服务器信息成功,重置重连计数",
  1220. "host:", config.host,
  1221. "port:", config.port,
  1222. "transport:", config.transport)
  1223. -- 重置重连计数,因为获得了新的服务器信息
  1224. reconnect_count = 0
  1225. -- 使用新的服务器信息重新连接
  1226. sys.timerStart(function()
  1227. log.info("[excloud]使用新服务器信息重新连接")
  1228. excloud.close() -- 确保完全关闭
  1229. sys.wait(200)
  1230. excloud.open()
  1231. end, config.reconnect_interval * 1000)
  1232. else
  1233. log.error("[excloud]重新获取服务器信息失败,停止重连")
  1234. if callback_func then
  1235. callback_func("reconnect_failed", {
  1236. count = reconnect_count,
  1237. max_reconnect = config.max_reconnect,
  1238. getip_failed = true
  1239. })
  1240. end
  1241. -- 彻底停止重连
  1242. is_open = false
  1243. end
  1244. else
  1245. -- 不使用getip,直接停止重连
  1246. log.info("[excloud]达到最大重连次数,停止重连")
  1247. if callback_func then
  1248. callback_func("reconnect_failed", {
  1249. count = reconnect_count,
  1250. max_reconnect = config.max_reconnect
  1251. })
  1252. end
  1253. is_open = false
  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. log.info("[excloud]执行第 " .. reconnect_count .. "/" .. config.max_reconnect .. " 次重连")
  1264. -- 在重连前检查服务状态
  1265. if not is_open then
  1266. log.info("[excloud]服务已关闭,取消重连")
  1267. return
  1268. end
  1269. -- 先执行内存清理
  1270. collectgarbage("collect")
  1271. -- 如果连接对象存在但连接已断开,先清理
  1272. if connection and not is_connected then
  1273. log.info("[excloud]清理残留的连接对象")
  1274. if config.transport == "tcp" then
  1275. socket.close(connection)
  1276. socket.release(connection)
  1277. elseif config.transport == "mqtt" then
  1278. connection:disconnect()
  1279. connection:close()
  1280. end
  1281. connection = nil
  1282. end
  1283. -- 重置连接状态但保持服务开启状态
  1284. is_connected = false
  1285. is_authenticated = false
  1286. -- 执行重连
  1287. local success, err = excloud.open()
  1288. if not success then
  1289. log.error("[excloud]重连失败:", err)
  1290. -- 重连失败会再次触发schedule_reconnect
  1291. else
  1292. log.info("[excloud]重连操作已发起")
  1293. end
  1294. end, config.reconnect_interval * 1000)
  1295. end
  1296. -- TCP socket事件回调函数
  1297. local function tcp_socket_callback(netc, event, param)
  1298. log.info("[excloud]socket cb", netc, event, param)
  1299. -- 取消连接超时定时器
  1300. if connect_timeout_timer then
  1301. sys.timerStop(connect_timeout_timer)
  1302. connect_timeout_timer = nil
  1303. end
  1304. -- 记录连接状态变化的运维日志
  1305. if config.aircloud_mtn_log_enabled then
  1306. if event == socket.LINK then
  1307. exmtn.log("info", "aircloud", "net_conn", "网络连接成功")
  1308. elseif event == socket.ON_LINE then
  1309. exmtn.log("info", "aircloud", "net_conn", "TCP连接成功", "host", config.host, "port", config.port)
  1310. elseif event == socket.CLOSED then
  1311. exmtn.log("info", "aircloud", "net_conn", "TCP连接断开", "param", param)
  1312. end
  1313. end
  1314. if param ~= 0 then
  1315. log.info("[excloud]socket", "连接断开")
  1316. is_connected = false
  1317. is_authenticated = false
  1318. if callback_func then
  1319. callback_func("disconnect", {})
  1320. end
  1321. -- 连接断开,释放资源
  1322. socket.release(connection)
  1323. connection = nil
  1324. -- 尝试重连
  1325. if config.auto_reconnect and is_open then
  1326. -- is_open = false
  1327. schedule_reconnect()
  1328. end
  1329. return
  1330. end
  1331. if event == socket.LINK then
  1332. -- 网络连接成功
  1333. log.info("[excloud]socket", "网络连接成功")
  1334. elseif event == socket.ON_LINE then
  1335. -- TCP连接成功
  1336. log.info("[excloud]socket", "TCP连接成功")
  1337. is_connected = true
  1338. -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
  1339. reconnect_count = 0
  1340. if callback_func then
  1341. callback_func("connect_result", { success = true })
  1342. end
  1343. -- 发送认证请求
  1344. send_auth_request()
  1345. elseif event == socket.EVENT then
  1346. -- 有数据到达
  1347. socket.rx(netc, rxbuff)
  1348. if rxbuff:used() > 0 then
  1349. local data = rxbuff:query()
  1350. log.info("[excloud]socket", "收到数据", #data, "字节", data:toHex())
  1351. -- 处理接收到的数据
  1352. parse_data(data)
  1353. end
  1354. -- -- 清空缓冲区
  1355. rxbuff:del()
  1356. elseif event == socket.TX_OK then
  1357. socket.wait(netc)
  1358. log.info("[excloud]socket", "发送完成")
  1359. elseif event == socket.CLOSED then
  1360. -- 连接错误或关闭
  1361. socket.release(connection)
  1362. connection = nil
  1363. log.info("[excloud]socket", "主动断开链接")
  1364. end
  1365. end
  1366. -- mqtt client的事件回调函数
  1367. local function mqtt_client_event_cbfunc(connected, event, data, payload, metas)
  1368. log.info("[excloud]mqtt_client_event_cbfunc", event, data, payload, json.encode(metas))
  1369. -- 取消连接超时定时器
  1370. if connect_timeout_timer then
  1371. sys.timerStop(connect_timeout_timer)
  1372. connect_timeout_timer = nil
  1373. end
  1374. -- 记录MQTT状态变化的运维日志
  1375. if config.aircloud_mtn_log_enabled then
  1376. if event == "conack" then
  1377. exmtn.log("info", "aircloud", "mqtt_conn", "MQTT连接成功", "host", config.host)
  1378. elseif event == "disconnect" then
  1379. exmtn.log("info", "aircloud", "mqtt_conn", "MQTT连接断开")
  1380. elseif event == "error" then
  1381. exmtn.log("info", "aircloud", "mqtt_error", "MQTT错误", "type", data, "code", payload)
  1382. end
  1383. end
  1384. -- mqtt连接成功
  1385. if event == "conack" then
  1386. is_connected = true
  1387. log.info("[excloud]MQTT connected")
  1388. -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
  1389. reconnect_count = 0
  1390. -- 订阅主题
  1391. local device_id_hex = string.toHex(device_id_binary)
  1392. local auth_topic = "/AirCloud/down/" .. device_id_hex .. "/auth"
  1393. local all_topic = "/AirCloud/down/" .. device_id_hex .. "/all"
  1394. log.info("[excloud]mqtt_client_event_cbfunc", "订阅主题", auth_topic, all_topic)
  1395. connection:subscribe(auth_topic, 0)
  1396. connection:subscribe(all_topic, 0)
  1397. if callback_func then
  1398. callback_func("connect_result", { success = true })
  1399. end
  1400. -- 发送认证请求
  1401. send_auth_request()
  1402. -- 订阅成功
  1403. elseif event == "suback" then
  1404. -- 取消订阅成功
  1405. elseif event == "unsuback" then
  1406. -- 接收到服务器下发的publish数据
  1407. -- data:string类型,表示topic
  1408. -- payload:string类型,表示payload
  1409. -- metas:table类型,数据内容如下
  1410. -- {
  1411. -- qos: number类型,取值范围0,1,2
  1412. -- retain:number类型,取值范围0,1
  1413. -- dup:number类型,取值范围0,1
  1414. -- message_id: number类型
  1415. -- }
  1416. elseif event == "recv" then
  1417. log.info("[excloud]接收到MQTT消息",
  1418. "主题:", data,
  1419. "数据长度:", #payload,
  1420. "QoS:", metas and metas.qos or "unknown",
  1421. "消息ID:", metas and metas.message_id or "unknown")
  1422. -- 对接收到的publish数据处理
  1423. parse_data(payload)
  1424. -- 发送成功publish数据
  1425. -- data:number类型,表示message id
  1426. elseif event == "sent" then
  1427. -- 服务器断开mqtt连接
  1428. elseif event == "disconnect" then
  1429. is_connected = false
  1430. is_authenticated = false
  1431. log.info("[excloud]MQTT disconnected")
  1432. if callback_func then
  1433. callback_func("disconnect", {})
  1434. end
  1435. -- 尝试重连
  1436. if config.auto_reconnect and is_open then
  1437. -- is_open = false
  1438. schedule_reconnect()
  1439. end
  1440. -- 收到服务器的心跳应答
  1441. elseif event == "pong" then
  1442. -- 严重异常,本地会主动断开连接
  1443. -- data:string类型,表示具体的异常,有以下几种:
  1444. -- "connect":tcp连接失败
  1445. -- "tx":数据发送失败
  1446. -- "conack":mqtt connect后,服务器应答CONNACK鉴权失败,失败码为payload(number类型)
  1447. -- "other":其他异常
  1448. elseif event == "error" then
  1449. is_connected = false
  1450. is_authenticated = false
  1451. local error_msg = "Unknown MQTT error"
  1452. if data == "connect" then
  1453. error_msg = "TCP connection failed"
  1454. -- 连接失败,应该考虑重新获取服务器信息
  1455. if reconnect_count >= config.max_reconnect and config.use_getip then
  1456. log.info("[excloud]MQTT连接多次失败,需要重新获取服务器信息")
  1457. config.current_conninfo = nil
  1458. end
  1459. elseif data == "tx" then
  1460. error_msg = "Data transmission failed"
  1461. elseif data == "conack" then
  1462. error_msg = "MQTT authentication failed with code: " .. tostring(payload)
  1463. else
  1464. error_msg = "Other MQTT error: " .. tostring(data)
  1465. end
  1466. log.info("[excloud]MQTT error: " .. error_msg)
  1467. if callback_func then
  1468. callback_func("disconnect", { error = error_msg })
  1469. end
  1470. -- 安全释放连接资源
  1471. if connection then
  1472. connection:disconnect()
  1473. connection:close()
  1474. connection = nil
  1475. end
  1476. -- 尝试重连
  1477. if config.auto_reconnect and is_open then
  1478. -- is_open = false
  1479. schedule_reconnect()
  1480. end
  1481. end
  1482. end
  1483. -- 设置配置参数
  1484. function excloud.setup(params)
  1485. if is_open then
  1486. return false, "excloud is already open"
  1487. end
  1488. -- 合并配置参数
  1489. for k, v in pairs(params) do
  1490. config[k] = v
  1491. end
  1492. -- 验证必要参数
  1493. if not config.auth_key then
  1494. return false, "auth_key is required"
  1495. end
  1496. if config.device_type == 1 then
  1497. config.device_id = mobile.imei()
  1498. log.info("[excloud]4G设备", "IMEI:", config.device_id, "MUID:", mobile.muid())
  1499. elseif config.device_type == 2 then
  1500. config.device_id = wlan.getMac(nil, true)
  1501. --以太网设备
  1502. elseif config.device_type == 4 then
  1503. config.device_id = netdrv.mac(socket.LWIP_ETH)
  1504. elseif config.device_type == 9 then
  1505. -- 虚拟设备:验证手机号和序列号
  1506. if not config.virtual_phone_number then
  1507. return false, "虚拟设备需要配置 virtual_phone_number"
  1508. end
  1509. -- 验证手机号格式(11位数字)
  1510. local phone_clean = config.virtual_phone_number:gsub("%D", "")
  1511. if #phone_clean ~= 11 then
  1512. return false, "虚拟手机号必须为11位数字"
  1513. end
  1514. -- 设置默认序列号(如果未提供)
  1515. if config.virtual_serial_num == nil then
  1516. config.virtual_serial_num = 0
  1517. end
  1518. -- 序列号范围检查(0-999)
  1519. config.virtual_serial_num = config.virtual_serial_num % 1000
  1520. -- 生成设备ID:手机号 + 3位序列号
  1521. local serial_str = string.format("%03d", config.virtual_serial_num)
  1522. config.device_id = phone_clean .. serial_str
  1523. log.info("虚拟设备配置", "手机号:", config.virtual_phone_number, "序列号:", serial_str, "设备ID:", config.device_id)
  1524. else
  1525. log.info("[excloud]未知设备类型", config.device_type)
  1526. config.device_id = "unknown"
  1527. end
  1528. -- 打包设备id
  1529. device_id_binary = packDeviceInfo(config.device_type, config.device_id)
  1530. -- 初始化运维日志模块
  1531. local mtn_ok, mtn_err = init_mtn_log()
  1532. if not mtn_ok then
  1533. log.warn("[excloud]运维日志初始化失败,但继续excloud初始化:", mtn_err)
  1534. end
  1535. log.info("[excloud]excloud.setup", "初始化成功", "设备ID:", config.device_id)
  1536. return true
  1537. end
  1538. -- 注册回调函数
  1539. function excloud.on(cbfunc)
  1540. if type(cbfunc) ~= "function" then
  1541. return false, "Callback must be a function"
  1542. end
  1543. callback_func = cbfunc
  1544. return true
  1545. end
  1546. -- 开启excloud服务
  1547. function excloud.open()
  1548. -- 如果之前连接异常断开,但状态未重置,先清理
  1549. if is_open and not is_connected then
  1550. log.warn("[excloud]检测到状态不一致,先清理残留状态")
  1551. excloud.close()
  1552. sys.wait(100) -- 短暂等待确保资源释放
  1553. end
  1554. -- 检查是否已打开
  1555. if is_open and is_connected then
  1556. return false, "excloud is already open and connected"
  1557. end
  1558. reconnect_count = 0
  1559. -- 判断是否初始化
  1560. if not device_id_binary then
  1561. return false, "excloud 没有初始化,请先调用setup"
  1562. end
  1563. -- 根据use_getip决定是否使用getip服务
  1564. if config.use_getip then
  1565. -- 使用getip服务发现
  1566. local getip_type
  1567. if config.transport == "tcp" then
  1568. getip_type = 3
  1569. elseif config.transport == "udp" then
  1570. getip_type = 4
  1571. elseif config.transport == "mqtt" then
  1572. getip_type = 5
  1573. else
  1574. return false, "不支持的传输协议: " .. config.transport
  1575. end
  1576. -- 获取服务器连接信息
  1577. if not config.current_conninfo or (config.transport ~= "mqtt" and not config.current_conninfo.ipv4) or
  1578. (config.transport == "mqtt" and not config.current_conninfo.ssl) then
  1579. log.info("[excloud]首次连接,获取服务器信息...")
  1580. local ok, result = excloud.getip_with_retry(getip_type)
  1581. if not ok then
  1582. return false, "获取服务器信息失败: " .. result
  1583. end
  1584. -- 更新连接配置
  1585. log.info("[excloud]服务器信息获取成功", "host:", config.host, "port:", config.port, "transport:", config.transport)
  1586. -- 保存文件上传信息
  1587. if result.imginfo then
  1588. config.current_imginfo = result.imginfo
  1589. end
  1590. if result.audinfo then
  1591. config.current_audinfo = result.audinfo
  1592. end
  1593. end
  1594. else
  1595. -- 不使用getip,直接使用用户配置的host和port
  1596. log.info("使用手动配置的服务器地址", config.host, config.port)
  1597. if not config.host or not config.port then
  1598. return false, "use_getip为false时,必须配置host和port"
  1599. end
  1600. end
  1601. -- 根据传输协议创建连接
  1602. if config.transport == "tcp" then
  1603. -- 创建接收缓冲区
  1604. rxbuff = zbuff.create(2048)
  1605. -- 创建TCP连接
  1606. log.info("[excloud]创建TCP连接")
  1607. connection = socket.create(nil, tcp_socket_callback)
  1608. if not connection then
  1609. return false, "Failed to create socket"
  1610. end
  1611. -- 准备SSL配置参数
  1612. local ssl_config = nil
  1613. if config.ssl then
  1614. if type(config.ssl) == "table" then
  1615. -- 使用详细的SSL配置
  1616. ssl_config = config.ssl
  1617. else
  1618. -- 简单的SSL启用
  1619. ssl_config = true
  1620. end
  1621. end
  1622. -- 配置socket参数
  1623. local config_success = socket.config(
  1624. connection,
  1625. config.local_port, -- 本地端口号
  1626. false, -- 是否是UDP,TCP连接为false
  1627. ssl_config and true or false, -- 是否是加密传输
  1628. config.keep_idle, -- keepalive idle时间
  1629. config.keep_interval, -- keepalive 探测间隔
  1630. config.keep_cnt, -- keepalive 探测次数
  1631. ssl_config and ssl_config.server_cert or nil, -- 服务器CA证书
  1632. ssl_config and ssl_config.client_cert or nil, -- 客户端证书
  1633. ssl_config and ssl_config.client_key or nil, -- 客户端私钥
  1634. ssl_config and ssl_config.client_password or nil -- 客户端私钥口令
  1635. )
  1636. if not config_success then
  1637. socket.release(connection)
  1638. connection = nil
  1639. return false, "Socket config failed"
  1640. end
  1641. socket.debug(connection, true)
  1642. -- 设置连接超时定时器
  1643. connect_timeout_timer = sys.timerStart(function()
  1644. if not is_connected then
  1645. log.error("TCP connection timeout")
  1646. if connection then
  1647. socket.close(connection)
  1648. socket.release(connection)
  1649. connection = nil
  1650. end
  1651. if callback_func then
  1652. callback_func("connect_result", { success = false, error = "Connection timeout" })
  1653. end
  1654. -- 尝试重连
  1655. if config.auto_reconnect and is_open then
  1656. -- is_open = false
  1657. schedule_reconnect()
  1658. end
  1659. end
  1660. end, config.timeout * 1000)
  1661. -- 连接到服务器
  1662. local ok, result = socket.connect(connection, config.host, config.port, config.mqtt_ipv6)
  1663. log.info("[excloud]TCP连接结果", ok, result)
  1664. if not ok then
  1665. --发生异常,强制close
  1666. socket.close(connection)
  1667. --释放资源
  1668. socket.release(connection)
  1669. connection = nil
  1670. if config.auto_reconnect then
  1671. is_open = false
  1672. schedule_reconnect()
  1673. end
  1674. return false, result
  1675. end
  1676. elseif config.transport == "mqtt" then
  1677. -- 准备MQTT SSL配置 - MQTT连接默认使用SSL加密
  1678. local ssl_config = true -- 最简单的SSL加密,不验证服务器证书
  1679. -- 如果有详细的SSL配置,使用详细配置
  1680. if config.ssl and type(config.ssl) == "table" then
  1681. ssl_config = config.ssl
  1682. end
  1683. -- 准备MQTT扩展参数
  1684. local mqtt_opts = {
  1685. rxSize = config.mqtt_rx_size or 32 * 1024, -- MQTT接收缓冲区大小,默认32K
  1686. conn_timeout = config.mqtt_conn_timeout or 30, -- MQTT连接超时时间,默认30秒
  1687. ipv6 = config.mqtt_ipv6 or false -- 是否使用IPv6连接,默认false
  1688. }
  1689. -- 创建MQTT客户端
  1690. connection = mqtt.create(nil, config.host, config.port, ssl_config, mqtt_opts)
  1691. if not connection then
  1692. return false, "Failed to create MQTT client"
  1693. end
  1694. -- 开启调试信息(可选)
  1695. if config.debug then
  1696. connection:debug(true)
  1697. end
  1698. -- 设置真实的MQTT认证信息
  1699. local client_id, username, password
  1700. if config.device_type == 1 then -- 4G设备
  1701. client_id = mobile.imei()
  1702. username = mobile.imei()
  1703. password = mobile.muid()
  1704. -- elseif config.device_type == 2 then -- WIFI设备
  1705. -- client_id = wlan.getMac(nil, true)
  1706. -- username = wlan.getMac(nil, true)
  1707. -- password = mobile.muid():toHex()
  1708. -- elseif config.device_type == 4 then -- 以太网设备
  1709. -- client_id = netdrv.mac(socket.LWIP_ETH)
  1710. -- username = netdrv.mac(socket.LWIP_ETH)
  1711. -- password = mobile.muid():toHex()
  1712. -- elseif config.device_type == 9 then -- 虚拟设备
  1713. -- -- 虚拟设备使用配置的设备ID
  1714. -- client_id = config.device_id
  1715. -- username = config.device_id
  1716. -- password = config.auth_key or config.device_id
  1717. else
  1718. return false, "MQTT connect failed, device_type not supported"
  1719. end
  1720. log.info("[excloud]MQTT认证信息",
  1721. "client_id:", client_id,
  1722. "username:", username,
  1723. "password:", password)
  1724. -- 设置认证信息(使用真实的设备信息,而不是getip返回的提示)
  1725. connection:auth(client_id, username, password, config.clean_session)
  1726. -- 设置保持连接间隔
  1727. connection:keepalive(config.keepalive or 240) -- 默认240秒
  1728. -- 设置遗嘱消息(如果需要)
  1729. if config.will_topic and config.will_payload then
  1730. local will_result = connection:will(
  1731. config.will_topic,
  1732. config.will_payload,
  1733. config.will_qos or 0,
  1734. config.will_retain or 0
  1735. )
  1736. if not will_result then
  1737. log.warn("[excloud]设置遗嘱消息失败")
  1738. end
  1739. end
  1740. -- 设置自动重连
  1741. if config.auto_reconnect then
  1742. connection:autoreconn(true, (config.reconnect_interval or 10) * 1000) -- 转换为毫秒
  1743. end
  1744. -- 注册事件回调
  1745. connection:on(mqtt_client_event_cbfunc)
  1746. -- 设置连接超时定时器
  1747. connect_timeout_timer = sys.timerStart(function()
  1748. if not is_connected then
  1749. log.error("MQTT connection timeout")
  1750. if connection then
  1751. connection:disconnect()
  1752. connection:close()
  1753. connection = nil
  1754. end
  1755. if callback_func then
  1756. callback_func("connect_result", { success = false, error = "Connection timeout" })
  1757. end
  1758. -- 尝试重连
  1759. if config.auto_reconnect and is_open then
  1760. -- is_open = false
  1761. schedule_reconnect()
  1762. end
  1763. end
  1764. end, config.timeout * 1000)
  1765. -- 连接到服务器
  1766. local ok = connection:connect()
  1767. if not ok then
  1768. --连接失败,释放资源
  1769. connection:close()
  1770. connection = nil
  1771. -- 发起连接失败,尝试重连
  1772. if config.auto_reconnect then
  1773. is_open = false
  1774. schedule_reconnect()
  1775. end
  1776. return false, "MQTT connect failed"
  1777. end
  1778. else
  1779. return false, "Unsupported transport: " .. config.transport
  1780. end
  1781. is_open = true
  1782. -- 记录服务启动日志
  1783. if config.aircloud_mtn_log_enabled then
  1784. exmtn.log("info", "aircloud", "system", "excloud服务启动", "transport", config.transport, "host", config.host, "port",
  1785. config.port)
  1786. end
  1787. log.info("[excloud]excloud service started")
  1788. return true
  1789. end
  1790. -- 发送数据
  1791. -- 发送消息到云端
  1792. -- @param data table 待发送的数据,每个元素是一个包含 field_meaning、data_type 和 value 的表
  1793. -- @param need_reply boolean 是否需要服务器回复,默认为 false
  1794. -- @param is_auth_msg boolean 是否是鉴权消息,默认为 false
  1795. function excloud.send(data, need_reply, is_auth_msg)
  1796. if not is_open then
  1797. return false, "excloud服务未开启"
  1798. end
  1799. if not is_connected then
  1800. return false, "未连接到服务器"
  1801. end
  1802. -- if not is_authenticated and not is_auth_msg then
  1803. -- return false, "设备未认证"
  1804. -- end
  1805. -- 检查参数是否为table
  1806. if type(data) ~= "table" then
  1807. return false, "data must be table"
  1808. end
  1809. if need_reply == nil then
  1810. need_reply = false
  1811. end
  1812. if is_auth_msg == nil then
  1813. is_auth_msg = false
  1814. end
  1815. -- 检查服务是否开启
  1816. if not is_open then
  1817. if callback_func then
  1818. callback_func("send_result", {
  1819. success = false,
  1820. error_msg = "excloud not open"
  1821. })
  1822. end
  1823. return false, "excloud not open"
  1824. end
  1825. -- 检查是否已连接
  1826. if not is_connected then
  1827. if callback_func then
  1828. callback_func("send_result", {
  1829. success = false,
  1830. error_msg = "excloud not connected"
  1831. })
  1832. end
  1833. return false, "excloud not connected"
  1834. end
  1835. -- 保存当前序列号用于回调
  1836. local current_sequence = sequence_num
  1837. -- 构建消息体
  1838. local message_body = ""
  1839. local parts = {}
  1840. for _, item in ipairs(data) do
  1841. log.info("[excloud]构建发送数据", item.field_meaning, item.data_type, item.value, message_body)
  1842. local success, tlv = build_tlv(item.field_meaning, item.data_type, item.value)
  1843. if not success then
  1844. return false, "excloud.send data is failed"
  1845. end
  1846. table.insert(parts, tlv)
  1847. -- message_body = message_body .. tlv
  1848. end
  1849. if #parts > 0 then
  1850. message_body = table.concat(parts)
  1851. parts = {}
  1852. else
  1853. log.warn("[excloud]没有有效的TLV数据可发送")
  1854. -- return false, "No valid TLV data to send"
  1855. end
  1856. -- 检查消息长度
  1857. local udp_auth_key = config.udp_auth_key and true or false
  1858. local total_length = #message_body + (udp_auth_key and 64 or 0)
  1859. log.info("[excloud]tlv发送数据长度4", total_length)
  1860. -- 构建消息头
  1861. local is_udp_transport = (config.transport == "udp")
  1862. local header = build_header(need_reply, is_udp_transport, total_length)
  1863. -- -- 添加鉴权key(如果是UDP的话)
  1864. local auth_key_part = ""
  1865. if config.transport == "udp" and udp_auth_key then
  1866. auth_key_part = config.udp_auth_key
  1867. if #auth_key_part < 64 then
  1868. auth_key_part = auth_key_part .. string.rep("\0", 64 - #auth_key_part)
  1869. elseif #auth_key_part > 64 then
  1870. auth_key_part = auth_key_part:sub(1, 64)
  1871. end
  1872. end
  1873. local full_message
  1874. -- 发送完整消息
  1875. if config.transport == "udp" then
  1876. full_message = header .. auth_key_part .. message_body
  1877. else
  1878. full_message = header .. message_body
  1879. end
  1880. log.info("[excloud]发送消息长度", #header, #message_body, #full_message, full_message:toHex())
  1881. local success, err_msg
  1882. if config.transport == "tcp" then
  1883. if not connection then
  1884. err_msg = "TCP connection not available"
  1885. success = false
  1886. else
  1887. success, err_msg = socket.tx(connection, full_message)
  1888. end
  1889. elseif config.transport == "mqtt" then
  1890. -- 根据是否为鉴权消息选择不同的topic
  1891. local topic
  1892. local device_id_hex = string.toHex(device_id_binary)
  1893. if is_auth_msg then
  1894. topic = "/AirCloud/up/" .. device_id_hex .. "/auth"
  1895. else
  1896. topic = "/AirCloud/up/" .. device_id_hex .. "/all"
  1897. end
  1898. log.info("[excloud]发布主题", topic, #full_message, full_message:toHex())
  1899. local message_id = connection:publish(topic, full_message, config.qos, config.retain)
  1900. if message_id then
  1901. success = true
  1902. if config.qos and config.qos > 0 then
  1903. log.info("[excloud]MQTT消息发布成功", "消息ID:", message_id)
  1904. else
  1905. log.info("[excloud]MQTT消息发布成功")
  1906. end
  1907. else
  1908. success = false
  1909. err_msg = "MQTT publish failed"
  1910. end
  1911. end
  1912. -- 通过回调返回发送结果
  1913. if callback_func then
  1914. callback_func("send_result", {
  1915. success = success,
  1916. error_msg = success and "Send successful" or err_msg,
  1917. sequence_num = current_sequence
  1918. })
  1919. end
  1920. collectgarbage("collect")
  1921. if success then
  1922. log.info("[excloud]数据发送成功", #full_message, "字节")
  1923. return true
  1924. else
  1925. log.error("数据发送失败", err_msg)
  1926. return false, err_msg
  1927. end
  1928. end
  1929. -- 关闭excloud服务
  1930. function excloud.close()
  1931. if not is_open then
  1932. return false, "excloud not open"
  1933. end
  1934. -- 停止所有定时器
  1935. if reconnect_timer then
  1936. sys.timerStop(reconnect_timer)
  1937. reconnect_timer = nil
  1938. end
  1939. if connect_timeout_timer then
  1940. sys.timerStop(connect_timeout_timer)
  1941. connect_timeout_timer = nil
  1942. end
  1943. -- 停止心跳
  1944. excloud.stop_heartbeat()
  1945. -- 关闭连接
  1946. if connection then
  1947. if config.transport == "tcp" then
  1948. socket.close(connection)
  1949. socket.release(connection)
  1950. elseif config.transport == "mqtt" then
  1951. -- 断开连接并释放资源
  1952. connection:disconnect()
  1953. connection:close()
  1954. end
  1955. connection = nil
  1956. end
  1957. -- 释放缓冲区
  1958. if rxbuff then
  1959. rxbuff = nil
  1960. end
  1961. -- 清空队列
  1962. pending_messages = {}
  1963. callback_func = nil
  1964. -- 记录服务关闭日志
  1965. if config.aircloud_mtn_log_enabled then
  1966. exmtn.log("info", "aircloud", "system", "excloud服务关闭")
  1967. end
  1968. -- 重置状态
  1969. is_open = false
  1970. is_connected = false
  1971. is_authenticated = false
  1972. pending_messages = {}
  1973. rxbuff = nil
  1974. reconnect_count = 0
  1975. is_heartbeat_running = false
  1976. collectgarbage("collect")
  1977. log.info("[excloud]excloud service stopped")
  1978. return true
  1979. end
  1980. -- 获取当前状态
  1981. function excloud.status()
  1982. return {
  1983. is_open = is_open,
  1984. is_connected = is_connected,
  1985. is_authenticated = is_authenticated,
  1986. sequence_num = sequence_num,
  1987. reconnect_count = reconnect_count,
  1988. pending_messages = #pending_messages,
  1989. }
  1990. end
  1991. -- 发送心跳消息
  1992. -- @param custom_data table 可选参数,自定义心跳内容
  1993. -- @param need_reply boolean 是否需要服务器回复,默认为false
  1994. -- @return boolean 是否发送成功
  1995. -- @return string 错误信息(如果失败)
  1996. function excloud.heartbeat(custom_data, need_reply)
  1997. -- 如果心跳数据未提供,则使用默认的心跳数据(空表)
  1998. local data = custom_data or heartbeat_data
  1999. -- 设置默认不需要回复
  2000. if need_reply == nil then
  2001. need_reply = false
  2002. end
  2003. -- 调用send函数发送心跳数据
  2004. return excloud.send(data, need_reply, false)
  2005. end
  2006. -- 启动自动心跳
  2007. -- @param interval number 心跳间隔(秒),默认300秒(5分钟)
  2008. -- @param custom_data table 自定义心跳内容,默认空表
  2009. -- @return boolean 是否启动成功
  2010. function excloud.start_heartbeat(interval, custom_data)
  2011. -- 停止现有的心跳定时器
  2012. if is_heartbeat_running then
  2013. excloud.stop_heartbeat()
  2014. end
  2015. -- 设置心跳间隔,默认5分钟
  2016. heartbeat_interval = interval or 300
  2017. -- 设置心跳数据
  2018. heartbeat_data = custom_data or {}
  2019. -- 创建并启动心跳定时器
  2020. heartbeat_timer = sys.timerLoopStart(function()
  2021. if is_open and is_connected then
  2022. local ok, err_msg = excloud.heartbeat()
  2023. if not ok then
  2024. log.info("[excloud]excloud", "心跳发送失败: " .. err_msg)
  2025. else
  2026. log.info("[excloud]excloud", "心跳发送成功")
  2027. end
  2028. end
  2029. end, heartbeat_interval * 1000) -- 转换为毫秒
  2030. is_heartbeat_running = true
  2031. log.info("[excloud]excloud", "自动心跳已启动,间隔 " .. heartbeat_interval .. " 秒")
  2032. return true
  2033. end
  2034. -- 停止自动心跳
  2035. -- @return boolean 是否停止成功
  2036. function excloud.stop_heartbeat()
  2037. if heartbeat_timer then
  2038. sys.timerStop(heartbeat_timer)
  2039. heartbeat_timer = nil
  2040. is_heartbeat_running = false
  2041. log.info("[excloud]excloud", "自动心跳已停止")
  2042. return true
  2043. end
  2044. return false
  2045. end
  2046. -- 获取当前服务器信息
  2047. function excloud.get_server_info()
  2048. return {
  2049. conninfo = config.current_conninfo,
  2050. imginfo = config.current_imginfo,
  2051. audinfo = config.current_audinfo,
  2052. mtninfo = config.current_mtninfo -- 新增:运维日志上传信息
  2053. }
  2054. end
  2055. -- 强制刷新服务器信息
  2056. -- function excloud.refresh_server_info()
  2057. -- config.current_conninfo = nil
  2058. -- config.current_imginfo = nil
  2059. -- config.current_audinfo = nil
  2060. -- return true
  2061. -- end
  2062. -- 导出常量
  2063. excloud.DATA_TYPES = DATA_TYPES
  2064. excloud.FIELD_MEANINGS = FIELD_MEANINGS
  2065. excloud.MTN_LOG_STATUS = MTN_LOG_STATUS
  2066. excloud.MTN_LOG_CACHE_WRITE = exmtn.CACHE_WRITE
  2067. excloud.MTN_LOG_ADD_WRITE = exmtn.ADD_WRITE
  2068. return excloud