diff --git a/air780e/core/LuatOS-SoC_V1109_EC618.soc b/air780e/core/LuatOS-SoC_V1109_EC618.soc new file mode 100644 index 0000000..e0f2f43 Binary files /dev/null and b/air780e/core/LuatOS-SoC_V1109_EC618.soc differ diff --git a/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.binpkg b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.binpkg new file mode 100644 index 0000000..69661ad Binary files /dev/null and b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.binpkg differ diff --git a/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.ota b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.ota new file mode 100644 index 0000000..58be551 Binary files /dev/null and b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.ota differ diff --git a/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.soc b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.soc new file mode 100644 index 0000000..06c8430 Binary files /dev/null and b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1.0.0_LuatOS-SoC_V1109_EC618.soc differ diff --git a/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1109.1.0_LuatOS-SoC_EC618.bin b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1109.1.0_LuatOS-SoC_EC618.bin new file mode 100644 index 0000000..4c96b30 Binary files /dev/null and b/air780e/core/SOC量产及远程升级文件/EC618/air780e_forwarder_1109.1.0_LuatOS-SoC_EC618.bin differ diff --git a/air780e/core/SOC量产及远程升级文件/EC618/output.sota b/air780e/core/SOC量产及远程升级文件/EC618/output.sota new file mode 100644 index 0000000..4c96b30 Binary files /dev/null and b/air780e/core/SOC量产及远程升级文件/EC618/output.sota differ diff --git a/script/config.lua b/air780e/script/config.lua similarity index 92% rename from script/config.lua rename to air780e/script/config.lua index 3314e3c..36277fc 100644 --- a/script/config.lua +++ b/air780e/script/config.lua @@ -1,6 +1,9 @@ return { NOTIFY_TYPE = "uart", + -- DINGTALK_WEBHOOK = "", + UART_ID = 1, + STATUS_GPIO = 1, -- -- 定时查询流量间隔, 单位毫秒, 设置为 0 关闭 (建议检查 util_mobile.lua 文件中运营商号码和查询代码是否正确, 以免发错短信导致扣费, 收到查询结果短信发送通知会消耗流量) QUERY_TRAFFIC_INTERVAL = 0, @@ -15,7 +18,7 @@ return { NOTIFY_APPEND_MORE_INFO = true, -- -- 通知最大重发次数 - NOTIFY_RETRY_MAX = 20, + NOTIFY_RETRY_MAX = 50, -- -- 开启低功耗模式, USB 断开连接无法查看日志, RNDIS 网卡会断开 LOW_POWER_MODE = false, diff --git a/script/main.lua b/air780e/script/main.lua similarity index 51% rename from script/main.lua rename to air780e/script/main.lua index 7f61e72..99eb5e8 100644 --- a/script/main.lua +++ b/air780e/script/main.lua @@ -13,24 +13,16 @@ require "sysplus" wdt.init(9000) sys.timerLoopStart(wdt.feed, 3000) --- 设置 DNS -socket.setDNS(nil, 1, "119.29.29.29") -socket.setDNS(nil, 2, "223.5.5.5") - -- 设置 SIM 自动恢复(单位: 毫秒), 搜索小区信息间隔(单位: 毫秒), 最大搜索时间(单位: 秒) mobile.setAuto(1000 * 10) - --- 开启 IPv6 --- mobile.ipv6(true) - -- POWERKEY local button_last_press_time, button_last_release_time = 0, 0 gpio.setup( - 35, + 10, function() local current_time = mcu.ticks() -- 按下 - if gpio.get(35) == 0 then + if gpio.get(10) == gpio.LOW then button_last_press_time = current_time -- 记录最后一次按下时间 return end @@ -62,6 +54,8 @@ util_mobile = require "util_mobile" -- util_location = require "util_location" util_notify = require "util_notify" +-- 传输状态机 GPIO1 默认低 内部下拉 +gpio.setup(config.STATUS_GPIO, nil, gpio.PULLDOWN) -- 短信接收回调 sms.setNewSmsCb( function(sender_number, sms_content, m) @@ -76,60 +70,119 @@ sms.setNewSmsCb( sms.send(receiver_number, sms_content_to_be_sent) is_sms_ctrl = true end - local my_number = mobile.number(mobile.simid()) + -- local my_number = mobile.number(mobile.simid()) -- 发送通知 util_notify.add( { - "#" .. string.gsub(my_number,"+86","",1) .. "收到短信来自" .. sender_number, - "", - sms_content, - "", - "发件时间: " .. time, - "短信状态: " .. (is_sms_ctrl and "控制" or "正常") + from = sender_number, + sms = sms_content, + from_time = time, + sms_type = (is_sms_ctrl and "控制" or "正常"), + type = "sms" } ) end ) - +old_ststus = "" sys.taskInit( function() - -- 等待网络环境准备就绪 - sys.waitUntil("IP_READY") - util_netled.init() - -- 开机通知 if config.BOOT_NOTIFY then - util_notify.add("#开机通知") + util_notify.add( + { + sms = "设备开机", + type = "boot" + } + ) end - + -- 等待网络环境准备就绪 + -- sys.waitUntil("IP_READY") + sys.subscribe("IP_READY", function() + log.info("main", "IP_READY") + end) -- 定时查询流量 - if config.QUERY_TRAFFIC_INTERVAL and config.QUERY_TRAFFIC_INTERVAL >= 1000 * 60 then - sys.timerLoopStart(util_mobile.queryTraffic, config.QUERY_TRAFFIC_INTERVAL) - end + -- if config.QUERY_TRAFFIC_INTERVAL and config.QUERY_TRAFFIC_INTERVAL >= 1000 * 60 then + -- sys.timerLoopStart(util_mobile.queryTraffic, config.QUERY_TRAFFIC_INTERVAL) + -- end -- -- 定时基站定位 -- if config.LOCATION_INTERVAL and config.LOCATION_INTERVAL >= 1000 * 30 then -- sys.timerLoopStart(util_location.refresh, config.LOCATION_INTERVAL, 30) -- end - + -- 断网后会发一次这个消息 + sys.subscribe("SIM_IND", function(status, value) + -- log.info("上次状态", old_ststus,"当前状态",status,old_ststus ~= string.gsub(status, "%s+", "")) + if old_ststus ~= string.gsub(status, "%s+", "") then + old_ststus = string.gsub(status, "%s+", "") + -- status的取值有: + -- RDY SIM卡就绪, value为nil + -- NORDY 无SIM卡, value为nil + -- SIM_PIN 需要输入PIN, value为nil + -- GET_NUMBER 获取到电话号码(不一定有值), value为nil + -- SIM_WC SIM卡的写入次数统计,掉电归0, value为统计值 + if status == "RDY" then + log.info("main", "SIM卡就绪") + elseif status == "NORDY" then + log.info("main", "无SIM卡") + util_notify.add( + { + sms = "设备没安装SIM卡", + type = "boot" + } + ) + elseif status == "SIM_PIN" then + log.info("main", "需要输入PIN") + util_notify.add( + { + sms = "设备SIM卡需要输入PIN码解锁", + type = "boot" + } + ) + end + end + end) + -- 电源键短按发送测试通知 sys.subscribe( "POWERKEY_SHORT_PRESS", function() - util_notify.add("#ALIVE") + log.info("main", "POWERKEY_SHORT_PRESS") + util_notify.add( + { + from = "15264925507", + sms = "本地测试短信通知并没有实际收到短信", + from_time = "2024-03-14 00:00:00", + sms_type = "正常", + type = "sms" + } + ) end ) -- 电源键长按查询流量 - sys.subscribe("POWERKEY_LONG_PRESS", util_mobile.queryTraffic) + sys.subscribe( + "POWERKEY_LONG_PRESS", + function() + log.info("main", "POWERKEY_LONG_PRESS") + util_notify.add( + { + from = "15264925507", + sms = "长按本地测试短信通知并没有实际收到短信", + from_time = "2024-03-14 00:00:00", + sms_type = "正常", + type = "sms" + } + ) + end + ) -- 开启低功耗模式 if config.LOW_POWER_MODE then sys.wait(1000 * 15) log.warn("main", "即将关闭 usb 电源, 如需查看日志请在配置中关闭低功耗模式") sys.wait(1000 * 5) - gpio.setup(23, nil) - gpio.close(33) + -- gpio.setup(23, nil) + -- gpio.close(33) pm.power(pm.USB, false) -- 关闭 USB pm.power(pm.GPS, false) pm.power(pm.GPS_ANT, false) diff --git a/script/util_mobile.lua b/air780e/script/util_mobile.lua similarity index 100% rename from script/util_mobile.lua rename to air780e/script/util_mobile.lua diff --git a/script/util_netled.lua b/air780e/script/util_netled.lua similarity index 100% rename from script/util_netled.lua rename to air780e/script/util_netled.lua diff --git a/script/util_notify.lua b/air780e/script/util_notify.lua similarity index 68% rename from script/util_notify.lua rename to air780e/script/util_notify.lua index 26923e2..4833707 100644 --- a/script/util_notify.lua +++ b/air780e/script/util_notify.lua @@ -28,7 +28,7 @@ local notify = { local res_body = uart.write(config.UART_ID, msg) return 200, '', res_body end, - -- -- 发送到 dingtalk + -- 发送到 dingtalk -- ["dingtalk"] = function(msg) -- if config.DINGTALK_WEBHOOK == nil or config.DINGTALK_WEBHOOK == "" then -- log.error("util_notify", "未配置 `config.DINGTALK_WEBHOOK`") @@ -76,14 +76,15 @@ local notify = { -- end, } -local function append() - local msg = "\n" - +local function append(msg) + if type(msg) ~= "table" then + log.error("util_notify.append", "添加扩展信息错误", "参数类型错误", type(msg)) + return msg + end -- 本机号码 - local my_number = mobile.number(mobile.simid()) - local number = string.gsub(my_number,"+86","",1) + local number = mobile.number(mobile.simid()) if number then - msg = msg .. "\n本机号码: " .. number + msg.to = string.gsub(number,"+86","",1) end -- 开机时长 @@ -95,50 +96,21 @@ local function append() minutes = minutes % 60 local boot_time = string.format("%02d:%02d:%02d", hours, minutes, seconds) if ms >= 0 then - msg = msg .. "\n开机时长: " .. boot_time + msg.start_time = boot_time end -- 运营商 local oper = util_mobile.getOper(true) if oper ~= "" then - msg = msg .. "\n运营商: " .. oper + msg.oper = oper end -- 信号 local rsrp = mobile.rsrp() if rsrp ~= 0 then - msg = msg .. "\n信号: " .. rsrp .. "dBm" + msg.rsrp = rsrp end - -- -- 频段 - -- local band = util_mobile.getBand() - -- if band >= 0 then - -- msg = msg .. "\n频段: B" .. band - -- end - - -- 流量统计 - -- local uplinkGB, uplinkB, downlinkGB, downlinkB = mobile.dataTraffic() - -- uplinkB = uplinkGB * 1024 * 1024 * 1024 + uplinkB - -- downlinkB = downlinkGB * 1024 * 1024 * 1024 + downlinkB - -- local function formatBytes(bytes) - -- if bytes < 1024 then - -- return bytes .. "B" - -- elseif bytes < 1024 * 1024 then - -- return string.format("%.2fKB", bytes / 1024) - -- elseif bytes < 1024 * 1024 * 1024 then - -- return string.format("%.2fMB", bytes / 1024 / 1024) - -- else - -- return string.format("%.2fGB", bytes / 1024 / 1024 / 1024) - -- end - -- end - -- msg = msg .. "\n流量: ↑" .. formatBytes(uplinkB) .. " ↓" .. formatBytes(downlinkB) - - -- 位置 - -- local _, _, map_link = util_location.get() - -- if map_link ~= "" then - -- msg = msg .. "\n位置: " .. map_link -- 这里使用 U+00a0 防止换行 - -- end - return msg end @@ -150,12 +122,12 @@ function util_notify.send(msg, channel) log.info("util_notify.send", "发送通知", channel) -- 判断消息内容 msg - if type(msg) ~= "string" then + if type(msg) ~= "table" then log.error("util_notify.send", "发送通知失败", "参数类型错误", type(msg)) return true end - if msg == "" then - log.error("util_notify.send", "发送通知失败", "消息为空") + if msg.sms == "" then + log.error("util_notify.send", "发送通知失败", "消息内容为空") return true end @@ -167,11 +139,11 @@ function util_notify.send(msg, channel) -- 通知内容追加更多信息 if config.NOTIFY_APPEND_MORE_INFO then - msg = msg .. append() + msg = append(msg) end -- 发送通知 - local code, headers, body = notify[channel](msg) + local code, headers, body = notify[channel](json.encode(msg)) if code == nil then log.info("util_notify.send", "发送通知失败, 无需重发", "code:", code, "body:", body) return true @@ -182,9 +154,6 @@ function util_notify.send(msg, channel) return true end if code >= 200 and code < 500 then - -- http 2xx 成功 - -- http 3xx 重定向, 重发也不会成功 - -- http 4xx 客户端错误, 重发也不会成功 log.info("util_notify.send", "发送通知成功", "code:", code, "body:", body) return true end @@ -196,16 +165,13 @@ end -- @param msg 消息内容 -- @param channels 通知渠道 function util_notify.add(msg, channels) - if type(msg) == "table" then - msg = table.concat(msg, "\n") + if type(msg) ~= "table" then + log.info("util_notify.add", "添加消息失败", "参数错误") end - channels = channels or config.NOTIFY_TYPE - if type(channels) ~= "table" then channels = {channels} end - for _, channel in ipairs(channels) do table.insert(msg_queue, {channel = channel, msg = msg, retry = 0}) end @@ -220,25 +186,29 @@ local function poll() local item, result while true do -- 消息队列非空, 且网络已注册 - if next(msg_queue) ~= nil and mobile.status() == 1 then - log.debug("util_notify.poll", "轮询消息队列中, 当前队列长度:", #msg_queue) - - item = msg_queue[1] - table.remove(msg_queue, 1) - - if item.retry > (config.NOTIFY_RETRY_MAX or 100) then - log.error("util_notify.poll", "超过最大重发次数", "msg:", item.msg) - else - result = util_notify.send(item.msg, item.channel) - item.retry = item.retry + 1 - - if not result then - -- 发送失败, 移到队尾 - table.insert(msg_queue, item) - sys.wait(5000) + if next(msg_queue) ~= nil then + -- log.debug("util_notify.poll等待信号",gpio.get(config.STATUS_GPIO),gpio.HIGH) + if gpio.get(config.STATUS_GPIO) == gpio.HIGH then + log.debug("util_notify.poll", "轮询消息队列中, 当前队列长度:", #msg_queue) + item = msg_queue[1] + table.remove(msg_queue, 1) + if item.retry > (config.NOTIFY_RETRY_MAX or 100) then + log.error("util_notify.poll", "超过最大重发次数", "消息内容:", json.encode(item.msg)) + else + result = util_notify.send(item.msg, item.channel) + item.retry = item.retry + 1 + if not result then + -- 发送失败, 移到队尾 + table.insert(msg_queue, item) + sys.wait(50) + end + sys.wait(1000) end + sys.wait(50) + else + -- log.debug("util_notify.poll", "等待主机状态下发, 当前队列长度:", #msg_queue) + sys.wait(50) end - sys.wait(50) else sys.waitUntil("NEW_MSG", 1000 * 10) end diff --git a/core/LuatOS-SoC_V1105_EC618.soc b/core/LuatOS-SoC_V1105_EC618.soc deleted file mode 100644 index d4bfe31..0000000 Binary files a/core/LuatOS-SoC_V1105_EC618.soc and /dev/null differ diff --git a/core/LuatOS-SoC_V1105_EC618_FULL.soc b/core/LuatOS-SoC_V1105_EC618_FULL.soc deleted file mode 100644 index 68f2dae..0000000 Binary files a/core/LuatOS-SoC_V1105_EC618_FULL.soc and /dev/null differ diff --git a/core/LuatOS-SoC_V1105_EC618_TTS.soc b/core/LuatOS-SoC_V1105_EC618_TTS.soc deleted file mode 100644 index 02f58e6..0000000 Binary files a/core/LuatOS-SoC_V1105_EC618_TTS.soc and /dev/null differ diff --git a/core/LuatOS-SoC_V1106_EC618.soc b/core/LuatOS-SoC_V1106_EC618.soc deleted file mode 100644 index 4fa6028..0000000 Binary files a/core/LuatOS-SoC_V1106_EC618.soc and /dev/null differ diff --git a/core/LuatOS-SoC_V1106_EC618_RNDIS.soc b/core/LuatOS-SoC_V1106_EC618_RNDIS.soc deleted file mode 100644 index 317e7ce..0000000 Binary files a/core/LuatOS-SoC_V1106_EC618_RNDIS.soc and /dev/null differ diff --git a/esp32c3/core/LuatOS-SoC_V1007_ESP32C3_USB.soc b/esp32c3/core/LuatOS-SoC_V1007_ESP32C3_USB.soc new file mode 100644 index 0000000..e8df2bc Binary files /dev/null and b/esp32c3/core/LuatOS-SoC_V1007_ESP32C3_USB.soc differ diff --git a/esp32c3/script/air780_helper.lua b/esp32c3/script/air780_helper.lua new file mode 100644 index 0000000..ed9dd61 --- /dev/null +++ b/esp32c3/script/air780_helper.lua @@ -0,0 +1,129 @@ +local air780_helper = { + -- 是否正在读取串口数据 +} +air780_helper.reading = false +air780_helper.cs_index = 1 +local sys = require("sys") +local utils = require("utils") +local config = require("config") +local util_notify = require("util_notify") + +local uart_timeout = 100 + + +-- 使用UART 1接口与Air780E通信 +-- ESP32 <--> Air780E +-- 02(UART1_TX) <--> 31(UART1_RXD) +-- 03(UART1_RX) <--> 30(UART1_TXD) +local uart_setup_result = uart.setup(config.UART_ID, 115200, 8, 1) +if uart_setup_result ~= 0 then + log.error("air780_helper", "UART初始化失败,返回值:"..uart_setup_result..",ESP32将重启") + sys.wait(1000) + rtos.reboot() +else + log.info("air780_helper", "UART初始化成功") +end + +-- 串口读缓冲区 +local send_queue = {} + +-- 注册串口接收事件回调 +uart.on(config.UART_ID, "receive", function(id, length) + air780_helper.reading = true + local s = "" + repeat + -- log.error("air780_helper", "开始设备串口") + s = uart.read(id, length) + if #s > 0 then + table.insert(send_queue, s) + sys.timerStart(sys.publish, config.UART_TIMEOUT, "UART_READY") + -- sys.publish("UART_READY") + end + until s == "" +end) + +sys.subscribe("UART_READY", function() + led_helper.shut_working_led() + led_helper.blink_status_led(config.LED_BLINK_DURATION.initializing) + -- 拼接所有收到的数据 + local data = table.concat(send_queue) + log.info("air780_helper", '读取短信完成',data) + -- 读取完成后清空缓冲区 + utils.clear_table(send_queue) + air780_helper.reading = false + log.info("air780_helper", "去NEW_MSG处理消息类型和内容") + sys.publish("NEW_MSG",data) + + -- while #data > 0 do + -- air780_helper.reading = false + -- sys.publish(config.UART_NEW_MSG,data) + -- end +end) + +-- 收到串口新消息 添加到发送队列 +sys.subscribe("NEW_MSG", function(data) + if data == nil then + return + end + if #data == 0 then + return + end + local msg = json.decode(data) + if utils.table_len(msg) <= 0 then + return + end + if msg == nil then + return + end + local numbers = fskv.kv_get("numbers") + -- log.info("air780_helper", "测试numbers", json.encode(numbers),type(numbers)) + local cs_index = air780_helper.cs_index - 1 + if utils.is_empty(msg.sms) == false then + air780_helper.reading = false + msg.index = tostring(cs_index) + -- 是否屏蔽自检信息 + local disable_boot = fskv.kv_get("disable_boot") + if disable_boot == 1 and msg.type == "boot" then + return + end + -- 如果没有读到号码就是用设置的号码 + if utils.is_empty(msg.to) then + if fskv.kv_get("numbers") then + if numbers[cs_index] ~= nil then + msg.to = numbers[cs_index] + end + end + end + if fskv.kv_get("hostname") then + msg.hostname = fskv.kv_get("hostname") + else + msg.hostname = "weiguo_" .. wlan.getMac() + end + if utils.is_empty(msg.from) and msg.type == "boot" then + msg.from = nil + msg.sms = "收到设备通知:" .. msg.sms + msg.index = nil + end + util_notify.add(msg) + utils.clear_table(msg) + msg = nil + end +end) +-- 发送AT指令 +function air780_helper.send_at_command(command) + uart.write(config.UART_ID, command) + uart.write(config.UART_ID, "\r\n") + log.debug("air780_helper", "发送AT指令\""..command.."\"") +end +-- 发送AT指令并等待指定topic +function air780_helper.send_at_command_and_wait(command, topic_listen_to, timeout) + while true do + air780_helper.send_at_command(command) + local is_successful, r1, r2, r3 = sys.waitUntil(topic_listen_to, timeout or 1000) + if is_successful then + return r1, r2, r3 + end + end +end + +return air780_helper diff --git a/esp32c3/script/config.lua b/esp32c3/script/config.lua new file mode 100644 index 0000000..03f77ac --- /dev/null +++ b/esp32c3/script/config.lua @@ -0,0 +1,42 @@ +return { + -- 轮询间隔 + POLL_INTERVAL = 3000, + UART_ID = 1, + -- 串口读取延时 毫秒 + UART_TIMEOUT = 500, + -- 设备选择用GPIO 对应5个子设备 默认下拉 + CS_GPIO = {2,3,10,6,7}, + DNS_SERVERS = { + "114.114.114.114", + "223.5.5.5", + "8.8.8.8", + "1.1.1.1" + }, + LOG_LEVEL = log.LOG_INFO, + -- LED针脚 + LED = { + LED_A = 12, + LED_B = 13 + }, + -- LED闪烁间隔 + LED_BLINK_DURATION = { + working = 50, + initializing = 500 + }, + -- 通知最大重发次数 + NOTIFY_RETRY_MAX = 50, + -- 通知配置 ======================================== + NOTIFY_TYPE = { + "dingtalk", + "wecom" + }, + NOTIFY_CHANNEL = { + -- 钉钉机器人的webhook地址 + DINGTALK_WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=a6927dc518beeec0fe75e355e4e5cf42f1671e25741ce9da2904d3ed1432da85", + -- 企业微信机器人的webhook地址 + WECOM_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=89564a1c-50d4-4dea-b3f7-511c3213ade7", + -- inotify推送地址 + INOTIFY_URL = "", + } + +} diff --git a/esp32c3/script/led_helper.lua b/esp32c3/script/led_helper.lua new file mode 100644 index 0000000..6bc4f66 --- /dev/null +++ b/esp32c3/script/led_helper.lua @@ -0,0 +1,85 @@ +local led_helper = {} + +local sys = require("sys") +local config = require("config") + +local status_led = gpio.setup( + config.LED.LED_A, + 0, + gpio.PULLUP) + +local working_led = gpio.setup( + config.LED.LED_B, + 0, + gpio.PULLUP +) + +local function stop_and_clear_timer(timer) + sys.timerStop(timer) + timer = nil +end + +local is_status_led_on = true +local status_led_blink_timer = nil +function led_helper.blink_status_led(duration) + if status_led_blink_timer then + stop_and_clear_timer(status_led_blink_timer) + end + + status_led_blink_timer = sys.timerLoopStart( + function () + status_led(is_status_led_on and 1 or 0) + is_status_led_on = not is_status_led_on + end, + duration) +end + +local is_working_led_on = true +local working_led_blink_timer = nil +function led_helper.blink_working_led(duration) + if working_led_blink_timer then + stop_and_clear_timer(working_led_blink_timer) + end + + working_led_blink_timer = sys.timerLoopStart( + function () + working_led(is_working_led_on and 1 or 0) + is_working_led_on = not is_working_led_on + end, + duration + ) +end + +function led_helper.light_status_led() + if status_led_blink_timer then + stop_and_clear_timer(status_led_blink_timer) + end + + status_led(1) +end + +function led_helper.shut_status_led() + if status_led_blink_timer then + stop_and_clear_timer(status_led_blink_timer) + end + + status_led(0) +end + +function led_helper.light_working_led() + if working_led_blink_timer then + stop_and_clear_timer(working_led_blink_timer) + end + + working_led(1) +end + +function led_helper.shut_working_led() + if working_led_blink_timer then + stop_and_clear_timer(working_led_blink_timer) + end + + working_led(0) +end + +return led_helper diff --git a/esp32c3/script/main copy.lua b/esp32c3/script/main copy.lua new file mode 100644 index 0000000..0e68185 --- /dev/null +++ b/esp32c3/script/main copy.lua @@ -0,0 +1,98 @@ +PROJECT = "sms_forwarder_wifi" +VERSION = "1.0.0" + +local sys = require("sys") +local config = require("config") +local constants = require("constants") +local air780 = require("air780_helper") +local led_helper = require("led_helper") +local utils = require("utils") + +require("sysplus") +require("notification_helper") + +if wdt then + wdt.init(9000) + sys.timerLoopStart(wdt.feed, 3000) +end + +log.setLevel(config.log_level) +log.style(1) + +log.info("bsp", rtos.bsp()) +log.info("mem_sys", rtos.meminfo("sys")) +log.info("mem_lua", rtos.meminfo("lua")) + +-- 每秒完整GC一次,防止内存不足问题 +sys.timerLoopStart(function() + collectgarbage("collect") +end, 1000) + +led_helper.blink_status_led(constants.led_blink_duration.initializing) + +sys.taskInit(function() + local logging_tag = "main - 初始化网络" + log.info(logging_tag, "正在连接无线网络"..config.wifi.ssid) + wlan.init() + wlan.setMode(wlan.STATION) + wlan.connect(config.wifi["ssid"], config.wifi.password) + sys.waitUntil("IP_READY") + local ip_address = wlan.getIP() + log.info(logging_tag, "无线网络连接成功,IP地址:"..ip_address) + + for index, value in ipairs(config.dns_servers) do + log.info(logging_tag, "配置第"..index.."个DNS服务器为"..value) + socket.setDNS(nil, index, value) + end + + log.info(logging_tag, "等待时间同步") + sys.waitUntil("NTP_UPDATE") + log.info(logging_tag, "时间同步完成") +end) + +sys.taskInit(function () + local logging_tag = "main - 初始化Air780" + local at_command_result + + + if config.disable_netled then + log.info(logging_tag, "正在关闭NET灯闪烁") + air780.send_at_command("AT+CNETLIGHT=0") + end + + log.info(logging_tag, "初始化完成,等待新短信...") + + -- 测试短信推送,解除注释可开机时自动模拟推送一条,用于模块独立测试 + -- sys.publish( + -- constants.air780_message_topic_new_notification_request, + -- '10086', + -- '测试短信内容') + + led_helper.light_status_led() +end) + +-- 收到新消息派发推送 +sys.subscribe(constants.air780_message_topic_new_sms_received, +function(data) + led_helper.blink_working_led(constants.led_blink_duration.working) + sms_content = table.concat(data, "\n") + if sms_content then + log.info("main", "收到短信:"..sms_content) + sys.publish( + constants.air780_message_topic_new_notification_request, + sms_content) + led_helper.shut_working_led() + return + + else + log.info("main", "收到来自"..phone_number.."的短信,即将转发...") + sys.publish( + constants.air780_message_topic_new_notification_request, + sms_content) + led_helper.shut_working_led() + return + end +end) + + +sys.run() diff --git a/esp32c3/script/main.lua b/esp32c3/script/main.lua new file mode 100644 index 0000000..bd8d696 --- /dev/null +++ b/esp32c3/script/main.lua @@ -0,0 +1,234 @@ +PROJECT = "sms_to_wifi" +VERSION = "1.0.0" + +local sys = require("sys") +local config = require("config") +local led_helper = require("led_helper") +local utils = require("utils") +local util_notify = require("util_notify") +local air780_helper = require("air780_helper") +require("sysplus") + + +if wdt then + wdt.init(9000) + sys.timerLoopStart(wdt.feed, 3000) +end + +log.setLevel(config.LOG_LEVEL) +log.style(1) + +log.info("bsp", rtos.bsp()) +log.info("mem_sys", rtos.meminfo("sys")) +log.info("mem_lua", rtos.meminfo("lua")) +-- 协议头 +local http_header = {["Content-Type"] = "application/json"} +-- 每秒完整GC一次,防止内存不足问题 +sys.timerLoopStart(function() + collectgarbage("collect") +end, 1000) + +led_helper.blink_status_led(config.LED_BLINK_DURATION.initializing) +-- 初始化fskv非易失存储 +-- utils.clear_fskv() + +-- 初始化网络 +sys.taskInit(function() + local logging_tag = "main - 初始化网络" + fskv.init() + if fskv.kv_get("hostname") then + wlan.hostname(fskv.kv_get("hostname")) + else + local mac = wlan.getMac() + wlan.hostname("weiguo_" .. mac) + end + wlan.init() + if fskv.kv_get("wlan_ssid") then + wlan.connect(fskv.kv_get("wlan_ssid"), fskv.kv_get("wlan_passwd")) + return + end + + log.info(logging_tag, "未配置wifi信息,开始配网!") + wlan.smartconfig(wlan.AIRKISS) + log.info(logging_tag, "开始微信配网") + local ret, ssid, passwd = sys.waitUntil("SC_RESULT", 120*1000) -- 等2分钟 + if ret == true then + fskv.kv_set("wlan_ssid", ssid) + fskv.kv_set("wlan_passwd", passwd) + log.info(logging_tag, "获取到wifi信息正在重启",ssid, passwd) + sys.wait(1000) + -- 官方推荐重启 + -- rtos.reboot() + else + -- smartconfig配置失败,开始AP模式网页配网。 + log.info(logging_tag, "网页配网","请连接ssid:weiguo_开头的wifi,打开:http://192.168.4.1进行配网") + wlan.smartconfig(wlan.STOP) + -- 设置为AP模式, 广播ssid, 接收wifi客户端的链接 + sys.wait(1000) + wlan.setMode(wlan.AP) + wlan.createAP("weiguo_" .. mac, "", "192.168.4.1", "255.255.255.0", 6) + -- 监听80端口 + httpsrv.start(80, function(client, method, uri, headers, body) + log.info("httpsrv", method, uri, json.encode(headers), body) + if uri == "/" then + return 200, http_header, '{"code":"1","msg":"等待配网"}' + end + if uri == "/config_wifi" then + local wan_config = json.decode(body) + if wan_config.ssid and wan_config.password then + fskv.kv_set("wlan_ssid", wan_config.ssid) + fskv.kv_set("wlan_passwd", wan_config.password) + log.info(logging_tag, "配置成功,正在重启") + wlan.connect(wan_config.ssid, wan_config.password) + return 200, http_header, '{"code":"0","msg":"配置成功"}' + -- 官方推荐重启 + -- rtos.reboot() + else + return 200, http_header, '{"code":"1","msg":"配置失败"}' + end + end + return 404, {}, "Not Found" .. uri + end) + end +end) + + +-- 初始化设备CS针脚 +local function init_cs() + for index, value in ipairs(config.CS_GPIO) do + log.info("设备选择GPIO初始化", "配置第"..index.."个设备,GPIO:"..value,air780_helper.cs_index) + gpio.setup(value, 0, gpio.PULLDOWN) + end +end +-- 轮巡主函数 +local function loop_cs() + if air780_helper.reading then + return + end + if air780_helper.cs_index > #config.CS_GPIO then + air780_helper.cs_index = 1 + end + -- 统一拉低 + for index, value in ipairs(config.CS_GPIO) do + gpio.setup(value, 0, gpio.PULLDOWN) + end + log.info("轮巡设备", air780_helper.cs_index," GPIO",config.CS_GPIO[air780_helper.cs_index]) + gpio.set(config.CS_GPIO[air780_helper.cs_index], gpio.HIGH) + air780_helper.cs_index = air780_helper.cs_index + 1 +end + +sys.taskInit( + function() + -- 联网完成 初始化 + sys.subscribe("IP_READY", function(ip) + log.info("联网完成", "ip ready", ip) + for index, value in ipairs(config.DNS_SERVERS) do + log.info("联网完成", "配置第"..index.."个DNS服务器为"..value) + socket.setDNS(nil, index, value) + end + led_helper.shut_status_led() + led_helper.blink_working_led(config.LED_BLINK_DURATION.initializing) + -- 设置设备CS针脚 + init_cs() + -- 开启定时轮询 + sys.timerLoopStart(loop_cs, config.POLL_INTERVAL) + end) + log.info("bsp", rtos.bsp()) + log.info("mac", wlan.getMac()) + log.info("hostname", "weiguo_" .. wlan.getMac()) + -- 电源键短按发送测试通知 + sys.subscribe( + "POWERKEY_SHORT_PRESS", + function() + log.info("main", "POWERKEY_SHORT_PRESS") + util_notify.add( + { + from = "15264925507", + sms = "本地测试短信通知并没有实际收到短信", + from_time = "2024-03-14 00:00:00", + sms_type = "正常", + type = "sms" + } + ) + end + ) + end +) +-- 配置 +sys.taskInit( + function() + -- 监听80端口 + httpsrv.start(80, function(client, method, uri, headers, body) + log.info("httpsrv", method, uri, json.encode(headers),"b", body) + if uri == "/" then + local numbers = fskv.kv_get("numbers") + local ssid = fskv.kv_get("wlan_ssid") + local passwd = fskv.kv_get("wlan_passwd") + local config = { + code = 0, + msg = "完成", + numbers = fskv.kv_get("numbers"), + ssid = fskv.kv_get("wlan_ssid"), + passwd = fskv.kv_get("wlan_passwd"), + hostname = fskv.kv_get("hostname"), + disable_boot = fskv.kv_get("disable_boot"), + notify_type = config.NOTIFY_TYPE, + notify_channel = { + dingtalk = fskv.kv_get("dingtalk"), + wecom = fskv.kv_get("wecom") + } + } + return 200, http_header, json.encode(config) + end + if uri == "/config" then + local web_config = json.decode(body) + log.info("httpsrv", "配置数据", json.encode(web_config)) + if web_config.numbers ~= nil then + fskv.kv_set("numbers", web_config.numbers) + end + if web_config.hostname ~= nil then + fskv.kv_set("hostname", web_config.hostname) + end + if web_config.ssid ~= nil then + fskv.kv_set("wlan_ssid", web_config.ssid) + end + if web_config.passwd ~= nil then + fskv.kv_set("wlan_passwd", web_config.passwd) + end + if web_config.disable_boot == 1 or web_config.disable_boot == 0 then + fskv.kv_set("disable_boot", web_config.disable_boot) + end + if web_config.ssid ~= nil or web_config.passwd ~= nil then + log.info(logging_tag, "配置成功,正在联网") + wlan.connect(wan_config.ssid, wan_config.password) + -- 官方推荐重启 + -- rtos.reboot() + end + local notify_channel = { + dingtalk = fskv.kv_get("dingtalk"), + wecom = fskv.kv_get("wecom") + } + if web_config.notify_channel.dingtalk ~= nil then + fskv.kv_set("dingtalk", web_config.notify_channel.dingtalk) + end + if web_config.notify_channel.wecom ~= nil then + fskv.kv_set("wecom", web_config.notify_channel.wecom) + end + + + web_config.code = 0 + web_config.msg = "配置成功" + if web_config.ssid ~= nil or web_config.passwd ~= nil then + web_config.msg = "配置成功,设备即将重启!" + return 200, http_header, json.encode(web_config) + else + return 200, http_header, json.encode(web_config) + end + end + return 404, {}, "Not Found" .. uri + end) + end +) + +-- 启动 +sys.run() \ No newline at end of file diff --git a/esp32c3/script/util_notify.lua b/esp32c3/script/util_notify.lua new file mode 100644 index 0000000..5b08446 --- /dev/null +++ b/esp32c3/script/util_notify.lua @@ -0,0 +1,212 @@ +local config = require("config") +local utils = require("utils") +local util_notify = {} +local sys = require("sys") +-- 消息队列 +local msg_queue = {} + +local function urlencodeTab(params) + local msg = {} + for k, v in pairs(params) do + table.insert(msg, string.urlEncode(k) .. "=" .. string.urlEncode(v)) + table.insert(msg, "&") + end + table.remove(msg) + return table.concat(msg) +end + +local notify = { + -- 发送到 dingtalk + ["dingtalk"] = function(msg) + local dingtalk_webhook = fskv.kv_get("dingtalk") + if utils.is_empty(dingtalk_webhook) then + log.error("util_notify", "未配置钉钉机器人webhook地址") + return + end + local title = "" + if utils.is_empty(msg.from) == false then + title = "来自" .. msg.from .. "的短信" + else + title = "短信设备通知" + end + local text = "##### " .. msg.sms .. "\n" + if utils.is_empty(msg.from) == false then + text = text .. "- 发送号码:".. msg.from .."\n" + end + if utils.is_empty(msg.to) == false then + text = text .. "- 设备号码:".. msg.to .."\n" + end + if utils.is_empty(msg.hostname) == false then + text = text .. "- 设备名称:".. msg.hostname .."\n" + end + if utils.is_empty(msg.index) == false then + text = text .. "- 子设备ID:".. msg.index .."\n" + end + if utils.is_empty(msg.from_time) == false then + text = text .. "- 接收时间:".. msg.from_time .."\n" + end + if utils.is_empty(msg.oper) == false then + text = text .. "- 运营商:".. msg.oper .."\n" + end + if utils.is_empty(msg.rsrp) == false then + text = text .. "- 信号强度:".. msg.rsrp .."\n" + end + local header = { + ["Content-Type"] = "application/json; charset=utf-8" + } + local body = { + msgtype = "markdown", + markdown = { + title = title, + text = text + } + } + local json_data = json.encode(body) + -- LuatOS Bug, json.encode 会将 \n 转换为 \b + json_data = string.gsub(json_data, "\\b", "\\n") + + log.info("util_notify", "POST", dingtalk_webhook) + return utils.fetch(nil, "POST", dingtalk_webhook, header, json_data) + end, + + -- 发送到 wecom + ["wecom"] = function(msg) + local wecom_webhook = fskv.kv_get("wecom") + if utils.is_empty(wecom_webhook) then + log.error("util_notify", "未配置企业微信机器人地址") + return + end + + local header = { + ["Content-Type"] = "application/json; charset=utf-8" + } + local text = msg.sms .. "\n" + if utils.is_empty(msg.from) == false then + text = text .. "发送号码:".. msg.from .."\n" + end + if utils.is_empty(msg.to) == false then + text = text .. "设备号码:".. msg.to .."\n" + end + if utils.is_empty(msg.hostname) == false then + text = text .. "设备名称:".. msg.hostname .."\n" + end + if utils.is_empty(msg.index) == false then + text = text .. "设备ID:".. msg.index .."\n" + end + if utils.is_empty(msg.from_time) == false then + text = text .. "接收时间:".. msg.from_time .."\n" + end + if utils.is_empty(msg.oper) == false then + text = text .. "运营商:".. msg.oper .."\n" + end + if utils.is_empty(msg.rsrp) == false then + text = text .. "信号强度:".. msg.rsrp .."\n" + end + local body = { + msgtype = "text", + text = { + content = text + } + } + local json_data = json.encode(body) + -- LuatOS Bug, json.encode 会将 \n 转换为 \b + json_data = string.gsub(json_data, "\\b", "\\n") + + log.info("util_notify", "POST", wecom_webhook) + return utils.fetch(nil, "POST", wecom_webhook, header, json_data) + end +} + +--- 发送通知 +-- @param msg 消息内容 +-- @param channel 通知渠道 +-- @return true: 无需重发, false: 需要重发 +function util_notify.send(msg, channel) + log.info("util_notify.send", "发送通知", channel) + + -- 判断消息内容 msg + if type(msg) ~= "table" then + log.error("util_notify.send", "发送通知失败", "参数类型错误", type(msg)) + return true + end + if msg.sms == "" then + log.error("util_notify.send", "发送通知失败", "消息内容为空") + return true + end + + -- 判断通知渠道 channel + if channel and notify[channel] == nil then + log.error("util_notify.send", "发送通知失败", "未知通知渠道", channel) + return true + end + + -- 发送通知 + local code, headers, body = notify[channel](msg) + if code == nil then + log.info("util_notify.send", "发送通知失败, 无需重发", "code:", code, "body:", body) + return true + end + if code == -6 then + -- 发生在 url 过长时, 重发也不会成功 + log.info("util_notify.send", "发送通知失败, 无需重发", "code:", code, "body:", body) + return true + end + if code >= 200 and code < 500 then + log.info("util_notify.send", "发送通知成功", "code:", code, "body:", body) + return true + end + log.error("util_notify.send", "发送通知失败, 等待重发", "code:", code, "body:", body) + return false +end + +--- 添加到消息队列 +-- @param msg 消息内容 +-- @param channels 通知渠道 +function util_notify.add(msg, channels) + log.info("util_notify.add", "添加到消息队列") + if type(msg) ~= "table" then + log.info("util_notify.add", "添加消息失败", "参数错误") + end + channels = channels or config.NOTIFY_TYPE + if type(channels) ~= "table" then + channels = {channels} + end + for _, channel in ipairs(channels) do + table.insert(msg_queue, {channel = channel, msg = msg, retry = 0}) + end + sys.publish("ADD_NEW_MSG") + log.debug("util_notify.add", "添加到消息队列, 当前队列长度:", #msg_queue) +end + +-- 轮询消息队列 +-- 发送成功则从消息队列中删除 +-- 发送失败则等待下次轮询 +local function poll() + local item, result + while true do + -- 消息队列非空, 且网络已注册 + if next(msg_queue) ~= nil then + log.debug("util_notify.poll", "轮询消息队列中, 当前队列长度:", #msg_queue) + item = msg_queue[1] + table.remove(msg_queue, 1) + if item.retry > (config.NOTIFY_RETRY_MAX or 100) then + log.error("util_notify.poll", "超过最大重发次数", "消息内容:", json.encode(item.msg)) + else + result = util_notify.send(item.msg, item.channel) + item.retry = item.retry + 1 + if not result then + -- 发送失败, 移到队尾 + table.insert(msg_queue, item) + sys.wait(5000) + end + end + sys.wait(50) + else + sys.waitUntil("ADD_NEW_MSG", 1000 * 10) + end + end +end + +sys.taskInit(poll) + +return util_notify diff --git a/esp32c3/script/utils.lua b/esp32c3/script/utils.lua new file mode 100644 index 0000000..d1bc2aa --- /dev/null +++ b/esp32c3/script/utils.lua @@ -0,0 +1,65 @@ +local utils = {} +local sys = require("sys") +function utils.bool_to_number(value) + return value and 1 or 0 +end + +function utils.is_empty(str) + return str == nil or str == "" +end + +function utils.clear_table(table) + for i = 0, #table do + table[i] = nil + end +end +function utils.table_len(t) + local leng=0 + if type(t) ~= "table" then + return leng + end + for k, v in pairs(t) do + leng=leng+1 + end + return leng; +end +function utils.clear_fskv() + fskv.init() + fskv.clear() +end +-- 用于生成 http 请求的 id +local http_count = 0 + +--- 对 LuatOS-Air http.request 的封装 +-- @param timeout (number) 超时时间 +-- @param method (string) 请求方法 +-- @param url (string) 请求地址 +-- @param headers (table) 请求头 +-- @param body (string) 请求体 +-- @return (number, table, string) 状态码, 响应头, 响应体 +function utils.fetch(timeout, method, url, headers, body) + + local code,headers,body = http.request(method, url, headers,body).wait() + return code, headers, body + -- timeout = timeout or 1000 * 30 + + -- http_count = http_count + 1 + -- local id = "http_c" .. http_count .. "_r" .. math.random(1000, 9999) + + -- local function callback(res_result, res_prompt, res_headers, res_body) + -- sys.publish(id, {res_result, res_prompt, res_headers, res_body}) + -- end + + -- log.info("utils.fetch", "开始请求", "id:", id) + -- http.request(method, url, nil, headers, body, timeout, callback) + -- local result, data = sys.waitUntil(id, 1000 * 60) + -- log.info("utils.fetch", "请求结束", "id:", id) + + -- if result == false then + -- log.warn("utils.fetch", "请求超时", "id:", id) + -- return 501, {}, "utils.fetch 请求超时" + -- end + + -- return tonumber(data[2]) or -99, data[3] or {}, data[4] or "" +end +return utils \ No newline at end of file