|
|
@@ -0,0 +1,1167 @@
|
|
|
+--[[
|
|
|
+@summary excloud扩展库
|
|
|
+@version 1.0
|
|
|
+@date 2025.09.22
|
|
|
+@author 孟伟
|
|
|
+@usage
|
|
|
+-- 应用场景
|
|
|
+该扩展库适用于各种物联网设备(如4G/WiFi/以太网设备)与云端服务器进行数据交互的场景。
|
|
|
+可用于设备状态上报、数据采集、远程控制等物联网应用。
|
|
|
+
|
|
|
+实现的功能:
|
|
|
+1. 支持多种设备类型(4G/WiFi/以太网)的接入认证
|
|
|
+2. 提供TCP和MQTT两种传输协议选择
|
|
|
+3. 实现设备与云端的双向通信(数据上报和命令下发)
|
|
|
+4. 支持数据的TLV格式编解码
|
|
|
+5. 提供自动重连机制,保证连接稳定性
|
|
|
+6. 支持不同数据类型(整数、浮点数、布尔值、字符串、二进制等)的传输
|
|
|
+
|
|
|
+-- 用法实例
|
|
|
+本扩展库对外提供了以下6个接口:
|
|
|
+1. excloud.setup(params) - 设置配置参数
|
|
|
+2. excloud.on(cbfunc) - 注册回调函数
|
|
|
+3. excloud.open() - 开启excloud服务
|
|
|
+4. excloud.send(data, need_reply, is_auth_msg) - 发送数据
|
|
|
+5. excloud.close() - 关闭excloud服务
|
|
|
+6. excloud.status() - 获取当前状态
|
|
|
+
|
|
|
+-- 示例:
|
|
|
+-- 导入excloud库
|
|
|
+local excloud = require("excloud")
|
|
|
+
|
|
|
+-- 注册回调函数
|
|
|
+excloud.on(function(event, data)
|
|
|
+ log.info("用户回调函数", event, json.encode(data))
|
|
|
+
|
|
|
+ if event == "connect_result" then
|
|
|
+ if data.success then
|
|
|
+ log.info("连接成功")
|
|
|
+ else
|
|
|
+ log.info("连接失败: " .. (data.error or "未知错误"))
|
|
|
+ end
|
|
|
+ elseif event == "auth_result" then
|
|
|
+ if data.success then
|
|
|
+ log.info("认证成功")
|
|
|
+ else
|
|
|
+ log.info("认证失败: " .. data.message)
|
|
|
+ end
|
|
|
+ elseif event == "message" then
|
|
|
+ log.info("收到消息, 流水号: " .. data.header.sequence_num)
|
|
|
+
|
|
|
+ -- 处理服务器下发的消息
|
|
|
+ for _, tlv in ipairs(data.tlvs) do
|
|
|
+ if tlv.field == excloud.FIELD_MEANINGS.CONTROL_COMMAND then
|
|
|
+ log.info("收到控制命令: " .. tostring(tlv.value))
|
|
|
+
|
|
|
+ -- 处理控制命令并发送响应
|
|
|
+ local response_ok, err_msg = excloud.send({
|
|
|
+ {
|
|
|
+ field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
|
|
|
+ data_type = excloud.DATA_TYPES.ASCII,
|
|
|
+ value = "命令执行成功"
|
|
|
+ }
|
|
|
+ }, false)
|
|
|
+
|
|
|
+ if not response_ok then
|
|
|
+ log.info("发送控制响应失败: " .. err_msg)
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ elseif event == "disconnect" then
|
|
|
+ log.warn("与服务器断开连接")
|
|
|
+ elseif event == "reconnect_failed" then
|
|
|
+ log.info("重连失败,已尝试 " .. data.count .. " 次")
|
|
|
+ elseif event == "send_result" then
|
|
|
+ if data.success then
|
|
|
+ log.info("发送成功,流水号: " .. data.sequence_num)
|
|
|
+ else
|
|
|
+ log.info("发送失败: " .. data.error_msg)
|
|
|
+ end
|
|
|
+ end
|
|
|
+end)
|
|
|
+
|
|
|
+
|
|
|
+sys.taskInit(function()
|
|
|
+ -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
|
|
+ while not socket.adapter(socket.dft()) do
|
|
|
+ log.warn("tcp_client_main_task_func", "wait IP_READY", socket.dft())
|
|
|
+ -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
|
|
+ -- 或者等待1秒超时退出阻塞等待状态;
|
|
|
+ -- 注意:此处的1000毫秒超时不要修改的更长;
|
|
|
+ -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
|
|
+ -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
|
|
+ -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
|
|
+ sys.waitUntil("IP_READY", 1000)
|
|
|
+ end
|
|
|
+ sys.wait(1000)
|
|
|
+ -- 配置excloud参数
|
|
|
+ -- local ok, err_msg = excloud.setup({
|
|
|
+ -- -- device_id = "862419074073247", -- 设备ID (IMEI前14位)
|
|
|
+ -- device_type = 1, -- 设备类型: 4G
|
|
|
+ -- host = "112.125.89.8", -- 服务器地址
|
|
|
+ -- port = 33316, -- 服务器端口
|
|
|
+ -- auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi", -- 鉴权密钥
|
|
|
+ -- transport = "tcp", -- 使用TCP传输
|
|
|
+ -- auto_reconnect = true, -- 自动重连
|
|
|
+ -- reconnect_interval = 10, -- 重连间隔(秒)
|
|
|
+ -- max_reconnect = 5, -- 最大重连次数
|
|
|
+ -- timeout = 30, -- 超时时间(秒)
|
|
|
+ -- })
|
|
|
+
|
|
|
+ if not ok then
|
|
|
+ log.info("初始化失败: " .. err_msg)
|
|
|
+ return
|
|
|
+ end
|
|
|
+ log.info("excloud初始化成功")
|
|
|
+ -- 开启excloud服务
|
|
|
+ local ok, err_msg = excloud.open()
|
|
|
+ if not ok then
|
|
|
+ log.info("开启excloud服务失败: " .. err_msg)
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ log.info("excloud服务已开启")
|
|
|
+ -- 在主循环中定期上报数据
|
|
|
+ while true do
|
|
|
+ -- 每30秒上报一次数据
|
|
|
+ sys.wait(30000)
|
|
|
+ local ok, err_msg = excloud.send({
|
|
|
+ {
|
|
|
+ field_meaning = excloud.FIELD_MEANINGS.LOCATION_METHOD,
|
|
|
+ data_type = excloud.DATA_TYPES.INTEGER,
|
|
|
+ value = 22 -- 随机温度值
|
|
|
+ },
|
|
|
+ {
|
|
|
+ field_meaning = excloud.FIELD_MEANINGS.HUMIDITY,
|
|
|
+ data_type = excloud.DATA_TYPES.FLOAT,
|
|
|
+ value = 33.2543 -- 随机湿度值
|
|
|
+ }
|
|
|
+ }, false) -- 不需要服务器回复
|
|
|
+
|
|
|
+ if not ok then
|
|
|
+ log.info("发送数据失败: " .. err_msg)
|
|
|
+ else
|
|
|
+ log.info("数据发送成功")
|
|
|
+ end
|
|
|
+ end
|
|
|
+end)
|
|
|
+
|
|
|
+]]
|
|
|
+local excloud = {}
|
|
|
+
|
|
|
+-- 内部状态变量
|
|
|
+local config = {
|
|
|
+ device_type = 1, -- 默认设备类型: 4G
|
|
|
+ device_id = "", -- 设备ID
|
|
|
+ protocol_version = 1, -- 协议版本
|
|
|
+ transport = "tcp", -- 传输协议: tcp/mqtt
|
|
|
+ host = "cloud.luatos.com", -- 服务器地址
|
|
|
+ port = 8900, -- 服务器端口
|
|
|
+ auth_key = nil, -- 用户鉴权密钥
|
|
|
+ keepalive = 300, -- mqtt心跳
|
|
|
+ auto_reconnect = true, -- 是否自动重连
|
|
|
+ reconnect_interval = 10, -- 重连间隔(秒)
|
|
|
+ max_reconnect = 3, -- 最大重连次数
|
|
|
+ timeout = 30, -- 连接超时时间(秒)
|
|
|
+ qos = 0, -- MQTT QoS等级
|
|
|
+ -- retain = false, -- MQTT retain标志
|
|
|
+ -- clean_session = true, -- MQTT clean session标志
|
|
|
+ ssl = false, -- 是否使用SSL
|
|
|
+ username = nil, -- MQTT用户名
|
|
|
+ password = nil, -- MQTT密码
|
|
|
+ udp_auth_key = nil, -- UDP鉴权密钥
|
|
|
+}
|
|
|
+
|
|
|
+local callback_func = nil -- 回调函数
|
|
|
+local is_open = false -- 服务是否开启
|
|
|
+local is_connected = false -- 是否已连接
|
|
|
+local is_authenticated = false -- 是否已鉴权
|
|
|
+local sequence_num = 0 -- 流水号
|
|
|
+local connection = nil -- 连接对象
|
|
|
+local device_id_binary = nil -- 二进制格式的设备ID
|
|
|
+local reconnect_timer = nil -- 重连定时器
|
|
|
+local reconnect_count = 0 -- 重连次数
|
|
|
+local pending_messages = {} -- 待发送消息队列
|
|
|
+local rxbuff = nil -- 接收缓冲区
|
|
|
+local connect_timeout_timer = nil -- 连接超时定时器
|
|
|
+
|
|
|
+-- 数据类型定义
|
|
|
+local DATA_TYPES = {
|
|
|
+ INTEGER = 0x0, -- 整数
|
|
|
+ FLOAT = 0x1, -- 浮点数
|
|
|
+ BOOLEAN = 0x2, -- 布尔值
|
|
|
+ ASCII = 0x3, -- ASCII字符串
|
|
|
+ BINARY = 0x4, -- 二进制数据
|
|
|
+ UNICODE = 0x5 -- Unicode字符串
|
|
|
+}
|
|
|
+
|
|
|
+-- 字段含义定义
|
|
|
+local FIELD_MEANINGS = {
|
|
|
+ -- 控制信令类型 (16-255)
|
|
|
+ AUTH_REQUEST = 16, -- 鉴权请求
|
|
|
+ AUTH_RESPONSE = 17, -- 鉴权回复
|
|
|
+ REPORT_RESPONSE = 18, -- 上报回应
|
|
|
+ CONTROL_COMMAND = 19, -- 控制命令
|
|
|
+ CONTROL_RESPONSE = 20, -- 控制回应
|
|
|
+ IRTU_DOWN = 21, -- iRTU下行命令
|
|
|
+ IRTU_UP = 22, -- iRTU上行回复
|
|
|
+
|
|
|
+ -- 传感类 (256-511)
|
|
|
+ TEMPERATURE = 256, -- 温度
|
|
|
+ HUMIDITY = 257, -- 湿度
|
|
|
+ PARTICULATE = 258, -- 颗粒数
|
|
|
+ ACIDITY = 259, -- 酸度
|
|
|
+ ALKALINITY = 260, -- 碱度
|
|
|
+ ALTITUDE = 261, -- 海拔
|
|
|
+ WATER_LEVEL = 262, -- 水位
|
|
|
+ ENV_TEMPERATURE = 263, -- 环境温度
|
|
|
+ POWER_METERING = 264, -- 电量计量
|
|
|
+
|
|
|
+ -- 资产管理类 (512-767)
|
|
|
+ GNSS_LONGITUDE = 512, -- GNSS经度
|
|
|
+ GNSS_LATITUDE = 513, -- GNSS纬度
|
|
|
+ SPEED = 514, -- 行驶速度
|
|
|
+ GNSS_CN = 515, -- 最强的4颗GNSS卫星的CN
|
|
|
+ SATELLITES_TOTAL = 516, -- 搜到的所有卫星数
|
|
|
+ SATELLITES_VISIBLE = 517, -- 可见卫星数
|
|
|
+ HEADING = 518, -- 航向角
|
|
|
+ LOCATION_METHOD = 519, -- 基站定位/GNSS定位标识
|
|
|
+ GNSS_INFO = 520, -- GNSS芯片型号和固件版本号
|
|
|
+ DIRECTION = 521, -- 方向
|
|
|
+
|
|
|
+ -- 设备参数类 (768-1023)
|
|
|
+ HEIGHT = 768, -- 高度
|
|
|
+ WIDTH = 769, -- 宽度
|
|
|
+ ROTATION_SPEED = 770, -- 转速
|
|
|
+ BATTERY_LEVEL = 771, -- 电量(mV)
|
|
|
+ SERVING_CELL = 772, -- 驻留小区
|
|
|
+ CELL_INFO = 773, -- 驻留小区和邻区
|
|
|
+ COMPONENT_MODEL = 774, -- 元器件型号
|
|
|
+ GPIO_LEVEL = 775, -- GPIO高低电平
|
|
|
+ BOOT_REASON = 776, -- 开机原因
|
|
|
+ BOOT_COUNT = 777, -- 开机次数
|
|
|
+ SLEEP_MODE = 778, -- 休眠模式
|
|
|
+ WAKE_INTERVAL = 779, -- 定时唤醒间隔
|
|
|
+ NETWORK_IP_TYPE = 780, -- 设备入网的IP类型
|
|
|
+ NETWORK_TYPE = 781, -- 当前联网方式
|
|
|
+
|
|
|
+ -- 软件数据类 (1024-1279)
|
|
|
+ LUA_CORE_ERROR = 1024, -- Lua核心库错误上报
|
|
|
+ LUA_EXT_ERROR = 1025, -- Lua扩展卡错误上报
|
|
|
+ LUA_APP_ERROR = 1026, -- Lua业务错误上报
|
|
|
+ FIRMWARE_VERSION = 1027, -- 固件版本号
|
|
|
+ SMS_FORWARD = 1028, -- SMS转发
|
|
|
+ CALL_FORWARD = 1029, -- 来电转发
|
|
|
+
|
|
|
+ -- 设备无关数据类 (1280-1535)
|
|
|
+ TIMESTAMP = 1280, -- 时间
|
|
|
+ RANDOM_DATA = 1281 -- 无意义数据
|
|
|
+}
|
|
|
+
|
|
|
+-- 将数字转换为大端字节序列
|
|
|
+local function to_big_endian(num, bytes)
|
|
|
+ local result = {}
|
|
|
+ for i = bytes, 1, -1 do
|
|
|
+ result[i] = string.char(num % 256)
|
|
|
+ num = math.floor(num / 256)
|
|
|
+ end
|
|
|
+ return table.concat(result)
|
|
|
+end
|
|
|
+
|
|
|
+-- 从大端字节序列转换为数字
|
|
|
+local function from_big_endian(data, start, length)
|
|
|
+ local value = 0
|
|
|
+ for i = start, start + length - 1 do
|
|
|
+ value = value * 256 + data:byte(i)
|
|
|
+ end
|
|
|
+ log.info("from_big_endian", value)
|
|
|
+ return value
|
|
|
+end
|
|
|
+
|
|
|
+-- 将设备ID进行编码
|
|
|
+function packDeviceInfo(deviceType, deviceId)
|
|
|
+ -- 验证设备类型
|
|
|
+ if deviceType ~= 1 and deviceType ~= 2 then
|
|
|
+ log.info("设备类型错误: 4G设备应为1, WIFI设备应为2")
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 设备类型字节
|
|
|
+ local result = { string.char(deviceType) }
|
|
|
+
|
|
|
+ -- 清理设备ID(移除非数字和字母字符,并转换为大写)
|
|
|
+ local cleanId = deviceId:gsub("[^%w]", ""):upper()
|
|
|
+
|
|
|
+ -- 处理不同类型的设备ID
|
|
|
+ if deviceType == 1 then
|
|
|
+ -- 4G设备 - IMEI处理
|
|
|
+ -- 只取前14位数字,忽略第15位
|
|
|
+ cleanId = cleanId:gsub("%D", ""):sub(1, 14)
|
|
|
+
|
|
|
+ -- 确保长度为14位(不足时前面补0)
|
|
|
+ if #cleanId < 14 then
|
|
|
+ cleanId = string.rep("0", 14 - #cleanId) .. cleanId
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 转换为BCD格式的字节
|
|
|
+ for i = 1, 14, 2 do
|
|
|
+ local byte = (tonumber(cleanId:sub(i, i)) * 16) + tonumber(cleanId:sub(i + 1, i + 1))
|
|
|
+ table.insert(result, string.char(byte))
|
|
|
+ end
|
|
|
+ elseif deviceType == 2 then
|
|
|
+ -- WIFI设备 - MAC地址处理
|
|
|
+ -- 移除非十六进制字符
|
|
|
+ cleanId = cleanId:gsub("[^0-9A-Fa-f]", "")
|
|
|
+
|
|
|
+ -- 确保长度为12个十六进制字符(6字节)
|
|
|
+ if #cleanId < 12 then
|
|
|
+ cleanId = string.rep("0", 12 - #cleanId) .. cleanId
|
|
|
+ else
|
|
|
+ cleanId = cleanId:sub(1, 12)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 转换为字节
|
|
|
+ local bytes = {}
|
|
|
+ for i = 1, 12, 2 do
|
|
|
+ local byteStr = cleanId:sub(i, i + 1)
|
|
|
+ table.insert(bytes, string.char(tonumber(byteStr, 16)))
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 确保有7个字节(不足时前面补0)
|
|
|
+ while #bytes < 7 do
|
|
|
+ table.insert(bytes, 1, string.char(0))
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 添加到结果中
|
|
|
+ for _, byte in ipairs(bytes) do
|
|
|
+ table.insert(result, byte)
|
|
|
+ end
|
|
|
+ else
|
|
|
+ log.info("未知设备类型 ")
|
|
|
+ return deviceId
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 返回8字节的二进制数据
|
|
|
+ return table.concat(result)
|
|
|
+end
|
|
|
+
|
|
|
+-- 编码数据值
|
|
|
+local function encode_value(data_type, value)
|
|
|
+ if data_type == DATA_TYPES.INTEGER then
|
|
|
+ if type(value) == "number" then
|
|
|
+ return to_big_endian(math.floor(value), 4)
|
|
|
+ else
|
|
|
+ log.info("Integer value expected")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ elseif data_type == DATA_TYPES.FLOAT then
|
|
|
+ if type(value) == "number" then
|
|
|
+ -- 简化处理:将浮点数转换为整数,乘以1000以保留三位小数
|
|
|
+ -- 然后调用to_big_endian函数将其转换为4字节的大端字节序列
|
|
|
+ return to_big_endian(math.floor(value * 1000), 4)
|
|
|
+ else
|
|
|
+ log.info("Number value expected")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ elseif data_type == DATA_TYPES.BOOLEAN then
|
|
|
+ if type(value) == "boolean" then
|
|
|
+ return value and "\1" or "\0"
|
|
|
+ else
|
|
|
+ log.info("Boolean value expected")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ elseif data_type == DATA_TYPES.ASCII then
|
|
|
+ if type(value) == "string" then
|
|
|
+ return value
|
|
|
+ else
|
|
|
+ log.info("String value expected")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ elseif data_type == DATA_TYPES.BINARY then
|
|
|
+ if type(value) == "string" then
|
|
|
+ return value
|
|
|
+ else
|
|
|
+ log.info("String value expected")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ elseif data_type == DATA_TYPES.UNICODE then
|
|
|
+ if type(value) == "string" then
|
|
|
+ -- 简化处理:直接使用字符串(实际应转换为UTF-16)
|
|
|
+ return value
|
|
|
+ else
|
|
|
+ log.info("String value expected")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ else
|
|
|
+ log.info("Unsupported data type: " .. data_type)
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- 解码数据值
|
|
|
+local function decode_value(data_type, value)
|
|
|
+ if data_type == DATA_TYPES.INTEGER then
|
|
|
+ return from_big_endian(value, 1, #value)
|
|
|
+ elseif data_type == DATA_TYPES.FLOAT then
|
|
|
+ -- 简化处理:将整数转换为浮点数(实际应使用IEEE 754格式)
|
|
|
+ return from_big_endian(value, 1, #value) / 1000
|
|
|
+ elseif data_type == DATA_TYPES.BOOLEAN then
|
|
|
+ return value:byte(1) ~= 0
|
|
|
+ elseif data_type == DATA_TYPES.ASCII then
|
|
|
+ return value
|
|
|
+ elseif data_type == DATA_TYPES.BINARY then
|
|
|
+ return value
|
|
|
+ elseif data_type == DATA_TYPES.UNICODE then
|
|
|
+ return value
|
|
|
+ else
|
|
|
+ log.info("Unsupported data type: " .. data_type)
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- 构建消息头
|
|
|
+-- @param need_reply boolean 是否需要服务器回复
|
|
|
+-- @param has_auth_key boolean 是否携带鉴权key
|
|
|
+-- @param data_length number 数据长度
|
|
|
+local function build_header(need_reply, is_udp_transport, data_length)
|
|
|
+ sequence_num = (sequence_num + 1) % 65536
|
|
|
+
|
|
|
+ -- 消息标识字段
|
|
|
+ local flags = config.protocol_version -- bit0-3: 协议版本号
|
|
|
+ if need_reply then
|
|
|
+ flags = flags + 16 -- bit4: 是否需要回复
|
|
|
+ end
|
|
|
+ if is_udp_transport then
|
|
|
+ flags = flags + 32 -- bit5: 是否是UDP承载
|
|
|
+ end
|
|
|
+ log.info("构建消息头", device_id_binary, to_big_endian(sequence_num, 2), to_big_endian(data_length, 2),
|
|
|
+ to_big_endian(flags, 4))
|
|
|
+ return device_id_binary ..
|
|
|
+ to_big_endian(sequence_num, 2) ..
|
|
|
+ to_big_endian(data_length, 2) ..
|
|
|
+ to_big_endian(flags, 4)
|
|
|
+end
|
|
|
+
|
|
|
+-- 构建TLV字段
|
|
|
+local function build_tlv(field_meaning, data_type, value)
|
|
|
+ local value_encoded = encode_value(data_type, value)
|
|
|
+ if value_encoded == nil then
|
|
|
+ log.info("构建tlv打包数据时长度为0")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ local length = #value_encoded
|
|
|
+ -- 字段类型(字段含义 + 数据类型)
|
|
|
+ local head = (field_meaning & 0x0FFF) | (data_type << 12) -- 2 字节头
|
|
|
+ return to_big_endian(head, 2) ..
|
|
|
+ to_big_endian(length, 2) ..
|
|
|
+ value_encoded
|
|
|
+end
|
|
|
+
|
|
|
+-- 解析消息头
|
|
|
+local function parse_header(header)
|
|
|
+ if #header < 16 then
|
|
|
+ log.info("消息头解析失败", "Header too short")
|
|
|
+ return nil, "Header too short"
|
|
|
+ end
|
|
|
+
|
|
|
+ local device_id = header:sub(1, 8)
|
|
|
+ local seq_num = from_big_endian(header, 9, 2)
|
|
|
+ local msg_length = from_big_endian(header, 11, 2)
|
|
|
+ local flags = from_big_endian(header, 13, 4)
|
|
|
+
|
|
|
+ -- 提取标志位
|
|
|
+ local protocol_version = flags % 16
|
|
|
+ local need_reply = (flags % 32) >= 16
|
|
|
+ local is_udp_transport = (flags % 64) >= 32
|
|
|
+
|
|
|
+ -- 打印解析结果,方便调试
|
|
|
+ log.info("消息头解析结果",
|
|
|
+ string.format(
|
|
|
+ "device_id: %s, sequence_num: %d, msg_length: %d, protocol_version: %d, need_reply: %s, is_udp_transport: %s",
|
|
|
+ string.toHex(device_id), seq_num, msg_length, protocol_version,
|
|
|
+ tostring(need_reply), tostring(is_udp_transport)))
|
|
|
+
|
|
|
+ return {
|
|
|
+ device_id = string.toHex(device_id),
|
|
|
+ sequence_num = seq_num,
|
|
|
+ msg_length = msg_length,
|
|
|
+ protocol_version = protocol_version,
|
|
|
+ need_reply = need_reply,
|
|
|
+ is_udp_transport = is_udp_transport
|
|
|
+ }
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+-- 工具函数:解析TLV
|
|
|
+local function parse_tlv(data, startPos)
|
|
|
+ -- 检查数据是否足够解析TLV的T的长度。
|
|
|
+ if #data < startPos + 3 then
|
|
|
+ return nil, startPos, "TLV data too short"
|
|
|
+ end
|
|
|
+
|
|
|
+ local fieldType = from_big_endian(data, startPos, 2)
|
|
|
+ local length = from_big_endian(data, startPos + 2, 2)
|
|
|
+ -- 提取原始字节值
|
|
|
+ local value = data:sub(startPos + 4, startPos + 4 + length - 1)
|
|
|
+ --解析TLV字段中的T
|
|
|
+ -- bit0-11: 字段含义
|
|
|
+ -- bit12-15: 数据类型
|
|
|
+ local field_meaning = fieldType & 0x0FFF -- 取低12位作为字段含义
|
|
|
+ local data_type = fieldType >> 12 -- 取高4位作为数据类型
|
|
|
+
|
|
|
+ local decoded_value = decode_value(data_type, value)
|
|
|
+ log.info("消息体解析结果", field_meaning, data_type, decoded_value)
|
|
|
+ return {
|
|
|
+ field = field_meaning,
|
|
|
+ type = data_type,
|
|
|
+ value = decoded_value,
|
|
|
+ length = length, --数据长度
|
|
|
+ }, startPos + 4 + length
|
|
|
+end
|
|
|
+
|
|
|
+-- 解析完整消息
|
|
|
+local function parse_message(data)
|
|
|
+ local header, err = parse_header(data:sub(1, 16))
|
|
|
+ if not header then
|
|
|
+ return nil, err
|
|
|
+ end
|
|
|
+ local auth_key = nil
|
|
|
+ local body_start = 17
|
|
|
+
|
|
|
+ -- 如果是UDP传输,解析认证key
|
|
|
+ if header.is_udp_transport then
|
|
|
+ if #data >= body_start + 64 - 1 then
|
|
|
+ auth_key = data:sub(body_start, body_start + 64 - 1)
|
|
|
+ body_start = body_start + 64
|
|
|
+ else
|
|
|
+ return nil, "Incomplete UDP authentication key"
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 解析TLV字段
|
|
|
+ local tlvs = {}
|
|
|
+ local pos = body_start
|
|
|
+ local end_pos = 16 + (header.msg_length)
|
|
|
+
|
|
|
+ if #data < end_pos then
|
|
|
+ return nil, "Message incomplete"
|
|
|
+ end
|
|
|
+ while pos < end_pos do
|
|
|
+ local tlv, new_pos, err = parse_tlv(data, pos)
|
|
|
+ if not tlv then
|
|
|
+ return nil, "Failed to parse TLV at position " .. pos
|
|
|
+ end
|
|
|
+ table.insert(tlvs, tlv)
|
|
|
+ -- 更新解析位置为解析完当前TLV字段后的新位置,以便继续解析后续的TLV字段
|
|
|
+ pos = new_pos
|
|
|
+ end
|
|
|
+
|
|
|
+ return {
|
|
|
+ header = header,
|
|
|
+ auth_key = auth_key,
|
|
|
+ tlvs = tlvs
|
|
|
+ }
|
|
|
+end
|
|
|
+
|
|
|
+-- 发送鉴权请求
|
|
|
+local function send_auth_request()
|
|
|
+ if not config.auth_key then
|
|
|
+ return false, "No auth key configured"
|
|
|
+ end
|
|
|
+ local auth_data
|
|
|
+ -- auth_data = config.auth_key .. "-" .. config.device_id .. "-" .. "323B131815B0DFC9"
|
|
|
+ --设备实测时打开
|
|
|
+ if config.device_type == 1 then
|
|
|
+ auth_data = config.auth_key .. "-" .. mobile.imei() .. "-" ..mobile.muid()
|
|
|
+ elseif config.device_type == 2 then
|
|
|
+ auth_data = config.auth_key .. "-" .. wlan.getMac(nil, true) .. "-" .. mobile.muid():toHex()
|
|
|
+ else
|
|
|
+ auth_data = config.auth_key .. "-"
|
|
|
+ end
|
|
|
+
|
|
|
+ local message = {
|
|
|
+ {
|
|
|
+ field_meaning = FIELD_MEANINGS.AUTH_REQUEST,
|
|
|
+ data_type = DATA_TYPES.ASCII,
|
|
|
+ value = auth_data
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("send auth request", message,message[1].value,message[1].data_type,message[1].field_meaning)
|
|
|
+ return excloud.send(message, true, true) -- 鉴权消息需要设置 is_auth_msg 为 true
|
|
|
+end
|
|
|
+
|
|
|
+-- 处理认证响应AAA
|
|
|
+local function handle_auth_response(tlvs)
|
|
|
+ for _, tlv in ipairs(tlvs) do
|
|
|
+ if tlv.field_meaning == FIELD_MEANINGS.AUTH_RESPONSE then
|
|
|
+ local success = (tlv.value == "OK" or tlv.value == "SUCCESS")
|
|
|
+ is_authenticated = success
|
|
|
+ if callback_func then
|
|
|
+ callback_func("auth_result", {
|
|
|
+ success = success,
|
|
|
+ message = tlv.value
|
|
|
+ })
|
|
|
+ end
|
|
|
+ -- 认证成功,发送待处理消息
|
|
|
+ if success then
|
|
|
+ for _, msg in ipairs(pending_messages) do
|
|
|
+ excloud.send(msg.data, msg.need_reply)
|
|
|
+ end
|
|
|
+ pending_messages = {}
|
|
|
+ end
|
|
|
+
|
|
|
+ return success
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ return false
|
|
|
+end
|
|
|
+
|
|
|
+-- 接收消息解析处理
|
|
|
+local function parse_data(data)
|
|
|
+ local message, err = parse_message(data)
|
|
|
+ if not message then
|
|
|
+ log.info("Failed to parse message: " .. err)
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ --数据返回给回调
|
|
|
+ if callback_func then
|
|
|
+ callback_func("message", message)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- -- 如果需要回复,发送确认AAA
|
|
|
+ -- if message.header.need_reply then
|
|
|
+ -- local response = {
|
|
|
+ -- {
|
|
|
+ -- field_meaning = FIELD_MEANINGS.REPORT_RESPONSE,
|
|
|
+ -- data_type = DATA_TYPES.ASCII,
|
|
|
+ -- value = "ACK"
|
|
|
+ -- }
|
|
|
+ -- }
|
|
|
+ -- excloud.send(response, false)
|
|
|
+ -- end
|
|
|
+end
|
|
|
+-- 重连 AAA
|
|
|
+local function schedule_reconnect()
|
|
|
+ if reconnect_count >= config.max_reconnect then
|
|
|
+ log.info("Max reconnection attempts reached")
|
|
|
+ if callback_func then
|
|
|
+ callback_func("reconnect_failed", { count = reconnect_count })
|
|
|
+ end
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ reconnect_count = reconnect_count + 1
|
|
|
+ log.info("重连次数 " .. reconnect_count)
|
|
|
+
|
|
|
+ -- 使用定时器安排重连
|
|
|
+ reconnect_timer = sys.timerStart(function()
|
|
|
+ log.info("Attempting to reconnect...")
|
|
|
+ excloud.open()
|
|
|
+ end, config.reconnect_interval * 1000)
|
|
|
+end
|
|
|
+-- TCP socket事件回调函数
|
|
|
+local function tcp_socket_callback(netc, event, param)
|
|
|
+ log.info("111111socket", netc, event, param)
|
|
|
+ -- 取消连接超时定时器
|
|
|
+ if connect_timeout_timer then
|
|
|
+ sys.timerStop(connect_timeout_timer)
|
|
|
+ connect_timeout_timer = nil
|
|
|
+ end
|
|
|
+
|
|
|
+ if param ~= 0 then
|
|
|
+ log.info("socket", "连接断开")
|
|
|
+ is_connected = false
|
|
|
+ is_authenticated = false
|
|
|
+
|
|
|
+ if callback_func then
|
|
|
+ callback_func("disconnect", {})
|
|
|
+ end
|
|
|
+ -- 连接断开,释放资源
|
|
|
+ socket.release(connection)
|
|
|
+ connection = nil
|
|
|
+
|
|
|
+ -- 尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+ return
|
|
|
+ end
|
|
|
+ if event == socket.LINK then
|
|
|
+ -- -- 网络连接成功
|
|
|
+ -- log.info("socket", "网络连接成功")
|
|
|
+ elseif event == socket.ON_LINE then
|
|
|
+ -- TCP连接成功
|
|
|
+ log.info("socket", "TCP连接成功")
|
|
|
+ is_connected = true
|
|
|
+
|
|
|
+ -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
|
|
|
+ reconnect_count = 0
|
|
|
+
|
|
|
+
|
|
|
+ -- 发送认证请求
|
|
|
+ send_auth_request()
|
|
|
+ if callback_func then
|
|
|
+ callback_func("connect_result", { success = true })
|
|
|
+ end
|
|
|
+ elseif event == socket.EVENT then
|
|
|
+ -- 有数据到达
|
|
|
+ socket.rx(netc, rxbuff)
|
|
|
+ if rxbuff:used() > 0 then
|
|
|
+ local data = rxbuff:query()
|
|
|
+ log.info("socket", "收到数据", #data, "字节", data:toHex())
|
|
|
+ -- 处理接收到的数据
|
|
|
+ parse_data(data)
|
|
|
+ end
|
|
|
+ -- 清空缓冲区
|
|
|
+ rxbuff:del()
|
|
|
+ elseif event == socket.TX_OK then
|
|
|
+ socket.wait(netc)
|
|
|
+ log.info("socket", "发送完成")
|
|
|
+ elseif event == socket.CLOSED then
|
|
|
+ -- 连接错误或关闭
|
|
|
+ log.info("socket", "主动断开链接")
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- mqtt client的事件回调函数
|
|
|
+local function mqtt_client_event_cbfunc(connected, event, data, payload, metas)
|
|
|
+ log.info("mqtt_client_event_cbfunc", connected, event, data, payload, json.encode(metas))
|
|
|
+ -- 取消连接超时定时器(如果连接成功或失败)
|
|
|
+ if connect_timeout_timer then
|
|
|
+ sys.timerStop(connect_timeout_timer)
|
|
|
+ connect_timeout_timer = nil
|
|
|
+ end
|
|
|
+
|
|
|
+ -- mqtt连接成功
|
|
|
+ if event == "conack" then
|
|
|
+ is_connected = true
|
|
|
+ log.info("MQTT connected")
|
|
|
+ -- 重置重连计数,如果是重连的话,连接上服务器给重连计数重置为0
|
|
|
+ reconnect_count = 0
|
|
|
+ -- 订阅主题
|
|
|
+ local auth_topic = "/AirCloud/" .. config.device_id .. "/auth"
|
|
|
+ local all_topic = "/AirCloud/" .. config.device_id .. "/all"
|
|
|
+ log.info("mqtt_client_event_cbfunc", "订阅主题", auth_topic, all_topic)
|
|
|
+ connection:subscribe(auth_topic, 0)
|
|
|
+ connection:subscribe(all_topic, 0)
|
|
|
+
|
|
|
+ -- 发送认证请求
|
|
|
+ send_auth_request()
|
|
|
+
|
|
|
+ if callback_func then
|
|
|
+ callback_func("connect_result", { success = true })
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 订阅成功
|
|
|
+ elseif event == "suback" then
|
|
|
+ -- 取消订阅成功
|
|
|
+ elseif event == "unsuback" then
|
|
|
+ -- 接收到服务器下发的publish数据
|
|
|
+ -- data:string类型,表示topic
|
|
|
+ -- payload:string类型,表示payload
|
|
|
+ -- metas:table类型,数据内容如下
|
|
|
+ -- {
|
|
|
+ -- qos: number类型,取值范围0,1,2
|
|
|
+ -- retain:number类型,取值范围0,1
|
|
|
+ -- dup:number类型,取值范围0,1
|
|
|
+ -- message_id: number类型
|
|
|
+ -- }
|
|
|
+ elseif event == "recv" then
|
|
|
+ -- 对接收到的publish数据处理
|
|
|
+ parse_data(payload)
|
|
|
+
|
|
|
+
|
|
|
+ -- 发送成功publish数据
|
|
|
+ -- data:number类型,表示message id
|
|
|
+ elseif event == "sent" then
|
|
|
+ -- 服务器断开mqtt连接
|
|
|
+ elseif event == "disconnect" then
|
|
|
+ is_connected = false
|
|
|
+ is_authenticated = false
|
|
|
+ connection:disconnect()
|
|
|
+ log.info("MQTT disconnected")
|
|
|
+
|
|
|
+ if callback_func then
|
|
|
+ callback_func("disconnect", {})
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 收到服务器的心跳应答
|
|
|
+ elseif event == "pong" then
|
|
|
+ -- 严重异常,本地会主动断开连接
|
|
|
+ -- data:string类型,表示具体的异常,有以下几种:
|
|
|
+ -- "connect":tcp连接失败
|
|
|
+ -- "tx":数据发送失败
|
|
|
+ -- "conack":mqtt connect后,服务器应答CONNACK鉴权失败,失败码为payload(number类型)
|
|
|
+ -- "other":其他异常
|
|
|
+ elseif event == "error" then
|
|
|
+ is_connected = false
|
|
|
+ is_authenticated = false
|
|
|
+ connection:disconnect()
|
|
|
+ local error_msg = "Unknown MQTT error"
|
|
|
+
|
|
|
+ -- 根据错误类型提供更详细的错误信息
|
|
|
+ if data == "connect" then
|
|
|
+ error_msg = "TCP connection failed"
|
|
|
+ elseif data == "tx" then
|
|
|
+ error_msg = "Data transmission failed"
|
|
|
+ elseif data == "conack" then
|
|
|
+ error_msg = "MQTT authentication failed with code: " .. tostring(payload)
|
|
|
+ end
|
|
|
+
|
|
|
+ log.info("MQTT error: " .. error_msg)
|
|
|
+
|
|
|
+ if callback_func then
|
|
|
+ callback_func("disconnect", { error = error_msg })
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+-- 设置配置参数
|
|
|
+function excloud.setup(params)
|
|
|
+ if is_open then
|
|
|
+ return false, "excloud is already open"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 合并配置参数
|
|
|
+ for k, v in pairs(params) do
|
|
|
+ config[k] = v
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 验证必要参数
|
|
|
+ -- if not config.device_id or config.device_id == "" then
|
|
|
+ -- return false, "Device ID is required"
|
|
|
+ -- end
|
|
|
+
|
|
|
+ if config.device_type == 1 then
|
|
|
+ config.device_id = mobile.imei()
|
|
|
+ elseif config.device_type == 2 then
|
|
|
+ config.device_id = wlan.getMac(nil, true)
|
|
|
+ --以太网设备
|
|
|
+ elseif config.device_type == 4 then
|
|
|
+ config.device_id = netdrv.mac(socket.LWIP_ETH)
|
|
|
+ else
|
|
|
+ return false, "未知设备类型"
|
|
|
+ end
|
|
|
+ -- 打包设备id
|
|
|
+ device_id_binary = packDeviceInfo(config.device_type, config.device_id)
|
|
|
+
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 注册回调函数
|
|
|
+function excloud.on(cbfunc)
|
|
|
+ if type(cbfunc) ~= "function" then
|
|
|
+ return false, "Callback must be a function"
|
|
|
+ end
|
|
|
+
|
|
|
+ callback_func = cbfunc
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 开启excloud服务
|
|
|
+function excloud.open()
|
|
|
+ -- 检查是否已打开
|
|
|
+ if is_open then
|
|
|
+ return false, "excloud is already open"
|
|
|
+ end
|
|
|
+
|
|
|
+ --判断是否初始化
|
|
|
+ if not device_id_binary then
|
|
|
+ return false, "excloud 没有初始化,请先调用setup"
|
|
|
+ end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ -- 根据传输协议创建连接
|
|
|
+ if config.transport == "tcp" then
|
|
|
+ -- 创建接收缓冲区
|
|
|
+ rxbuff = zbuff.create(2048)
|
|
|
+ -- 创建TCP连接
|
|
|
+ log.info("创建TCP连接")
|
|
|
+ connection = socket.create(nil, tcp_socket_callback)
|
|
|
+ if not connection then
|
|
|
+ return false, "Failed to create socket"
|
|
|
+ end
|
|
|
+ -- 配置socket
|
|
|
+ socket.config(connection) -- 开启TCP保活,防止长时间无数据交互被运营商断线
|
|
|
+ socket.debug(connection, true) -- 开启调试
|
|
|
+
|
|
|
+ -- 设置连接超时定时器
|
|
|
+ connect_timeout_timer = sys.timerStart(function()
|
|
|
+ if not is_connected then
|
|
|
+ log.error("TCP connection timeout")
|
|
|
+ if connection then
|
|
|
+ socket.close(connection)
|
|
|
+ socket.release(connection)
|
|
|
+ connection = nil
|
|
|
+ end
|
|
|
+
|
|
|
+ if callback_func then
|
|
|
+ callback_func("connect_result", { success = false, error = "Connection timeout" })
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end, config.timeout * 1000)
|
|
|
+
|
|
|
+ -- 连接到服务器
|
|
|
+ local ok, err = socket.connect(connection, config.host, config.port)
|
|
|
+ log.info("TCP连接结果", ok, err)
|
|
|
+ if not ok then
|
|
|
+ --发生异常,强制close
|
|
|
+ socket.close(connection)
|
|
|
+ --释放资源
|
|
|
+ socket.release(connection)
|
|
|
+ connection = nil
|
|
|
+ -- 首次连接失败尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+ return false, err
|
|
|
+ end
|
|
|
+ elseif config.transport == "mqtt" then
|
|
|
+ -- 创建MQTT客户端
|
|
|
+ connection = mqtt.create(nil, config.host, config.port, config.ssl)
|
|
|
+
|
|
|
+ -- 设置认证信息
|
|
|
+ connection:auth(config.device_id, config.username, config.password)
|
|
|
+
|
|
|
+ -- 注册事件回调
|
|
|
+ connection:on(mqtt_client_event_cbfunc)
|
|
|
+ -- 设置保持连接间隔
|
|
|
+ connection:keepalive(config.keepalive)
|
|
|
+
|
|
|
+ -- 设置连接超时定时器
|
|
|
+ connect_timeout_timer = sys.timerStart(function()
|
|
|
+ if not is_connected then
|
|
|
+ log.error("MQTT connection timeout")
|
|
|
+
|
|
|
+ if callback_func then
|
|
|
+ callback_func("connect_result", { success = false, error = "Connection timeout" })
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end, config.timeout * 1000)
|
|
|
+
|
|
|
+ -- 连接到服务器
|
|
|
+ local ok = connection:connect()
|
|
|
+ if not ok then
|
|
|
+ --连接失败,释放资源
|
|
|
+ connection:disconnect()
|
|
|
+ -- 发起连接失败,尝试重连
|
|
|
+ if config.auto_reconnect and is_open then
|
|
|
+ is_open = false
|
|
|
+ schedule_reconnect()
|
|
|
+ end
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ else
|
|
|
+ return false, "Unsupported transport: " .. config.transport
|
|
|
+ end
|
|
|
+
|
|
|
+ is_open = true
|
|
|
+ log.info("excloud service started")
|
|
|
+
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 发送数据
|
|
|
+-- 发送消息到云端
|
|
|
+-- @param data table 待发送的数据,每个元素是一个包含 field_meaning、data_type 和 value 的表
|
|
|
+-- @param need_reply boolean 是否需要服务器回复,默认为 false
|
|
|
+-- @param is_auth_msg boolean 是否是鉴权消息,默认为 false
|
|
|
+function excloud.send(data, need_reply, is_auth_msg)
|
|
|
+ -- 检查参数是否为table
|
|
|
+ if type(data) ~= "table" then
|
|
|
+ -- 通过回调返回错误
|
|
|
+ if callback_func then
|
|
|
+ callback_func("send_result", {
|
|
|
+ success = false,
|
|
|
+ error_msg = "Invalid data parameter: table expected"
|
|
|
+ })
|
|
|
+ end
|
|
|
+ return false,"Invalid data parameter: table expected"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查服务是否开启
|
|
|
+ if not is_open then
|
|
|
+ if callback_func then
|
|
|
+ callback_func("send_result", {
|
|
|
+ success = false,
|
|
|
+ error_msg = "excloud service not open"
|
|
|
+ })
|
|
|
+ end
|
|
|
+ return false, "excloud service not open"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查是否已连接
|
|
|
+ if not is_connected then
|
|
|
+ if callback_func then
|
|
|
+ callback_func("send_result", {
|
|
|
+ success = false,
|
|
|
+ error_msg = "Not connected to server"
|
|
|
+ })
|
|
|
+ end
|
|
|
+ return false, "Not connected to server"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 保存当前序列号用于回调
|
|
|
+ local current_sequence = sequence_num
|
|
|
+
|
|
|
+ -- 构建消息体
|
|
|
+ local message_body = ""
|
|
|
+ for _, item in ipairs(data) do
|
|
|
+ log.info("发送数据333", item.field_meaning, item.data_type, item.value, message_body)
|
|
|
+
|
|
|
+ message_body = message_body .. build_tlv(item.field_meaning, item.data_type, item.value)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查消息长度
|
|
|
+ local udp_auth_key = config.udp_auth_key and true or false
|
|
|
+ local total_length = #message_body + (udp_auth_key and 64 or 0)
|
|
|
+
|
|
|
+ log.info("tlv发送数据长度4", total_length)
|
|
|
+
|
|
|
+ -- 构建消息头
|
|
|
+ local is_udp_transport = (config.transport == "udp")
|
|
|
+ local header = build_header(need_reply or false, is_udp_transport, total_length)
|
|
|
+
|
|
|
+ -- -- 添加鉴权key(如果是UDP的话)
|
|
|
+ local auth_key_part = ""
|
|
|
+ if config.transport == "udp" and udp_auth_key then
|
|
|
+ auth_key_part = config.udp_auth_key
|
|
|
+ if #auth_key_part < 64 then
|
|
|
+ auth_key_part = auth_key_part .. string.rep("\0", 64 - #auth_key_part)
|
|
|
+ elseif #auth_key_part > 64 then
|
|
|
+ auth_key_part = auth_key_part:sub(1, 64)
|
|
|
+ end
|
|
|
+ end
|
|
|
+ local full_message
|
|
|
+ -- 发送完整消息
|
|
|
+ if config.transport == "udp" then
|
|
|
+ full_message = header .. auth_key_part .. message_body
|
|
|
+ else
|
|
|
+ full_message = header .. message_body
|
|
|
+ end
|
|
|
+
|
|
|
+ log.info("发送消息长度", #header, #message_body, #full_message, full_message:toHex())
|
|
|
+
|
|
|
+ local success, err_code, err_msg
|
|
|
+ if config.transport == "tcp" then
|
|
|
+ if not connection then
|
|
|
+ err_msg = "TCP connection not available"
|
|
|
+ success = false
|
|
|
+ else
|
|
|
+ success, err_msg = socket.tx(connection, full_message)
|
|
|
+ end
|
|
|
+ elseif config.transport == "mqtt" then
|
|
|
+ -- 根据是否为鉴权消息选择不同的topic
|
|
|
+ local topic
|
|
|
+ if is_auth_msg then
|
|
|
+ topic = "/AirCloud/" .. config.device_id .. "/auth"
|
|
|
+ else
|
|
|
+ topic = "/AirCloud/" .. config.device_id .. "/all"
|
|
|
+ end
|
|
|
+ log.info("发布主题", topic, #full_message, full_message:toHex())
|
|
|
+ success = connection:publish(topic, full_message, config.qos, config.retain)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 通过回调返回发送结果
|
|
|
+ if callback_func then
|
|
|
+ callback_func("send_result", {
|
|
|
+ success = success,
|
|
|
+ error_msg = success and "Send successful" or err_msg,
|
|
|
+ sequence_num = current_sequence
|
|
|
+ })
|
|
|
+ end
|
|
|
+
|
|
|
+ if success then
|
|
|
+ log.info("数据发送成功", #full_message, "字节")
|
|
|
+ return true
|
|
|
+ else
|
|
|
+ log.error("数据发送失败", err_msg)
|
|
|
+ return false, err_code, err_msg
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- 关闭excloud服务
|
|
|
+function excloud.close()
|
|
|
+ if not is_open then
|
|
|
+ return false, "excloud not open"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 取消重连定时器
|
|
|
+ if reconnect_timer then
|
|
|
+ sys.timerStop(reconnect_timer)
|
|
|
+ reconnect_timer = nil
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 关闭连接
|
|
|
+ if connection then
|
|
|
+ if config.transport == "tcp" then
|
|
|
+ socket.close(connection)
|
|
|
+ socket.release(connection)
|
|
|
+ elseif config.transport == "mqtt" then
|
|
|
+ connection:disconnect()
|
|
|
+ end
|
|
|
+ connection = nil
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 重置状态
|
|
|
+ is_open = false
|
|
|
+ is_connected = false
|
|
|
+ is_authenticated = false
|
|
|
+ pending_messages = {}
|
|
|
+ rxbuff = nil
|
|
|
+
|
|
|
+ log.info("excloud service stopped")
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 获取当前状态
|
|
|
+function excloud.status()
|
|
|
+ return {
|
|
|
+ is_open = is_open,
|
|
|
+ is_connected = is_connected,
|
|
|
+ sequence_num = sequence_num,
|
|
|
+ reconnect_count = reconnect_count,
|
|
|
+ pending_messages = #pending_messages,
|
|
|
+ }
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+-- 导出常量
|
|
|
+excloud.DATA_TYPES = DATA_TYPES
|
|
|
+excloud.FIELD_MEANINGS = FIELD_MEANINGS
|
|
|
+
|
|
|
+return excloud
|