diff --git a/README.md b/README.md index b4f5654..a64ffde 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,13 @@ USB连接Air780e,选择 `Luatools/resource/618_lua_lod/版本号` 目录下的 目前固件包含`gpio`、`uart`、`pwm`、`wdt`、`crypto`、`rtc`、`network`、`sntp`、`tls`、`wlan`、`pm`、`cjson`、`ntp`、`shell`、`dbg`。 +# 保活 API 说明 + +API 提供 GET 和 POST 请求支持。 + +- GET 请求返回 POST 请求存储的时间戳 +- POST 请求接收 `{ "expiry": "1732622763" }`,并存储 + # 致谢 本项目参考[低成本短信转发器](https://github.com/chenxuuu/sms_forwarding)而来,尤其是PDU相关代码,没有`chenxuuu`的这份项目和[50元内自制短信转发器(Air780E+ESP32C3)](https://www.chenxublog.com/2022/10/28/19-9-sms-forwarding-air780e-esp32c3.html)这篇文章,我不会这么快就完成开发。 diff --git a/air780_helper.lua b/air780_helper.lua index 671e692..bedd415 100644 --- a/air780_helper.lua +++ b/air780_helper.lua @@ -38,6 +38,10 @@ end) -- 发送AT指令 function air780_helper.send_at_command(command) uart.write(uart_id, command) + -- 如果是PDU模式下的短信内容,直接返回 + if command:sub(1, 6) == "001110" then + return + end uart.write(uart_id, "\r\n") log.debug("air780_helper", "发送AT指令\""..command.."\"") end @@ -80,6 +84,20 @@ sys.subscribe(constants.uart_ready_message, function() return end + -- 响应发送短信指令 + if current_line:find(">", 1, true) then + -- log.debug("air780_helper", "捕获到短信发送提示符") + sys.publish(constants.air780_helper_sms_send_ready) + return + end + + -- 响应发送短信成功 + if current_line:find("+CMGS:", 1, true) then + -- log.debug("air780_helper", "捕获到短信发送成功") + sys.publish(constants.air780_send_sms_success) + return + end + local urc = current_line:match("^%+(%w+)") if urc then -- URC上报 @@ -129,7 +147,7 @@ sys.subscribe(constants.uart_ready_message, function() end until #data == 0 end - end + end end end end) @@ -145,4 +163,36 @@ function air780_helper.send_at_command_and_wait(command, topic_listen_to, timeou end end +function air780_helper.topic_wait(topic, timeout) + local is_successful, r1, r2, r3 = sys.waitUntil(topic, timeout or 1000) + return is_successful +end + +function air780_helper.sent_sms(to, text) + local logging_tag = "air780_helper.sent_sms" + local data, len = pdu_helper.encode_pdu(to, text) + if not data or not len then + log.error(logging_tag, "短信编码失败") + return false + end + air780_helper.send_at_command("AT+CMGS=" .. len) + + -- 增加调试信息 + log.debug(logging_tag, "等待短信发送提示符") + local result = air780_helper.topic_wait(constants.air780_helper_sms_send_ready, 3000) + if not result then + log.error(logging_tag, "短信发送失败,AT+CMGS=" .. len .. " 超时") + return false + end + + air780_helper.send_at_command(data .. "\x1A") + local result = air780_helper.topic_wait(constants.air780_send_sms_success, 5000) + if result then + log.info(logging_tag, "短信发送成功") + return true + else + log.error(logging_tag, "短信发送失败") + return false + end +end return air780_helper diff --git a/config.lua b/config.lua index d88c479..9571982 100644 --- a/config.lua +++ b/config.lua @@ -18,15 +18,22 @@ config.disable_rndis = true -- 而1156版本AT固件第一次检测有概率检测不到SIM卡,需要重试 config.retry_sim_detection = true +-- 续期 API +config.renew_api = "https://api.example.com/sim/expiry" +-- 续期间隔 +config.renew_day = 180 +-- 续期检查间隔,单位小时。为 0 时禁用 +config.renew_check_interval = 0 +-- 续期接收号码 +config.renew_number = "+8613881388138" +-- 续期短信内容 +config.renew_content = "注意余额" + config.wifi = { { ssid = "Wi-Fi名", password = "Wi-Fi密码", }, - -- { - -- ssid = "", - -- password = "", - -- } } -- 手动配置DNS服务器 @@ -73,7 +80,7 @@ config.notification_channel = { -- telegram 机器人 telegram = { enabled = false, - -- Webhook地址 + -- Webhook地址, https://api.telegram.org/bot/sendMessage webhook_url = "", -- chat_id, 通过 https://api.telegram.org/bot/getUpdates 获取 chat_id = "" diff --git a/constants.lua b/constants.lua index 2d778fd..6869e45 100644 --- a/constants.lua +++ b/constants.lua @@ -32,5 +32,7 @@ constants.air780_message_topic_new_message_notification_configured = "NEW_MESSAG constants.air780_message_topic_network_connected = "NETWORK_CONNECTED" constants.air780_message_topic_new_sms_received = "NEW_SMS_RECEIVED" constants.air780_message_topic_new_notification_request = "NEW_NOTIFICATION_REQUEST" +constants.air780_helper_sms_send_ready = "SMS_SEND_READY" +constants.air780_send_sms_success = "SMS_SENT_SUCCESSFULLY" return constants diff --git a/main.lua b/main.lua index d4df959..9480c8d 100644 --- a/main.lua +++ b/main.lua @@ -110,7 +110,7 @@ sys.taskInit(function () log.info(logging_tag, "GPRS已附着") break else - log.info(logging_tag, "GPRS未附着,将在5秒后重新检查") + log.info(logging_tag, "GPRS未附着, 将在5秒后重新检查") sys.wait(5000) end end @@ -131,6 +131,16 @@ sys.taskInit(function () led_helper.light_status_led() end) +sys.taskInit(function() + renew = require("renew") + sys.waitUntil(constants.air780_message_topic_new_message_notification_configured) + sys.waitUntil("NTP_UPDATE") + if config.renew_check_interval > 0 then + log.info("renew", "自动续期启动") + renew.renew() + end +end) + --[[ long_sms_buffer = { [phone_number] = { diff --git a/pdu_helper.lua b/pdu_helper.lua index fac935d..3248a36 100644 --- a/pdu_helper.lua +++ b/pdu_helper.lua @@ -363,4 +363,18 @@ function pdu_helper.decode_pdu(pdu, len) return sender_number, sms_content_in_utf8, sms_receive_time, long_sms, total_message_count, current_idx, sms_id end +-- 生成PDU短信编码 +-- 仅支持单条短信,传入数据为utf8编码 +-- 返回值为pdu编码与长度 +function pdu_helper.encode_pdu(num,data) + data = utf8_to_ucs2(data):toHex() + local numlen, datalen, pducnt, pdu, pdulen, udhi = string.format("%02X", #num), #data / 2, 1, "", "", "" + if datalen > 140 then--短信内容太长啦 + data = data:sub(1, 140 * 2) + end + datalen = string.format("%02X", datalen) + pdu = "001110" .. numlen .. number_to_bcd_number(num) .. "000800" .. datalen .. data + return pdu, #pdu // 2 - 1 +end + return pdu_helper diff --git a/renew.lua b/renew.lua new file mode 100644 index 0000000..ee386b8 --- /dev/null +++ b/renew.lua @@ -0,0 +1,88 @@ +local renew = {} + +local sys = require("sys") +local sysplus = require("sysplus") +local air780 = require("air780_helper") +local config = require("config") + +-- 获取到期时间 +-- GET https://api.example.com/sim/expiry 1732622763 +function renew.get_expire_date() + local code, headers, body = http.request("GET", config.renew_api, {["User-Agent"] = "LuatOS/1.0.0 (Lua; ESP32; Air780)"}, nil, {ipv6=true}).wait() + + -- 如果 code 为负数,说明请求失败 + if code < 0 or code ~= 200 then + log.error("renew", "获取到期时间", "code=" .. code) + return nil + end + + -- 尝试将 body 转换为数字 + local date = tonumber(body) + if not date then + log.error("renew", "获取到期时间", "未知时间格式:" .. tostring(body)) + return nil + end + + return date +end + +-- 更新到期时间 +-- POST https://api.example.com/sim/expiry { "expiry": "1732622763" } +function renew.update_expire_date(date) + local body = { expiry = date } + local code, headers, body = http.request("POST", config.renew_api, {["Content-Type"] = "application/json", ["User-Agent"] = "LuatOS/1.0.0 (Lua; ESP32; Air780)"}, json.encode(body), {ipv6=true}).wait() + if code ~= 200 then + log.error("renew", "更新到期时间", "code=" .. code) + return false + end + return true +end + +-- 发送短信 +-- 发送短信后,更新到期时间,到期时间为当前时间戳 + config.renew_day +function renew.send_sms() + if air780.sent_sms(config.renew_number, config.renew_content) then + log.debug("renew", "sent_sms success") + if renew.update_expire_date(os.time() + config.renew_day * 24 * 60 * 60) then + log.debug("renew", "更新到期时间成功") + return true + else + log.error("renew", "更新到期时间失败") + return false + end + else + log.error("renew", "发送短信失败") + return false + end + return true +end + +-- 保活 +-- 如果 date 为空,则发送短信 +-- 如果当前时间戳大于等于 date,则发送短信 +-- 检查间隔为 config.renew_check_interval 单位小时 +function renew.renew() + while true do + local date = renew.get_expire_date() + if date == nil then + log.error("renew", "get_expire_date", "get_expire_date failed") + goto continue + end + if date == "" or os.time() >= date then + log.debug("renew", "已到期,开始保活") + if renew.send_sms() then + log.debug("renew", "保活成功") + else + log.error("renew", "保活失败") + end + + goto continue + end + log.debug("renew", "时间未到,无需保活") + + ::continue:: + sys.wait(config.renew_check_interval * 60 * 60 * 1000) + end +end + +return renew