#!/usr/bin/lua -- 创业惠康 协议常量定义 -- CYHK Protocol, private Lua module -- Created by jiaqiang.ye@lnxall.com -- 2022/01/17 local cyhkp = {} local cjson = require 'cjson' local invoker = require 'invoker' local posix = require 'posix' -- mapping for reponse command (from the angle of gateway) cyhkp.CMDRSP = {} -- mapping for struct-id and CMD/RSP local s_ids = {} cyhkp.CMD_HEARTBEAT = 111 -- 网关发送心跳 cyhkp.CMD_HEARTBEAT_RSP = 112 -- 平台回复心跳 s_ids[cyhkp.CMD_HEARTBEAT] = 11800 s_ids[cyhkp.CMD_HEARTBEAT_RSP] = 11801 cyhkp.CMD_CONNECT = 101 -- 网关发送入网请求 cyhkp.CMD_CONNECT_RSP = 102 -- 平台回复入网请求 s_ids[cyhkp.CMD_CONNECT] = 11802 s_ids[cyhkp.CMD_CONNECT_RSP] = 11803 cyhkp.CMD_NTPCFG = 121 -- 平台发送NTP服务器配置 cyhkp.CMD_NTPCFG_RSP = 122 -- 网关回复平台 cyhkp.CMDRSP[cyhkp.CMD_NTPCFG] = cyhkp.CMD_NTPCFG_RSP s_ids[cyhkp.CMD_NTPCFG] = 11804 s_ids[cyhkp.CMD_NTPCFG_RSP] = 11805 cyhkp.CMD_NETCFG = 131 -- 平台下发网络配置 cyhkp.CMD_NETCFG_RSP = 132 -- 网关回复网络配置 cyhkp.CMDRSP[cyhkp.CMD_NETCFG] = cyhkp.CMD_NETCFG_RSP s_ids[cyhkp.CMD_NETCFG] = 11806 s_ids[cyhkp.CMD_NETCFG_RSP] = 11807 cyhkp.CMD_NETSTATUS = 133 -- 平台请求网关网络状态 cyhkp.CMD_NETSTATUS_RSP = 134 -- 网关回复网络信息 cyhkp.CMDRSP[cyhkp.CMD_NETSTATUS] = cyhkp.CMD_NETSTATUS_RSP s_ids[cyhkp.CMD_NETSTATUS] = 11808 s_ids[cyhkp.CMD_NETSTATUS_RSP] = 11809 cyhkp.CMD_FWUPGRADE = 171 -- 平台下发升级请求 cyhkp.CMD_FWUPGRADE_RSP = 172 -- 网关回复 cyhkp.CMDRSP[cyhkp.CMD_FWUPGRADE] = cyhkp.CMD_FWUPGRADE_RSP s_ids[cyhkp.CMD_FWUPGRADE] = 11810 s_ids[cyhkp.CMD_FWUPGRADE_RSP] = 11811 cyhkp.CMD_REBOOT = 201 -- 平台下发重启 cyhkp.CMD_REBOOT_RSP = 202 -- 网关回复 cyhkp.CMDRSP[cyhkp.CMD_REBOOT] = cyhkp.CMD_REBOOT_RSP s_ids[cyhkp.CMD_REBOOT] = 11812 s_ids[cyhkp.CMD_REBOOT_RSP] = 11813 cyhkp.CMD_PARAMS = 141 -- 平台下发运行参数配置 cyhkp.CMD_PARAMS_RSP = 142 -- 网关回复 cyhkp.CMDRSP[cyhkp.CMD_PARAMS] = cyhkp.CMD_PARAMS_RSP s_ids[cyhkp.CMD_PARAMS] = 11814 s_ids[cyhkp.CMD_PARAMS_RSP] = 11815 cyhkp.CMD_DATA_REPORT = 161 -- 数据主动上报或回复平台数据请求 cyhkp.CMD_DATA_REPLY = 163 -- 平台回复或平台下发数据 cyhkp.CMDRSP[cyhkp.CMD_DATA_REPLY] = cyhkp.CMD_DATA_REPORT s_ids[cyhkp.CMD_DATA_REPORT] = 11816 s_ids[cyhkp.CMD_DATA_REPLY] = 11817 cyhkp.CMD_STATUS = 221 -- 网关主动上报运行状态 cyhkp.CMD_STATUS_REQ = 222 -- 平台请求,网关运行状态获取 cyhkp.CMDRSP[cyhkp.CMD_STATUS_REQ] = cyhkp.CMD_STATUS s_ids[cyhkp.CMD_STATUS_REQ] = 11818 s_ids[cyhkp.CMD_STATUS] = 11819 cyhkp.STRUCTIDS = s_ids -- msg_type for network configurations cyhkp.MSGTYPE_NETCFG_WAN = 31 -- WAN配置 cyhkp.MSGTYPE_NETCFG_WIFI = 32 -- WiFi配置 cyhkp.MSGTYPE_NETCFG_LAN = 33 -- LAN配置 cyhkp.MSGTYPE_NETCFG_4GLTE = 34 -- 4G-LTE配置 cyhkp.MSGTYPE_NETCFG_MQTT = 35 -- MQTT-Broker配置 -- msg_type for parameter settings, 运行参数配置 cyhkp.MSGTYPE_PARAM_CARD = 41 -- 插卡配置 cyhkp.MSGTYPE_PARAM_TEMPLATE = 42 -- 模版配置 cyhkp.MSGTYPE_PARAM_DEVICE = 43 -- 设备修改 cyhkp.MSGTYPE_PARAM_APPINST = 44 -- 应用安装 -- The absolute path of download firmware cyhkp.FWPATH = "/tmp/woolink_upgrade.bin" -- productKey used in MQTT topic cyhkp.PRODUCTKEY = 'LNXALL_CYHK_PRODUCTKEY' -- default network device for WAN cyhkp.WAN_NETDEV = "eth0" -- path to the MQTT server configuration file cyhkp.MQTTSVRCFG = '/app/config/mqtt_cyhk.json' -- path to MQTT-TLS cafile cyhkp.MQTTCA = '/app/config/mqtt_cyhk.ca' -- path to MQTT-TLS certfile cyhkp.MQTTCERT = '/app/config/mqtt_cyhk.cert' -- path to MQTT-TLS keyfile cyhkp.MQTTKEY = '/app/config/mqtt_cyhk.key' -- path to the node configuration files cyhkp.NODECFG = '/app/node/nodes_cfg.json' cyhkp.MODELCFG = '/app/node/object_model_cfg.json' cyhkp.TEMPLATECFG = '/app/node/templates_cfg.json' cyhkp.RS485PORTCFG = '/app/config/rs485_port_cfg.json' -- absolutely path for opkg cyhkp.opkg = '/bin/opkg' -- CYHK protocol version, 创业惠康协议版本号 cyhkp.VERSION = "20220725" -- gateway response post action cyhkp.POST_REBOOT = 0x01 cyhkp.POST_RELOAD_NETWORK = 0x02 cyhkp.POST_RELOAD_WIFI = 0x04 cyhkp.POST_RELOAD_FIREWALL = 0x08 cyhkp.POST_RELOAD_DNSMASQ = 0x10 cyhkp.POST_FWUPGRADE = 0x20 cyhkp.POST_RECONNECT = 0x40 -- nodes_cfg.json and/or templates_cfg.json have been modified, -- need to reload services, such as protocol_parser, ble, data_reporter cyhkp.POST_RELOAD_SERVICES = 0x80 -- 网络通信协议映射表 cyhkp.PROTO_BLE = 0 cyhkp.PROTO_LORA = 1 cyhkp.PROTO_UWB = 2 cyhkp.PROTO_ZIGBEE = 3 cyhkp.PROTO_NBIOT = 4 cyhkp.PROTO_WIFI = 100 cyhkp.PROTO_ETHERNET = 101 cyhkp.PROTO_CELLULAR = 102 -- Cellular network, 蜂窝网 -- function to determine card settings JSON file cyhkp.cardinfo_path = function (cardid, protocol) local prefix = '/app/config' if protocol == cyhkp.PROTO_BLE then return nil, "BLE does not need card settings" end if protocol == cyhkp.PROTO_LORA then return string.format('%s/lora_cfg_%d.json', prefix, cardid) end if protocol == cyhkp.PROTO_ZIGBEE then return string.format('%s/fastzigbee_cfg_%d.json', prefix, cardid) end if protocol == cyhkp.PROTO_UWB then return nil, "unknown UWB card settings" end if protocol == cyhkp.PROTO_NBIOT then return nil, "unknown NBIOT card settings" end return nil, string.format('unknown cardid and protocol: %d, %d', cardid, protocol) end -- get factory information, by invoking -- `factory get, and reading /etc/issue cyhkp.factory_get = function () -- no argument local est, outbuf = invoker.invoke(invoker.OUTPUT + invoker.CLOSEFD, 'factory', 'get') if est ~= 0 or type(outbuf) ~= "string" or #outbuf == 0 then return nil -- no output found end local rval = {} rval["PRODUCT"] = string.match(outbuf, "PRODUCT=([^\r\n]+)") rval["SN"] = string.match(outbuf, "SN=([^\r\n]+)") rval["DATE"] = string.match(outbuf, "DATE=([^\r\n]+)") rval["HWVER"] = string.match(outbuf, "HWVER=([^\r\n]+)") -- use uppercase MAC address local macaddr = string.match(outbuf, "MAC=([^\r\n]+)") if macaddr and #macaddr > 0 then rval["MAC"] = macaddr:upper() end rval["FIRMVER"] = invoker.readfile("/etc/issue", invoker.TRIMEND) return rval end -- second level indexing of an array cyhkp.find_index = function (extab, itemn, itemv) local typn = type(itemv) for kdx, item in ipairs(extab) do if type(item) == "table" and typn == type(item[itemn]) and itemv == item[itemn] then return kdx end end return nil end -- function to write a Json configuration file cyhkp.write_json_config = function (jfile, jcfg) local jdat = nil local jhdl = io.open(jfile, "wb") if not jhdl then return false end if type(jcfg) == "table" then jdat = cjson.encode(jcfg) end if jdat then jhdl:write(jdat) end jhdl:close(); jhdl = nil return true end -- function to load a JSON configuration file cyhkp.load_json_config = function (jcfg, dcfg) if type(jcfg) ~= "string" or #jcfg == 0 then return dcfg end local jhdl = io.open(jcfg, "rb") if not jhdl then return dcfg end local jdat = jhdl:read("*a") jhdl:close(); jhdl = nil if not jdat or #jdat == 0 then return dcfg end local okay, cfgs = pcall(cjson.decode, jdat) if not okay then return dcfg end return cfgs end -- function to load MQTT server configuration file cyhkp.load_mqtt_svrcfg = function (mqcfg) local mcfg = mqcfg or cyhkp.MQTTSVRCFG local dftcfg = { host = "10.10.2.10", port = 1883 } local mqttcfg = cyhkp.load_json_config(mcfg, nil) if not mqttcfg then return dftcfg end if type(mqttcfg.host) ~= "string" or string.len(mqttcfg.host) == 0 or type(mqttcfg.port) ~= "number" then io.stderr:write("Error, corrupted MQTT config file\n") return dftcfg end return mqttcfg end -- function to load file via curl cyhkp.download = function (dlurl, outfile, curltime, totaltime, maxsize, md5sum) local args, argc = {}, 0 -- setup arguments argc = argc + 1; args[argc] = "curl" argc = argc + 1; args[argc] = "--insecure" argc = argc + 1; args[argc] = "--silent" argc = argc + 1; args[argc] = '--output' argc = argc + 1; args[argc] = outfile if curltime then argc = argc + 1; args[argc] = '--connect-timeout' argc = argc + 1; args[argc] = type(curltime) == "number" and tostring(curltime) or "5" end argc = argc + 1; args[argc] = dlurl -- remove existing output file if already exist if posix.access(outfile) then posix.unlink(outfile) end -- invoke `curl application local curlpid = invoker.invoke(invoker.NOWAIT + invoker.NOSTDIO + invoker.CLOSEFD, args) if not curlpid then return nil, "failed to invoke curl" end local nowt = invoker.uptime() while true do local exited, exst = invoker.waitpid(curlpid, true) if exited then if exst ~= 0 then posix.unlink(outfile); posix.sync() return nil, "failed to download via curl: " .. dlurl end break end if totaltime and totaltime > 0 then local thetime = invoker.uptime() if (thetime - nowt) > totaltime then -- download time has expired if invoker.kill(curlpid, posix.SIGTERM) then invoker.waitpid(curlpid, false) end posix.unlink(outfile); posix.sync() return nil, "download has expired: " .. dlurl end end if maxsize and maxsize > 0 then local filest = posix.stat(outfile) if type(filest) == "table" then local cursize = filest.st_size if type(cursize) == "number" then io.stdout:write(string.format("\rDownloading to %s: %.02f%%", outfile, (cursize / maxsize) * 100.0)) io.stdout:flush() end end end posix.sync(); posix.nanosleep{tv_sec = 0, tv_nsec = 500000000} end local outst = posix.stat(outfile) if not outst then return nil, "curl has not write to " .. outfile end if md5sum then local _, md5buf = invoker.invoke(invoker.OUTPUT + invoker.CLOSEFD, "md5sum", outfile) if type(md5buf) ~= "string" or #md5buf == 0 then -- fatal error, cannot calculate the md5sum for `outfile posix.unlink(outfile) return nil, "cannot get md5sum for " .. outfile end md5buf = string.match(md5buf, "^(%x+)") if md5buf ~= md5sum then posix.unlink(outfile) return nil, "md5sum values do not match for " .. outfile end end return outst.st_size end -- function to reload a list of services cyhkp.reloadservices = function () local ble_sup = "/etc/init.d/ble_sup" local flags = invoker.NOSTDIO + invoker.CLOSEFD invoker.invoke(flags, ble_sup, "stop") local cyhk_svrs = { "ble_1", "ble_2", "ble_3", "ble_4", "protocol_parser", "data_reporter", } for _, svr in ipairs(cyhk_svrs) do local initd = "/etc/init.d/" .. svr if posix.access(initd) then invoker.invoke(flags, initd, "stop") invoker.waitsec(0x1) -- delay at most one second invoker.invoke(flags, initd, "start") else io.stderr:write("Service not found: " .. initd .. "\n") io.stderr:flush() end end invoker.invoke(flags, ble_sup, "start") end return cyhkp