commit 0857ca6b60b37f46bfb2a75e70894b3f860ed03b Author: Mizore Date: Sat Jan 14 15:48:13 2023 +0800 :tada: 初始化项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec3edd6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.luatide +luatide_project.json \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cd2c79e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Mizore + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LuatOS-SoC_V1003_EC618_TEMP_20220106_174000.soc b/LuatOS-SoC_V1003_EC618_TEMP_20220106_174000.soc new file mode 100644 index 0000000..262b3e8 Binary files /dev/null and b/LuatOS-SoC_V1003_EC618_TEMP_20220106_174000.soc differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..18e3bf4 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Air780E 短信转发 + +## 使用方法 + +### 1. 按注释修改 `script/config.lua` 配置文件 + +### 2. 烧录脚本 + +根据 [air780e.cn](http://air780e.cn) 官方指引下载 LuaTools 并写入 `script` 目录下文件 diff --git a/script/config.lua b/script/config.lua new file mode 100644 index 0000000..5d68b41 --- /dev/null +++ b/script/config.lua @@ -0,0 +1,23 @@ +return { + -- 通知类型 telegram, pushdeer, bark + NOTIFY_TYPE = "pushdeer", + -- + -- telegram 通知配置, https://github.com/0wQ/telegram-notify + TELEGRAM_PROXY_API = "", + TELEGRAM_TOKEN = "", + TELEGRAM_CHAT_ID = "", + -- + -- pushdeer 通知配置, https://www.pushdeer.com/ + PUSHDEER_API = "https://api2.pushdeer.com/message/push", + PUSHDEER_KEY = "", + -- + -- bark 通知配置, https://github.com/Finb/Bark + BARK_API = "https://api.day.app", + BARK_KEY = "", + -- + -- 定时查询流量间隔, 单位毫秒, 设置为 0 关闭 + QUERY_TRAFFIC_INTERVAL = 1000 * 60 * 60 * 6, + -- + -- 定时基站定位间隔, 单位毫秒, 设置为 0 关闭 + LOCATION_INTERVAL = 1000 * 60 * 30 +} diff --git a/script/lbsLoc.lua b/script/lbsLoc.lua new file mode 100644 index 0000000..8f992d2 --- /dev/null +++ b/script/lbsLoc.lua @@ -0,0 +1,330 @@ +--[[ +@module lbsLoc +@summary lbsLoc 发送基站定位请求 +@version 1.0 +@date 2022.12.16 +@author luatos +@usage +--注意:因使用了sys.wait()所有api需要在协程中使用 +--用法实例 +PRODUCT_KEY = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi" +local lbsLoc = require("lbsLoc") +local function reqLbsLoc() + lbsLoc.request(getLocCb) +end +-- 功能:获取基站对应的经纬度后的回调函数 +-- 参数:-- result:number类型,0表示成功,1表示网络环境尚未就绪,2表示连接服务器失败,3表示发送数据失败,4表示接收服务器应答超时,5表示服务器返回查询失败;为0时,后面的5个参数才有意义 + -- lat:string类型,纬度,整数部分3位,小数部分7位,例如031.2425864 + -- lng:string类型,经度,整数部分3位,小数部分7位,例如121.4736522 + -- addr:目前无意义 + -- time:string类型或者nil,服务器返回的时间,6个字节,年月日时分秒,需要转为十六进制读取 + -- 第一个字节:年减去2000,例如2017年,则为0x11 + -- 第二个字节:月,例如7月则为0x07,12月则为0x0C + -- 第三个字节:日,例如11日则为0x0B + -- 第四个字节:时,例如18时则为0x12 + -- 第五个字节:分,例如59分则为0x3B + -- 第六个字节:秒,例如48秒则为0x30 + -- locType:numble类型或者nil,定位类型,0表示基站定位成功,255表示WIFI定位成功 +function getLocCb(result, lat, lng, addr, time, locType) + log.info("testLbsLoc.getLocCb", result, lat, lng) + -- 获取经纬度成功 + if result == 0 then + log.info("服务器返回的时间", time:toHex()) + log.info("定位类型,基站定位成功返回0", locType) + end + sys.timerStart(lbsLoc,20000) +end +reqLbsLoc() +]] + +local lbsLoc = {} +local d1Name = "D1_TASKL" +--- 阻塞等待网卡的网络连接上,只能用于任务函数中 +-- @string 任务标志 +-- @int 超时时间,如果==0或者空,则没有超时一致等待 +-- @... 其他参数和socket.linkup一致 +-- @return 失败或者超时返回false 成功返回true +local function waitLink(taskName, timeout, ...) + local is_err, result = socket.linkup(...) + if is_err then + return false + end + if not result then + result = sys_wait(taskName, socket.LINK, timeout) + else + return true + end + if type(result) == 'table' and result[2] == 0 then + return true + else + return false + end +end + +--- 阻塞等待IP或者域名连接上,如果加密连接还要等握手完成,只能用于任务函数中 +-- @string 任务标志 +-- @int 超时时间,如果==0或者空,则没有超时一致等待 +-- @... 其他参数和socket.connect一致 +-- @return 失败或者超时返回false 成功返回true +local function connect(taskName,timeout, ... ) + local is_err, result = socket.connect(...) + if is_err then + return false + end + if not result then + result = sys_wait(taskName, socket.ON_LINE, timeout) + else + return true + end + if type(result) == 'table' and result[2] == 0 then + return true + else + return false + end +end + +--- 阻塞等待数据发送完成,只能用于任务函数中 +-- @string 任务标志 +-- @int 超时时间,如果==0或者空,则没有超时一致等待 +-- @... 其他参数和socket.tx一致 +-- @return +-- @boolean 失败或者超时返回false,缓冲区满了或者成功返回true +-- @boolean 缓存区是否满了 +local function tx(taskName,timeout, ...) + local is_err, is_full, result = socket.tx(...) + if is_err then + return false, is_full + end + if is_full then + return true, true + end + if not result then + result = sys_wait(taskName, socket.TX_OK, timeout) + else + return true, is_full + end + if type(result) == 'table' and result[2] == 0 then + return true, false + else + return false, is_full + end +end + +--- ASCII字符串 转化为 BCD编码格式字符串(仅支持数字) +-- @string inStr 待转换字符串 +-- @number destLen 转换后的字符串期望长度,如果实际不足,则填充F +-- @return string data,转换后的字符串 +-- @usage +local function numToBcdNum(inStr,destLen) + local l,t,num = string.len(inStr or ""),{} + + destLen = destLen or (inStr:len()+1)/2 + + for i=1,l,2 do + num = tonumber(inStr:sub(i,i+1),16) + + if i==l then + num = 0xf0+num + else + num = (num%0x10)*0x10 + (num-(num%0x10))/0x10 + end + + table.insert(t,num) + end + + local s = string.char(unpack(t)) + + l = string.len(s) + if l < destLen then + s = s .. string.rep("\255",destLen-l) + elseif l > destLen then + s = string.sub(s,1,destLen) + end + + return s +end + +--- BCD编码格式字符串 转化为 号码ASCII字符串(仅支持数字) +-- @string num 待转换字符串 +-- @return string data,转换后的字符串 +-- @usage +local function bcdNumToNum(num) + local byte,v1,v2 + local t = {} + + for i=1,num:len() do + byte = num:byte(i) + v1,v2 = bit.band(byte,0x0f),bit.band(bit.rshift(byte,4),0x0f) + + if v1 == 0x0f then break end + table.insert(t,v1) + + if v2 == 0x0f then break end + table.insert(t,v2) + end + + return table.concat(t) +end + + +local function netCB(msg) + log.info("未处理消息", msg[1], msg[2], msg[3], msg[4]) +end + + +local function enCellInfo(s) + local ret,t,mcc,mnc,lac,ci,rssi,k,v,m,n,cntrssi = "",{} + for k,v in pairs(s) do + mcc,mnc,lac,ci,rssi = v.mcc,v.mnc,v.tac,v.cid,((v.rsrq + 144) >31) and 31 or (v.rsrq + 144) + local handle = nil + for k,v in pairs(t) do + if v.lac == lac and v.mcc == mcc and v.mnc == mnc then + if #v.rssici < 8 then + table.insert(v.rssici,{rssi=rssi,ci=ci}) + end + handle = true + break + end + end + if not handle then + table.insert(t,{mcc=mcc,mnc=mnc,lac=lac,rssici={{rssi=rssi,ci=ci}}}) + end + log.info("rssi、mcc、mnc、lac、ci", rssi,mcc,mnc,lac,ci) + end + for k,v in pairs(t) do + ret = ret .. pack.pack(">HHb",v.lac,v.mcc,v.mnc) + for m,n in pairs(v.rssici) do + cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici-1) or 0),5),n.rssi) + ret = ret .. pack.pack(">bi",cntrssi,n.ci) + end + end + return string.char(#t)..ret +end + +local function enWifiInfo(tWifi) + local ret,cnt,k,v = "",0 + if tWifi then + for k,v in pairs(tWifi) do + log.info("lbsLoc.enWifiInfo",k,v) + ret = ret..pack.pack("Ab",(k:gsub(":","")):fromHex(),(v<0) and (v+255) or v) + cnt = cnt+1 + end + end + return string.char(cnt)..ret +end + +local function enMuid() --获取模块MUID + local muid = mobile.muid() + return string.char(muid:len())..muid +end + +local function trans(str) + local s = str + if str:len()<10 then + s = str..string.rep("0",10-str:len()) + end + + return s:sub(1,3).."."..s:sub(4,10) +end + + +local function taskClient(cbFnc, reqAddr, timeout, productKey, host, port,reqTime, reqWifi) + while mobile.status() == 0 do + if not sys.waitUntil("IP_READY", timeout) then return cbFnc(1) end + end + local retryCnt = 0 + sys.wait(3000) + local reqStr = pack.pack("bAbAAAAA", productKey:len(), productKey, + (reqAddr and 2 or 0) + (reqTime and 4 or 0) + 8 +(reqWifi and 16 or 0) + 32, "", + numToBcdNum(mobile.imei()), enMuid(), + enCellInfo(mobile.getCellInfo()), + enWifiInfo(reqWifi)) + log.info("reqStr", reqStr:toHex()) + local rx_buff = zbuff.create(17) + -- sys.wait(5000) + while true do + netc = socket.create(nil, d1Name) -- 创建socket对象 + if not netc then cbFnc(6) return end -- 创建socket失败 + socket.debug(netc, false) + socket.config(netc, nil, true, nil) + waitLink(d1Name, 0, netc) + local result = connect(d1Name, 15000, netc, host, port) + if result then + while true do + log.info(" lbsloc socket_service connect true") + sys.wait(2000); + local result, _ = tx(d1Name, 0, netc, reqStr) ---发送数据 + if result then + sys.wait(5000); + local is_err, param, _, _ = socket.rx(netc, rx_buff) -- 接收数据 + log.info("是否接收和数据长度", not is_err, param) + if not is_err then -- 如果接收成功 + socket.close(netc) -- 关闭连接 + socket.release(netc) + local read_buff = rx_buff:toStr(0, param) + rx_buff:clear() + log.info("lbsLoc receive", read_buff:toHex()) + if read_buff:len() >= 11 and(read_buff:byte(1) == 0 or read_buff:byte(1) == 0xFF) then + local locType = read_buff:byte(1) + cbFnc(0, trans(bcdNumToNum(read_buff:sub(2, 6))), + trans(bcdNumToNum(read_buff:sub(7, 11))), reqAddr and + read_buff:sub(13, 12 + read_buff:byte(12)) or nil, + read_buff:sub(reqAddr and (13 + read_buff:byte(12)) or 12, -1), + locType) + else + log.warn("lbsLoc.query", "根据基站查询经纬度失败") + if read_buff:byte(1) == 2 then + log.warn("lbsLoc.query","main.lua中的PRODUCT_KEY和此设备在iot.openluat.com中所属项目的ProductKey必须一致,请去检查") + else + log.warn("lbsLoc.query","基站数据库查询不到所有小区的位置信息") + log.warn("lbsLoc.query","在trace中向上搜索encellinfo,然后在电脑浏览器中打开http://bs.openluat.com/,手动查找encellinfo后的所有小区位置") + log.warn("lbsLoc.query","如果手动可以查到位置,则服务器存在BUG,直接向技术人员反映问题") + log.warn("lbsLoc.query","如果手动无法查到位置,则基站数据库还没有收录当前设备的小区位置信息,向技术人员反馈,我们会尽快收录") + end + cbFnc(5) + end + return + else + socket.close(netc) + socket.release(netc) + retryCnt = retryCnt+1 + if retryCnt>=3 then return cbFnc(4) end + break + end + else + socket.close(netc) + socket.release(netc) + retryCnt = retryCnt+1 + if retryCnt>=3 then return cbFnc(3) end + break + end + end + else + socket.close(netc) + socket.release(netc) + retryCnt = retryCnt + 1 + if retryCnt >= 3 then return cbFnc(2) end + end + end +end + + +--[[ +发送基站/WIFI定位请求(仅支持中国区域的位置查询) +@api lbsLoc.request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi) +@function cbFnc 用户回调函数,回调函数的调用形式为:cbFnc(result,lat,lng,addr,time,locType) +@bool reqAddr 是否请求服务器返回具体的位置字符串信息,目前此功能不完善,参数可以传nil +@number timeout 请求超时时间,单位毫秒,默认20000毫秒 +@string productKey IOT网站上的产品证书,如果在main.lua中定义了PRODUCT_KEY变量,则此参数可以传nil +@string host 服务器域名,此参数可选,目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil +@string port 服务器端口,此参数可选,目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil +@bool reqTime 是否需要服务器返回时间信息,true返回,false或者nil不返回,此参数可选,目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil +@table reqWifi 搜索到的WIFI热点信息(MAC地址和信号强度),如果传入了此参数,后台会查询WIFI热点对应的经纬度,此参数格式如下: +{["1a:fe:34:9e:a1:77"] = -63,["8c:be:be:2d:cd:e9"] = -81,["20:4e:7f:82:c2:c4"] = -70,} +@return nil +]] +function lbsLoc.request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi) + sysplus.taskInitEx(taskClient, d1Name, netCB, cbFnc,reqAddr or nil,timeout or 20000,productKey or _G.PRODUCT_KEY,host or "bs.openluat.com",port or "12411",reqTime,reqWifi) +end + +return lbsLoc \ No newline at end of file diff --git a/script/main.lua b/script/main.lua new file mode 100644 index 0000000..b32e570 --- /dev/null +++ b/script/main.lua @@ -0,0 +1,109 @@ +PROJECT = "air780e_forwarder" +VERSION = "1.0.0" + +log.setLevel("DEBUG") +log.info("main", PROJECT, VERSION) + +sys = require "sys" +sysplus = require "sysplus" +require "sysplus" + +-- 添加硬狗防止程序卡死, 在支持的设备上启用这个功能 +if wdt then + -- 初始化 watchdog 设置为 9s + wdt.init(9000) + -- 3s 喂一次狗 + sys.timerLoopStart(wdt.feed, 3000) +end + +-- 设置 DNS +socket.setDNS(nil, 1, "119.29.29.29") +socket.setDNS(nil, 2, "223.5.5.5") + +-- 设置 SIM 自动恢复, 搜索小区信息间隔, 最大搜索时间 +mobile.setAuto(1000 * 10, 1000 * 60, 1000 * 5) + +-- POWERKEY +local powerkey_timer = 0 +gpio.setup( + 35, + function() + local powerkey_state = gpio.get(35) + if powerkey_state == 0 then + powerkey_timer = os.time() + else + if powerkey_timer == 0 then + return + end + local time = os.time() - powerkey_timer + if time >= 2 then + log.info("POWERKEY_LONG_PRESS", time) + sys.publish("POWERKEY_LONG_PRESS") + else + log.info("POWERKEY_SHORT_PRESS", time) + sys.publish("POWERKEY_SHORT_PRESS") + end + powerkey_timer = 0 + end + end, + gpio.PULLUP, + gpio.FALLING +) + +config = require "config" +util_netled = require "util_netled" +util_mobile = require "util_mobile" +util_location = require "util_location" +util_notify = require "util_notify" + +-- 短信回调 +sms.setNewSmsCb( + function(num, txt, metas) + log.info("smsCallback", num, txt, metas and json.encode(metas) or "") + util_netled.blink(200, 200, 1000) + util_notify.send({txt, "", "发件人号码: " .. num, "#SMS"}) + end +) + +sys.taskInit( + function() + -- 等待网络环境准备就绪 + sys.waitUntil("IP_READY") + + util_netled.blink(200, 200, 5000) + + -- 开机基站定位 + util_location.getCoord( + function() + log.info("publish", "COORD_INIT_DONE") + sys.publish("COORD_INIT_DONE") + end + ) + sys.waitUntil("COORD_INIT_DONE", 1000 * 20) + + -- 开机通知 + util_notify.send("#BOOT") + + -- 定时查询流量 + 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 * 10 then + sys.timerLoopStart(util_location.getCoord, config.LOCATION_INTERVAL) + end + + -- 电源键短按发送测试通知 + sys.subscribe( + "POWERKEY_SHORT_PRESS", + function() + util_notify.send("#ALIVE") + end + ) + -- 电源键长按查询流量 + sys.subscribe("POWERKEY_LONG_PRESS", util_mobile.queryTraffic) + end +) + +sys.run() diff --git a/script/util_location.lua b/script/util_location.lua new file mode 100644 index 0000000..e275312 --- /dev/null +++ b/script/util_location.lua @@ -0,0 +1,50 @@ +local lbsLoc = require "lbsLoc" + +local util_location = {} + +local last_lat, last_lng = 0, 0 +local last_time = 0 + +-- 获取坐标 +function util_location.getCoord(callback, type, wifi, timeout) + local is_callback = callback ~= nil + if callback == nil then + callback = function() + end + end + + sys.taskInit( + function() + local current_time = os.time() + if not is_callback then + if current_time - last_time < 30 then + log.info("util_location.getCoord", "距离上次定位时间太短", current_time - last_time) + return + end + sys.wait(1000) + end + last_time = current_time + lbsLoc.request( + function(result, lat, lng, addr, time, locType) + log.info("util_location.getCoord", result, lat, lng, locType) + if result == 0 and lat and lng then + last_lat, last_lng = lat, lng + return callback(lat, lng) + end + return callback(last_lat, last_lng) + end, + nil, + timeout, + "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K", + nil, + nil, + nil, + wifi + ) + end + ) + + return last_lat, last_lng +end + +return util_location diff --git a/script/util_mobile.lua b/script/util_mobile.lua new file mode 100644 index 0000000..e210c62 --- /dev/null +++ b/script/util_mobile.lua @@ -0,0 +1,54 @@ +local util_mobile = {mcc = 99, mnc = 99, band = 99} + +-- 查询流量代码 +local trafficCode = { + CU = {"10010", "1071"}, + CM = {"10086", "cxll"}, + CT = {"10001", "108"} +} + +-- 获取运营商 +function util_mobile.getOper(is_zh) + if util_mobile.mcc ~= 460 then + return "" + end + + if util_mobile.mnc == 1 then + return is_zh and "中国联通" or "CU" + end + + if util_mobile.mnc == 0 then + return is_zh and "中国移动" or "CM" + end + + if util_mobile.mnc == 11 then + return is_zh and "中国电信" or "CT" + end + + if util_mobile.mnc == 15 then + return is_zh and "中国广电" or "CB" + end + + return "" +end + +-- 发送查询流量短信 +function util_mobile.queryTraffic() + local oper = util_mobile.getOper() + if oper and trafficCode[oper] then + sms.send(trafficCode[oper][1], trafficCode[oper][2]) + else + log.warn("queryTraffic", "查询流量代码未配置") + end +end + +sys.subscribe( + "CELL_INFO_UPDATE", + function() + local info = mobile.getCellInfo()[1] or {} + util_mobile.mcc, util_mobile.mnc, util_mobile.band = info.mcc, info.mnc, info.band + log.info("cell", "mcc:", util_mobile.mcc, "mnc:", util_mobile.mnc, "band:", util_mobile.band) + end +) + +return util_mobile diff --git a/script/util_netled.lua b/script/util_netled.lua new file mode 100644 index 0000000..6e87996 --- /dev/null +++ b/script/util_netled.lua @@ -0,0 +1,36 @@ +local util_netled = {} + +local netled = gpio.setup(27, 0, gpio.PULLUP) + +local netled_default_duration = 200 +local netled_default_interval = 2000 + +local netled_duration = netled_default_duration +local netled_interval = netled_default_interval + +sys.taskInit( + function() + while true do + netled(1) + sys.wait(netled_duration) + netled(0) + sys.wait(netled_interval) + end + end +) + +function util_netled.blink(duration, interval, restore) + netled_duration = duration or netled_default_duration + netled_interval = interval or netled_default_interval + if restore then + sys.timerStart( + function() + netled_duration = netled_default_duration + netled_interval = netled_default_interval + end, + restore + ) + end +end + +return util_netled diff --git a/script/util_notify.lua b/script/util_notify.lua new file mode 100644 index 0000000..6cf87d0 --- /dev/null +++ b/script/util_notify.lua @@ -0,0 +1,151 @@ +local util_notify = {} + +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 + +-- 发送到 telegram +local function notifyToTelegram(msg) + if config.TELEGRAM_PROXY_API == nil or config.TELEGRAM_PROXY_API == "" then + log.error("util_notify.notifyToTelegram", "未配置 `config.TELEGRAM_PROXY_API`") + return + end + + local header = { + ["content-type"] = "text/plain", + ["x-disable-web-page-preview"] = "1", + ["x-chat-id"] = config.TELEGRAM_CHAT_ID or "", + ["x-token"] = config.TELEGRAM_TOKEN or "" + } + + log.info("util_notify.notifyToTelegram", "POST", config.TELEGRAM_PROXY_API) + return http.request("POST", config.TELEGRAM_PROXY_API, header, msg).wait() +end + +-- 发送到 pushdeer +local function notifyToPushDeer(msg, httpCallback) + if config.PUSHDEER_API == "" then + log.error("util_notify.notifyToPushDeer", "未配置 `config.PUSHDEER_API`") + return + end + if config.PUSHDEER_KEY == "" then + log.error("util_notify.notifyToPushDeer", "未配置 `config.PUSHDEER_KEY`") + return + end + + local header = { + ["Content-Type"] = "application/x-www-form-urlencoded" + } + local body = { + pushkey = config.PUSHDEER_KEY or "", + type = "text", + text = msg + } + + log.info("util_notify.notifyToPushDeer", "POST", config.PUSHDEER_API) + return http.request("POST", config.PUSHDEER_API, header, urlencodeTab(body)).wait() +end + +-- 发送到 bark +local function notifyToBark(msg, httpCallback) + if config.BARK_API == "" then + log.error("util_notify.notifyToBark", "未配置 `config.BARK_API`") + return + end + if config.BARK_KEY == "" then + log.error("util_notify.notifyToBark", "未配置 `config.BARK_KEY`") + return + end + + local header = { + ["Content-Type"] = "application/x-www-form-urlencoded" + } + local body = { + body = msg + } + local url = config.BARK_API .. "/" .. config.BARK_KEY + + log.info("util_notify.notifyToBark", "POST", url) + return http.request("POST", url, header, urlencodeTab(body)).wait() +end + +function util_notify.send(msg) + log.info("util_notify.send", "发送通知", config.NOTIFY_TYPE) + + if type(msg) == "table" then + msg = table.concat(msg, "\n") + end + if type(msg) ~= "string" then + log.error("util_notify.send", "发送通知失败", "参数类型错误", type(msg)) + return + end + + local model = hmeta.model() or "" + local simid = mobile.simid() + local iccid = mobile.iccid(simid) or "" + local rsrp = mobile.rsrp() + local mcc, mnc, band = util_mobile.mcc, util_mobile.mnc, util_mobile.band + local oper = util_mobile.getOper(true) + local lat, lng = util_location.getCoord() + local map_url = "https://apis.map.qq.com/uri/v1/marker?coord_type=1&marker=title:+;coord:" .. lat .. "," .. lng + + msg = msg .. "\n" + if model then + msg = msg .. "\nMODEL: " .. model + end + if iccid then + msg = msg .. "\nICCID: " .. iccid + end + if oper then + msg = msg .. "\n运营商: " .. oper + end + msg = msg .. "\n信号: " .. rsrp .. "dBm" + if band ~= "" then + msg = msg .. "\n频段: B" .. band + end + if lat ~= 0 and lng ~= 0 then + msg = msg .. "\n位置: " .. map_url + end + + -- 判断通知类型 + local notify + if config.NOTIFY_TYPE == "telegram" then + notify = notifyToTelegram + elseif config.NOTIFY_TYPE == "pushdeer" then + notify = notifyToPushDeer + elseif config.NOTIFY_TYPE == "bark" then + notify = notifyToBark + else + log.error("util_notify.send", "发送通知失败", "未配置 `config.NOTIFY_TYPE`") + return + end + + sys.taskInit( + function() + sys.wait(100) + local max_retry = 10 + local retry_count = 0 + + while retry_count < max_retry do + local code, headers, body = notify(msg) + if code == 200 then + log.info("util_notify.send", "发送通知成功", "retry_count:", retry_count) + break + else + retry_count = retry_count + 1 + log.error("util_notify.send", "发送通知失败", "retry_count:", retry_count, "code:", code, "body:", body) + util_netled.blink(500, 200, 3000) + sys.wait(10000) + end + end + end + ) +end + +return util_notify