Skip to content

Commit 03ab453

Browse files
committedMar 18, 2023
Initial commit
0 parents  commit 03ab453

22 files changed

+1228
-0
lines changed
 

‎.gitignore

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Created by https://www.toptal.com/developers/gitignore/api/windows,lua,visualstudiocode
2+
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,lua,visualstudiocode
3+
4+
### Lua ###
5+
# Compiled Lua sources
6+
luac.out
7+
8+
# luarocks build files
9+
*.src.rock
10+
*.zip
11+
*.tar.gz
12+
13+
# Object files
14+
*.o
15+
*.os
16+
*.ko
17+
*.obj
18+
*.elf
19+
20+
# Precompiled Headers
21+
*.gch
22+
*.pch
23+
24+
# Libraries
25+
*.lib
26+
*.a
27+
*.la
28+
*.lo
29+
*.def
30+
*.exp
31+
32+
# Shared objects (inc. Windows DLLs)
33+
*.dll
34+
*.so
35+
*.so.*
36+
*.dylib
37+
38+
# Executables
39+
*.exe
40+
*.out
41+
*.app
42+
*.i*86
43+
*.x86_64
44+
*.hex
45+
46+
47+
### VisualStudioCode ###
48+
.vscode/*
49+
!.vscode/settings.json
50+
!.vscode/tasks.json
51+
!.vscode/launch.json
52+
!.vscode/extensions.json
53+
!.vscode/*.code-snippets
54+
55+
# Local History for Visual Studio Code
56+
.history/
57+
58+
# Built Visual Studio Code Extensions
59+
*.vsix
60+
61+
### VisualStudioCode Patch ###
62+
# Ignore all local history of files
63+
.history
64+
.ionide
65+
66+
### Windows ###
67+
# Windows thumbnail cache files
68+
Thumbs.db
69+
Thumbs.db:encryptable
70+
ehthumbs.db
71+
ehthumbs_vista.db
72+
73+
# Dump file
74+
*.stackdump
75+
76+
# Folder config file
77+
[Dd]esktop.ini
78+
79+
# Recycle Bin used on file shares
80+
$RECYCLE.BIN/
81+
82+
# Windows Installer files
83+
*.cab
84+
*.msi
85+
*.msix
86+
*.msm
87+
*.msp
88+
89+
# Windows shortcuts
90+
*.lnk
91+
92+
# End of https://www.toptal.com/developers/gitignore/api/windows,lua,visualstudiocode

‎.vscode/settings.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"Lua.diagnostics.globals": [
3+
"wdt",
4+
"log",
5+
"gpio",
6+
"uart",
7+
"wlan",
8+
"http",
9+
"hmeta",
10+
"rtos",
11+
"json",
12+
"socket"
13+
],
14+
"Lua.completion.autoRequire": false
15+
}

‎LICENSE.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2023 boris1993
2+
3+
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:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
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.

‎README.md

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Air780E短信转发
2+
3+
利用ESP32驱动Air780E实现短信转发,兼容合宙ESP32S3和ESP32C3。
4+
5+
**⚠ 仅支持联通、移动网络,不支持电信网络 ⚠**
6+
7+
# 功能
8+
9+
- [x] 自动转发收到的短信,短信内容支持多种语言(其实就是ASCII和UCS-2字符集),目前已测试过英文、中文、日语、俄语字符
10+
- [x] 支持多个推送平台,目前接入:
11+
- [x] [LuatOS社区提供的推送服务器](https://push.luatos.org/)
12+
- [x] Bark
13+
- [x] Server酱
14+
- [x] 钉钉机器人
15+
- [x] 推送加 PushPlus
16+
17+
# 使用方法
18+
19+
## 硬件组装
20+
21+
- 按照下图方向为Air780e和ESP32焊上排针和排座。注意合宙不送排座,需要自己买。
22+
23+
| Air780E | ESP32S3 |
24+
|--------------------------|--------------------------|
25+
| ![](/image/air780e.jpeg) | ![](/image/esp32s3.jpeg) |
26+
27+
- 按图示方向插入SIM卡
28+
29+
![](/image/sim_card_direction.jpeg)
30+
31+
- 按图示方向将Air780E和ESP32组合
32+
33+
![](/image/put_together.jpeg)
34+
35+
## 修改脚本,刷入ESP32
36+
37+
- 修改[`config.lua`](config.lua)
38+
- 修改`config.board_type`为正确的型号,可选值见注释
39+
- 修改`config.wifi`,填入无线网络的SSID和密码
40+
- 修改`config.notification_channel`,将要启用的通知通道的`enabled`配置置为`true`,并填写推送平台相关配置
41+
- 烧录脚本
42+
-[`firmware`](firmware)目录中对应的固件烧入开发板
43+
- 将所有`lua`脚本下载至开发板
44+
![](/image/burning_firmware_and_scripts.png)
45+
- 将开发板上电开机,等待初始化完成后,即可转发短信到配置的通知通道
46+
47+
# LED灯状态含义
48+
49+
- ESP32
50+
- C3的`D4`或S3的`LED A`为初始化状态灯,闪烁代表正在初始化,常亮代表初始化完成,准备转发短信
51+
- C3的`D5`或S3的`LED B`为工作状态灯,平时长灭,收到新短信后高频闪烁,转发完成后熄灭
52+
53+
| ESP32C3 | ESP32S3 |
54+
|-----------------------------|-----------------------------|
55+
| ![](/image/esp32c3_led.jpg) | ![](/image/esp32s3_led.png) |
56+
57+
- Air780
58+
- `POW`灯为电源指示灯,通电后常亮。注意,这个LED不代表开机状态,只要板子有电这个灯就会亮
59+
- `NET`灯为网络状态指示灯,长亮短灭代表正在初始化蜂窝网络,短亮长灭代表网络注册成功,可以接收短信
60+
61+
# Firmware目录下的文件说明
62+
63+
- `LuatOS-SoC_V1004_ESP32C3_classic.soc`对应`ESP32C3 经典款`
64+
- `LuatOS-SoC_V1004_ESP32C3_lite.soc`对应`ESP32C3 简约款`
65+
- `LuatOS-SoC_V1004_ESP32S3.soc`对应`ESP32S3`
66+
67+
固件均通过[合宙云编译](https://wiki.luatos.com/develop/compile/Cloud_compilation.html)精简掉了不需要的功能,以保证内存空间充足。`LuaTools`自动下载的固件不能用,系统启动之后内存就不够用了,发不出去HTTP请求。
68+
69+
目前固件包含`gpio``uart``pwm``wdt``crypto``rtc``network``sntp``tls``wlan``pm``cjson``ntp``shell``dbg`
70+
71+
# 致谢
72+
73+
本项目参考[低成本短信转发器](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)这篇文章,我不会这么快就完成开发。
74+
75+
# 赞助
76+
77+
| 支付宝 | 微信 | Bitcoin |
78+
| ------ | ---- | ------- |
79+
| ![](https://sat02pap001files.storage.live.com/y4mQubRjj6HwFcaRN5WA43bM81G13d2xI-3OAoLSsXXDxJQZ_inF6qA_OFDB51Pg3yfjXu8CSyioCTUI3StB_Dltd7vmBWNHRT0Ok8zMd9Rf_WU42mgDY-pJW_yCrJ0KEUsd32yi5xqB1wjR4lv8jzMboKmpphgwoeOpPR5xgnfhNbfU8ozvDcfnnEiCpvZ6rLk?width=548&height=542&cropmode=none) | ![](https://sat02pap001files.storage.live.com/y4mRChq9zMZbQZK0gVO19Smbyt74YG1QWTI9RAgewZpJKn6BOEg0GK-_AgR9LwdjDSJriEgnz05YSc9fYUiH09i-PKnb40lZI0AqbvtcyXJvqVSdiWbGpeqPFmIktJb2t-bjIXqrupCzZxXWPXmrrFXXdFzgSWstjebkOujhr-ByhKWoLvgn3GHu2WpnGzbKgXs?width=602&height=599&cropmode=none) | ![3H8yBE359vkbpvC4nSP5xwafWThUh4JvGB](https://sat02pap001files.storage.live.com/y4m7ll7ouERuCbkCXI1x-PQJMYTzonfgpFoEL7Odz8HwPC-O2DngJrulJd23PzD6dJnucGf1zC6zGp4PFyVZjJecRWVT69c06Y4OPdjpEh5Z3E6qkRNg1ZMuP9bxQ3R_YKt2HtjzG_BD3_a9gUkRwHm-zmNH1gxJxnSbysa_qbS8xoiFenQioB4RcU-tMZn71z8?width=1044&height=1098&cropmode=none) |

‎air780_helper.lua

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
local air780_helper = {}
2+
3+
local sys = require("sys")
4+
local constants = require("constants")
5+
local pdu_helper = require("pdu_helper")
6+
local utils = require("utils")
7+
8+
local uart_timeout = 100
9+
10+
-- 使用UART 1接口与Air780E通信
11+
-- ESP32 <--> Air780E
12+
-- 02(UART1_TX) <--> 31(UART1_RXD)
13+
-- 03(UART1_RX) <--> 30(UART1_TXD)
14+
local uart_id = 1
15+
local uart_setup_result = uart.setup(uart_id, 115200, 8, 1)
16+
if uart_setup_result ~= 0 then
17+
log.error("air780_helper", "UART初始化失败,返回值:"..uart_setup_result..",ESP32将重启")
18+
sys.wait(1000)
19+
rtos.reboot()
20+
end
21+
22+
-- 串口读缓冲区
23+
local send_queue = {}
24+
25+
-- 注册串口接收事件回调
26+
uart.on(uart_id, "receive", function(id, length)
27+
local s = ""
28+
29+
repeat
30+
s = uart.read(id, length)
31+
if #s > 0 then
32+
table.insert(send_queue, s)
33+
sys.timerStart(sys.publish, uart_timeout, constants.uart_ready_message)
34+
end
35+
until s == ""
36+
end)
37+
38+
-- 发送AT指令
39+
function air780_helper.send_at_command(command)
40+
uart.write(uart_id, command)
41+
uart.write(uart_id, "\r\n")
42+
log.debug("air780_helper", "发送AT指令\""..command.."\"")
43+
end
44+
45+
sys.subscribe(constants.uart_ready_message, function()
46+
-- 拼接所有收到的数据
47+
local data = table.concat(send_queue)
48+
log.debug("air780_helper", data)
49+
50+
-- 读取完成后清空缓冲区
51+
utils.clear_table(send_queue)
52+
53+
data = data:gsub("\n", "\r")
54+
data = data:split("\r")
55+
56+
while #data > 0 do
57+
local current_line = table.remove(data, 1)
58+
59+
-- 响应指令"AT",用于检测连接Air780E是否成功
60+
if current_line == "AT" then
61+
sys.publish(constants.air780_message_topic_at_received)
62+
return
63+
end
64+
65+
-- 响应设定短消息格式指令
66+
if current_line:find("AT+CMGF", 1, true) then
67+
sys.publish(constants.air780_message_topic_sms_format_set)
68+
return
69+
end
70+
71+
-- 响应设定字符集指令
72+
if current_line:find("AT+CSCS", 1, true) then
73+
sys.publish(constants.air780_message_topic_charset_configured)
74+
return
75+
end
76+
77+
-- 响应配置新消息提示指令
78+
if current_line:find("AT+CNMI", 1, true) then
79+
sys.publish(constants.air780_message_topic_new_message_notification_configured)
80+
return
81+
end
82+
83+
local urc = current_line:match("^%+(%w+)")
84+
85+
if urc then -- URC上报
86+
if urc == "CGATT" then
87+
-- 基站附着状态
88+
sys.publish(constants.air780_message_topic_network_connected, current_line:match("%+CGATT: *(%d)") == "1")
89+
elseif urc == "CMT" then
90+
-- 收到短信
91+
local pdu_length = tonumber(current_line:match("%+CMT: *, *(%d+)"))
92+
93+
repeat
94+
local line = table.remove(data, 1)
95+
if #line > 0 then
96+
local phone_number, sms_content, receive_time, is_long_sms, total, current_id = pdu_helper.decode_pdu(line, pdu_length)
97+
log.info("air780_helper", ""..receive_time.." 收到短信,来自号码"..phone_number..", 内容:\""..sms_content.."\"")
98+
99+
sys.publish(
100+
constants.air780_message_topic_new_sms_received,
101+
phone_number,
102+
sms_content,
103+
receive_time,
104+
is_long_sms,
105+
total,
106+
current_id)
107+
break
108+
end
109+
until #data == 0
110+
end
111+
else -- 其他命令
112+
local cmd = current_line:match("^AT%+(%w+)")
113+
if cmd then--命令回复
114+
if cmd == "CPIN" then
115+
-- 检查卡
116+
repeat
117+
local l = table.remove(data, 1)
118+
if #l > 0 then
119+
if l:find("READY") then
120+
-- 找到卡了
121+
sys.publish(constants.air780_message_topic_sim_detected, true)
122+
return
123+
elseif l:find("CME ERROR") then
124+
-- 没卡?
125+
sys.publish(constants.air780_message_topic_sim_detected, false)
126+
return
127+
end
128+
end
129+
until #data == 0
130+
end
131+
end
132+
end
133+
end
134+
end)
135+
136+
-- 发送AT指令并等待指定topic
137+
function air780_helper.send_at_command_and_wait(command, topic_listen_to, timeout)
138+
while true do
139+
air780_helper.send_at_command(command)
140+
local is_successful, r1, r2, r3 = sys.waitUntil(topic_listen_to, timeout or 1000)
141+
if is_successful then
142+
return r1, r2, r3
143+
end
144+
end
145+
end
146+
147+
return air780_helper

‎config.lua

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
local config = {}
2+
3+
config.log_level = log.LOG_INFO
4+
5+
-- ESP32板子型号
6+
-- esp32c3 / esp32s3
7+
config.board_type = "esp32s3"
8+
9+
-- 是否禁止RNDIS
10+
-- 禁止RNDIS可以防止流量流失
11+
config.disable_rndis = true
12+
13+
config.wifi = {
14+
ssid = "Wi-Fi名",
15+
password = "Wi-Fi密码"
16+
}
17+
18+
config.notification_channel = {
19+
-- 合宙推送服务器
20+
luatos = {
21+
enabled = true,
22+
token = ""
23+
},
24+
-- Bark
25+
bark = {
26+
enabled = true,
27+
api_key = ""
28+
},
29+
-- Server酱
30+
server_chan = {
31+
enabled = false,
32+
send_key = ""
33+
},
34+
-- 钉钉Webhook机器人
35+
ding_talk = {
36+
enabled = true,
37+
-- Webhook地址
38+
webhook_url = "",
39+
-- 机器人安全设定中的关键词
40+
keyword = ""
41+
},
42+
-- PushPlus 推送加
43+
pushplus = {
44+
enabled = true,
45+
token = ""
46+
}
47+
}
48+
49+
return config

‎constants.lua

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
local constants = {}
2+
3+
constants.gpio = {
4+
esp32c3 = {
5+
LED_A = 12,
6+
LED_B = 13
7+
},
8+
esp32s3 = {
9+
LED_A = 10,
10+
LED_B = 11
11+
}
12+
}
13+
14+
constants.led_blink_duration = {
15+
working = 50,
16+
initializing = 500
17+
}
18+
19+
constants.pdu_sms_center_type = {
20+
domestic = "81",
21+
global = "91"
22+
}
23+
24+
constants.uart_ready_message = "UART_RECV_ID"
25+
26+
constants.air780_message_topic_at_received = "AT_RECEIVED"
27+
constants.air780_message_topic_sim_detected = "SIM_DETECTED"
28+
constants.air780_message_topic_sms_format_set = "SMS_FORMAT_SET"
29+
constants.air780_message_topic_charset_configured = "CHARSET_CONFIGURED"
30+
constants.air780_message_topic_new_message_notification_configured = "NEW_MESSAGE_NOTIFICATION_CONFIGURED"
31+
constants.air780_message_topic_network_connected = "NETWORK_CONNECTED"
32+
constants.air780_message_topic_new_sms_received = "NEW_SMS_RECEIVED"
33+
constants.air780_message_topic_new_notification_request = "NEW_NOTIFICATION_REQUEST"
34+
35+
return constants
727 KB
Binary file not shown.
727 KB
Binary file not shown.

‎firmware/LuatOS-SoC_V1004_ESP32S3.soc

770 KB
Binary file not shown.

‎image/air780e.jpeg

1.69 MB
Loading
68.1 KB
Loading

‎image/esp32c3_led.jpg

13.4 KB
Loading

‎image/esp32s3.jpeg

1.92 MB
Loading

‎image/esp32s3_led.png

147 KB
Loading

‎image/put_together.jpeg

1.78 MB
Loading

‎image/sim_card_direction.jpeg

2.23 MB
Loading

‎led_helper.lua

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
local led_helper = {}
2+
3+
local sys = require("sys")
4+
local config = require("config")
5+
local constants = require("constants")
6+
local utils = require("utils")
7+
8+
local status_led = gpio.setup(
9+
constants.gpio[config.board_type].LED_A,
10+
0,
11+
gpio.PULLUP)
12+
13+
local working_led = gpio.setup(
14+
constants.gpio[config.board_type].LED_B,
15+
0,
16+
gpio.PULLUP
17+
)
18+
19+
local function stop_and_clear_timer(timer)
20+
sys.timerStop(timer)
21+
timer = nil
22+
end
23+
24+
local is_status_led_on = true
25+
local status_led_blink_timer = nil
26+
function led_helper.blink_status_led(duration)
27+
if status_led_blink_timer then
28+
stop_and_clear_timer(status_led_blink_timer)
29+
end
30+
31+
status_led_blink_timer = sys.timerLoopStart(
32+
function ()
33+
status_led(is_status_led_on and 1 or 0)
34+
is_status_led_on = not is_status_led_on
35+
end,
36+
duration)
37+
end
38+
39+
local is_working_led_on = true
40+
local working_led_blink_timer = nil
41+
function led_helper.blink_working_led(duration)
42+
if working_led_blink_timer then
43+
stop_and_clear_timer(working_led_blink_timer)
44+
end
45+
46+
working_led_blink_timer = sys.timerLoopStart(
47+
function ()
48+
working_led(is_working_led_on and 1 or 0)
49+
is_working_led_on = not is_working_led_on
50+
end,
51+
duration
52+
)
53+
end
54+
55+
function led_helper.light_status_led()
56+
if status_led_blink_timer then
57+
stop_and_clear_timer(status_led_blink_timer)
58+
end
59+
60+
status_led(1)
61+
end
62+
63+
function led_helper.shut_status_led()
64+
if status_led_blink_timer then
65+
stop_and_clear_timer(status_led_blink_timer)
66+
end
67+
68+
status_led(0)
69+
end
70+
71+
function led_helper.light_working_led()
72+
if working_led_blink_timer then
73+
stop_and_clear_timer(working_led_blink_timer)
74+
end
75+
76+
working_led(1)
77+
end
78+
79+
function led_helper.shut_working_led()
80+
if working_led_blink_timer then
81+
stop_and_clear_timer(working_led_blink_timer)
82+
end
83+
84+
working_led(0)
85+
end
86+
87+
return led_helper

‎main.lua

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
PROJECT = "sms_forwarder_wifi"
2+
VERSION = "1.0.0"
3+
4+
local sys = require("sys")
5+
local config = require("config")
6+
local constants = require("constants")
7+
local air780 = require("air780_helper")
8+
local led_helper = require("led_helper")
9+
local utils = require("utils")
10+
11+
require("sysplus")
12+
require("notification_helper")
13+
14+
if wdt then
15+
wdt.init(9000)
16+
sys.timerLoopStart(wdt.feed, 3000)
17+
end
18+
19+
log.setLevel(config.log_level)
20+
log.style(1)
21+
22+
log.info("bsp", rtos.bsp())
23+
log.info("mem_sys", rtos.meminfo("sys"))
24+
log.info("mem_lua", rtos.meminfo("lua"))
25+
26+
-- 每秒完整GC一次,防止内存不足问题
27+
sys.timerLoopStart(function()
28+
collectgarbage("collect")
29+
end, 1000)
30+
31+
led_helper.blink_status_led(constants.led_blink_duration.initializing)
32+
33+
sys.taskInit(function()
34+
local logging_tag = "main - 初始化网络"
35+
log.info(logging_tag, "正在连接无线网络"..config.wifi.ssid)
36+
wlan.init()
37+
wlan.setMode(wlan.STATION)
38+
wlan.connect(config.wifi["ssid"], config.wifi.password)
39+
sys.waitUntil("IP_READY")
40+
local ip_address = wlan.getIP()
41+
log.info(logging_tag, "无线网络连接成功,IP地址:"..ip_address)
42+
43+
log.info(logging_tag, "等待时间同步")
44+
sys.waitUntil("NTP_UPDATE")
45+
log.info(logging_tag, "时间同步完成")
46+
end)
47+
48+
sys.taskInit(function ()
49+
local logging_tag = "main - 初始化Air780"
50+
51+
local at_command_result
52+
53+
log.info(logging_tag, "正在尝试连接Air780E")
54+
air780.send_at_command_and_wait("AT", constants.air780_message_topic_at_received)
55+
log.info(logging_tag, "Air780E已连接")
56+
57+
log.info(logging_tag, "正在检查有无SIM卡")
58+
at_command_result = air780.send_at_command_and_wait("AT+CPIN?", constants.air780_message_topic_sim_detected)
59+
if not at_command_result then
60+
log.error(logging_tag, "未检测到SIM卡")
61+
return
62+
end
63+
64+
log.info(logging_tag, "正在配置短信功能")
65+
-- 配置短消息格式为PDU格式
66+
air780.send_at_command_and_wait("AT+CMGF=0", constants.air780_message_topic_sms_format_set)
67+
-- 配置短信使用UCS2编码
68+
air780.send_at_command_and_wait("AT+CSCS=\"UCS2\"", constants.air780_message_topic_charset_configured)
69+
-- 配置短信内容直接上报,不缓存
70+
air780.send_at_command_and_wait("AT+CNMI=2,2,0,0,0", constants.air780_message_topic_new_message_notification_configured)
71+
log.info(logging_tag, "短信功能配置完成")
72+
73+
if config.disable_rndis then
74+
log.info(logging_tag, "正在禁用RNDIS")
75+
air780.send_at_command("AT+RNDISCALL=0,0")
76+
end
77+
78+
log.info(logging_tag, "检查GPRS附着状态")
79+
while true do
80+
local result = air780.send_at_command_and_wait("AT+CGATT?", constants.air780_message_topic_network_connected)
81+
if result then
82+
log.info(logging_tag, "GPRS已附着")
83+
break
84+
else
85+
log.info(logging_tag, "GPRS未附着,将在5秒后重新检查")
86+
sys.wait(5000)
87+
end
88+
end
89+
90+
log.info(logging_tag, "初始化完成,等待新短信...")
91+
92+
led_helper.light_status_led()
93+
end)
94+
95+
--[[
96+
long_sms_buffer = {
97+
[phone_number] = {
98+
[id] = "content"
99+
}
100+
}
101+
--]]
102+
local long_sms_buffer = {}
103+
104+
sys.subscribe(constants.air780_message_topic_new_sms_received,
105+
function(phone_number, sms_content, _, is_long_message, total_message_number, current_message_id)
106+
led_helper.blink_working_led(constants.led_blink_duration.working)
107+
108+
if is_long_message then
109+
log.info("main", "收到长短信,来自"..phone_number..",正在将第"..current_message_id.."条存入缓冲区,共"..total_message_number.."")
110+
111+
if not long_sms_buffer[phone_number] then
112+
long_sms_buffer[phone_number] = {}
113+
end
114+
115+
long_sms_buffer[phone_number][current_message_id] = sms_content
116+
117+
if long_sms_buffer[phone_number] and #long_sms_buffer[phone_number] == total_message_number then
118+
local full_content = ""
119+
120+
local message_ids = {}
121+
for key in pairs(long_sms_buffer[phone_number]) do
122+
table.insert(message_ids, key)
123+
end
124+
125+
table.sort(message_ids)
126+
127+
for _, id in ipairs(message_ids) do
128+
log.debug("main", "message id: "..id..", content: "..long_sms_buffer[phone_number][id])
129+
full_content = full_content..long_sms_buffer[phone_number][id]
130+
end
131+
132+
-- 清空缓冲区
133+
utils.clear_table(long_sms_buffer[phone_number])
134+
message_ids = nil
135+
136+
log.info("main", "长短信接收完成,完整内容:"..full_content)
137+
sys.publish(
138+
constants.air780_message_topic_new_notification_request,
139+
phone_number,
140+
full_content)
141+
142+
led_helper.shut_working_led()
143+
return
144+
end
145+
else
146+
log.info("main", "收到来自"..phone_number.."的短信,即将转发...")
147+
sys.publish(
148+
constants.air780_message_topic_new_notification_request,
149+
phone_number,
150+
sms_content)
151+
152+
led_helper.shut_working_led()
153+
return
154+
end
155+
end)
156+
157+
sys.run()

‎notification_helper.lua

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
local notification_helper = {}
2+
3+
local sys = require("sys")
4+
local sysplus = require("sysplus")
5+
local constants = require("constants")
6+
local config = require("config")
7+
local utils = require("utils")
8+
9+
-- https://tutorialspots.com/lua-urlencode-and-urldecode-5528.html
10+
local function urlencode(str)
11+
str = string.gsub(
12+
str,
13+
"([^0-9a-zA-Z !'()*._~-])", -- locale independent
14+
function(c)
15+
return string.format ("%%%02X", string.byte(c))
16+
end)
17+
str = string.gsub (str, " ", "+")
18+
return str
19+
end
20+
21+
local function bark(sender_number, content)
22+
if not config.notification_channel.bark.enabled then
23+
return
24+
end
25+
26+
if utils.is_empty(config.notification_channel.bark.api_key) then
27+
log.warn("notification_helper", "Bark API key为空,跳过调用Bark API")
28+
return
29+
end
30+
31+
log.info("notification_helper", "正在发送Bark通知")
32+
33+
local url = "https://api.day.app/"..config.notification_channel.bark.api_key
34+
log.debug("notification_helper", "Calling Bark API: "..url)
35+
36+
local request_body = {
37+
title = sender_number,
38+
body = content,
39+
level = "timeSensitive",
40+
}
41+
42+
local code, headers, body = http.request(
43+
"POST",
44+
url,
45+
{["Content-Type"] = "application/json"},
46+
json.encode(request_body),
47+
{ipv6=true}
48+
).wait()
49+
if code ~= 200 then
50+
log.warn("notification_helper", "Bark API返回值不是200,HTTP状态码:"..code..",响应内容:"..(body or ""))
51+
end
52+
53+
log.info("notification_helper", "Bark通知发送完成")
54+
end
55+
56+
local function luatos_notification(sender_number, content)
57+
if not config.notification_channel.luatos.enabled then
58+
return
59+
end
60+
61+
if utils.is_empty(config.notification_channel.luatos.token) then
62+
log.warn("notification_helper", "合宙推送平台token为空,跳过调用合宙推送平台API")
63+
end
64+
65+
log.info("notification_helper", "正在发送合宙推送平台通知")
66+
67+
local url = "https://push.luatos.org/4B9C2E1F93C648CFB11CAA8294C525D5.send/"..urlencode(sender_number).."/"..urlencode(content)
68+
log.debug("notification_helper", "Calling LuatOS notification API: "..url)
69+
70+
local code, headers, body = http.request("GET", url, nil, nil, {ipv6=true}).wait()
71+
if code ~= 200 then
72+
log.warn("notification_helper", "合宙推送API返回值不是200,HTTP状态码:"..code..",响应内容:"..(body or ""))
73+
end
74+
75+
log.info("notification_helper", "合宙推送平台通知发送完成")
76+
end
77+
78+
local function server_chan(sender_number, content)
79+
if not config.notification_channel.server_chan.enabled then
80+
return
81+
end
82+
83+
if utils.is_empty(config.notification_channel.server_chan.send_key) then
84+
log.warn("notification_helper", "Server酱的SendKey为空,跳过调用Server酱API")
85+
end
86+
87+
log.info("notification_helper", "正在发送Server酱通知")
88+
89+
local url = "https://sctapi.ftqq.com/"..config.notification_channel.server_chan.send_key..".send"
90+
log.debug("notification_helper", "Calling ServerChan API: "..url)
91+
92+
local request_body = {
93+
title = sender_number,
94+
desp = content
95+
}
96+
local request_body_json, json_error = json.encode(request_body)
97+
98+
if json_error then
99+
log.warn("notification_helper", "Server酱请求序列化失败,错误信息:"..json_error)
100+
return
101+
end
102+
103+
log.debug("notification_helper", "ServerChan request body: "..request_body_json)
104+
105+
local code, headers, response_body = http.request(
106+
-- Method
107+
"POST",
108+
-- URL
109+
url,
110+
-- Headers
111+
{["Content-Type"] = "application/json"},
112+
request_body_json,
113+
{ipv6=true}
114+
).wait()
115+
116+
if code ~= 200 then
117+
log.warn("notification_helper", "Server酱API返回值不是200,HTTP状态码:"..code..",响应内容:"..(response_body or ""))
118+
end
119+
120+
log.info("notification_helper", "Server酱通知发送完成")
121+
end
122+
123+
local function ding_talk_bot(sender_number, content)
124+
if not config.notification_channel.ding_talk.enabled then
125+
return
126+
end
127+
128+
if utils.is_empty(config.notification_channel.ding_talk.webhook_url) then
129+
log.warn("notification_helper", "钉钉机器人webhook URL未填写,跳过调用钉钉机器人webhook")
130+
return
131+
elseif utils.is_empty(config.notification_channel.ding_talk.keyword) then
132+
log.warn("notification_helper", "钉钉机器人关键词未填写,跳过调用钉钉机器人webhook")
133+
return
134+
end
135+
136+
log.info("notification_helper", "正在发送钉钉机器人通知")
137+
138+
local url = config.notification_channel.ding_talk.webhook_url
139+
local keyword = config.notification_channel.ding_talk.keyword
140+
141+
local request_body = {
142+
msgtype = "markdown",
143+
markdown = {
144+
title = keyword,
145+
text = "收到来自 **"..sender_number.."** 的短信,内容:\n\n"..content
146+
}
147+
}
148+
149+
local code, headers, response_body = http.request(
150+
"POST",
151+
url,
152+
{["Content-Type"] = "application/json"},
153+
json.encode(request_body),
154+
{ipv6=true}
155+
).wait()
156+
157+
if code ~= 200 then
158+
log.warn("notification_helper", "钉钉webhook返回值不是200,HTTP状态码:"..code..",响应内容:"..(response_body or ""))
159+
end
160+
161+
log.info("notification_helper", "钉钉机器人通知发送完成")
162+
end
163+
164+
local function pushplus(sender_number, content)
165+
if not config.notification_channel.pushplus.enabled then
166+
return
167+
elseif utils.is_empty(config.notification_channel.pushplus.token) then
168+
log.warn("notification_helper", "PushPlus token未填写,跳过调用PushPlus")
169+
end
170+
171+
log.info("notification_helper", "正在发送PushPlus通知")
172+
173+
local url = "http://www.pushplus.plus/send"
174+
local request_body = {
175+
token = config.notification_channel.pushplus.token,
176+
title = sender_number,
177+
content = content
178+
}
179+
180+
local code, headers, response_body = http.request(
181+
"POST",
182+
url,
183+
{["Content-Type"] = "application/json"},
184+
json.encode(request_body),
185+
{ipv6=true}
186+
).wait()
187+
188+
if code ~= 200 then
189+
log.warn("notification_helper", "PushPlus API返回值不是200,HTTP状态码:"..code..",响应内容:"..(response_body or ""))
190+
end
191+
192+
log.info("notification_helper", "PushPlus通知发送完成")
193+
end
194+
195+
local notification_channels = {
196+
bark = bark,
197+
luatos_notification = luatos_notification,
198+
server_chan = server_chan,
199+
ding_talk_bot = ding_talk_bot,
200+
pushplus = pushplus,
201+
}
202+
203+
local function call_notification_channels(sender_number, content)
204+
for _, notification_channel in pairs(notification_channels) do
205+
sys.taskInit(function()
206+
notification_channel(sender_number, content)
207+
end)
208+
end
209+
end
210+
211+
sys.subscribe(constants.air780_message_topic_new_notification_request,
212+
function(sender_number, content)
213+
call_notification_channels(sender_number, content)
214+
end)
215+
216+
return notification_helper

‎pdu_helper.lua

+327
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
--[[
2+
PDU helper
3+
大部分代码来自 https://github.com/chenxuuu/sms_forwarding/blob/master/script/pdu.lua
4+
--]]
5+
local pdu_helper = {}
6+
7+
local constants = require("constants")
8+
9+
--[[
10+
GSM字符集
11+
https://en.wikipedia.org/wiki/GSM_03.38#GSM_7-bit_default_alphabet_and_extension_table_of_3GPP_TS_23.038_/_GSM_03.38
12+
--]]
13+
local charmap = {
14+
[0] = 0x40, 0xa3, 0x24, 0xa5, 0xe8, 0xE9, 0xF9, 0xEC, 0xF2, 0xC7, 0x0A, 0xD8, 0xF8, 0x0D, 0xC5, 0xE5
15+
, 0x0394, 0x5F, 0x03A6, 0x0393, 0x039B, 0x03A9, 0x03A0, 0x03A8, 0x03A3, 0x0398, 0x039E, 0x1B, 0xC6, 0xE5, 0xDF, 0xA9
16+
, 0x20, 0x21, 0x22, 0x23, 0xA4, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F
17+
, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
18+
, 0xA1, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F
19+
, 0X50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0xC4, 0xD6, 0xD1, 0xDC, 0xA7
20+
, 0xBF, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F
21+
, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xE4, 0xF6, 0xF1, 0xFC, 0xE0}
22+
23+
--[[
24+
GSM扩展字符集
25+
https://en.wikipedia.org/wiki/GSM_03.38#GSM_7-bit_default_alphabet_and_extension_table_of_3GPP_TS_23.038_/_GSM_03.38
26+
--]]
27+
local charmap_ext = {[10] = 0x0C, [20] = 0x5E, [40] = 0x7B, [41] = 0x7D, [47] = 0x5C, [60] = 0x5B, [61] = 0x7E
28+
, [62] = 0x5D, [64] = 0x7C, [101] = 0xA4}
29+
30+
local function number_to_bcd_number(number)
31+
local number_length = #number
32+
local prefix
33+
local converted_number = ""
34+
35+
if string.sub(number, 1, 1) == "+" then
36+
prefix = constants.pdu_sms_center_type.global
37+
number_length = number_length - 1
38+
number = number:sub(2, -1)
39+
else
40+
prefix = constants.pdu_sms_center_type.domestic
41+
end
42+
43+
-- 每次取两位,前后颠倒后,拼接至converted_number
44+
for i = 1, (number_length - (number_length % 2)) / 2 do
45+
converted_number = converted_number..number:sub(i * 2, i * 2)..number:sub(i * 2 - 1, i * 2 - 1)
46+
end
47+
48+
-- 如果号码长度为奇数,那么在末尾补一个F
49+
if number_length % 2 ~= 0 then
50+
converted_number = converted_number.."F"..number:sub(number_length, number_length)
51+
end
52+
53+
return prefix..converted_number
54+
end
55+
56+
local function bcd_number_to_ascii(bcd_number)
57+
local length = #bcd_number
58+
local prefix = ""
59+
local converted_number = ""
60+
61+
if length % 2 ~= 0 then
62+
log.warn("pdu_helper", "BCD数字\""..bcd_number.."\"无效")
63+
return
64+
end
65+
66+
if bcd_number:sub(1, 2) == constants.pdu_sms_center_type.global then
67+
prefix = "+"
68+
end
69+
70+
-- 去掉本地/国际标识部分
71+
length = length - 2
72+
bcd_number = bcd_number:sub(3, -1)
73+
74+
-- 每次取两位,前后颠倒后,拼接至converted_number
75+
for i = 1, (length - (length % 2)) / 2 do
76+
converted_number = converted_number..bcd_number:sub(i * 2, i * 2)..bcd_number:sub(i * 2 - 1, i * 2 - 1)
77+
end
78+
79+
if converted_number:sub(length, length):upper() == "F" then
80+
converted_number = converted_number:sub(1, -2)
81+
end
82+
83+
return prefix..converted_number
84+
end
85+
86+
-- 解码GSM 8-bit编码
87+
local function gsm_8bit_decode(data)
88+
local ucs_data = ""
89+
local lpcnt = #data / 2
90+
91+
for i = 1, lpcnt do
92+
ucs_data = ucs_data.."00"..data:sub((i - 1) * 2 + 1, i * 2)
93+
end
94+
95+
return ucs_data, lpcnt
96+
end
97+
98+
-- 解码GSM 7-bit编码
99+
local function gsm_7bit_decode(data, longsms)
100+
local ucsdata, lpcnt, tmpdata, resdata, nbyte, nleft, ucslen, olddat = "", #data / 2, 0, 0, 0, 0, 0
101+
102+
if longsms then
103+
tmpdata = tonumber("0x" .. data:sub(1, 2))
104+
resdata = tmpdata >> 1
105+
if olddat == 27 then
106+
if charmap_ext[resdata] then --特殊字符
107+
olddat, resdata = resdata, charmap_ext[resdata]
108+
ucsdata = ucsdata:sub(1, -5)
109+
else
110+
olddat, resdata = resdata, charmap[resdata]
111+
end
112+
else
113+
olddat, resdata = resdata, charmap[resdata]
114+
end
115+
ucsdata = ucsdata .. string.format("%04X", resdata)
116+
else
117+
tmpdata = tonumber("0x" .. data:sub(1, 2))
118+
resdata = ((tmpdata<<nbyte)|nleft)&0x7f
119+
if olddat == 27 then
120+
if charmap_ext[resdata] then --特殊字符
121+
olddat, resdata = resdata, charmap_ext[resdata]
122+
ucsdata = ucsdata:sub(1, -5)
123+
else
124+
olddat, resdata = resdata, charmap[resdata]
125+
end
126+
else
127+
olddat, resdata = resdata, charmap[resdata]
128+
end
129+
ucsdata = ucsdata .. string.format("%04X", resdata)
130+
131+
nleft = tmpdata >> (7 - nbyte)
132+
nbyte = nbyte + 1
133+
ucslen = ucslen + 1
134+
end
135+
136+
for i = 2, lpcnt do
137+
tmpdata = tonumber("0x" .. data:sub((i - 1) * 2 + 1, i * 2))
138+
if tmpdata == nil then break end
139+
resdata = ((tmpdata<<nbyte)|nleft)&0x7f
140+
if olddat == 27 then
141+
if charmap_ext[resdata] then --特殊字符
142+
olddat, resdata = resdata, charmap_ext[resdata]
143+
ucsdata = ucsdata:sub(1, -5)
144+
else
145+
olddat, resdata = resdata, charmap[resdata]
146+
end
147+
else
148+
olddat, resdata = resdata, charmap[resdata]
149+
end
150+
ucsdata = ucsdata .. string.format("%04X", resdata)
151+
152+
nleft = tmpdata >> (7 - nbyte)
153+
nbyte = nbyte + 1
154+
ucslen = ucslen + 1
155+
156+
if nbyte == 7 then
157+
if olddat == 27 then
158+
if charmap_ext[nleft] then --特殊字符
159+
olddat, nleft = nleft, charmap_ext[nleft]
160+
ucsdata = ucsdata:sub(1, -5)
161+
else
162+
olddat, nleft = nleft, charmap[nleft]
163+
end
164+
else
165+
olddat, nleft = nleft, charmap[nleft]
166+
end
167+
ucsdata = ucsdata .. string.format("%04X", nleft)
168+
nbyte, nleft = 0, 0
169+
ucslen = ucslen + 1
170+
end
171+
end
172+
173+
return ucsdata, ucslen
174+
end
175+
176+
local function ucs2_to_utf8(s)
177+
local temp = {}
178+
for i=1, #s, 2 do
179+
local d1, d2 = s:byte(i), s:byte(i + 1)
180+
if d1 == 0 and d2 <= 0x7f then --不大于0x007F
181+
table.insert(temp, string.char(d2))
182+
elseif d1 < 0x07 then --不大于0x07FF 00000aaa bbbbbbbb ==> 110aaabb 10bbbbbb
183+
table.insert(temp, string.char(0xc0 + (d1 << 2) + (d2 >> 6), 0x80 + (d2 & 0x3f)))
184+
else --aaaaaaaa bbbbbbbb ==> 1110aaaa 10aaaabb 10bbbbbb
185+
table.insert(temp,string.char(0xe0 + (d1 >> 4), 0x80 + ((d1 & 0x0f) << 2) + (d2 >> 6), 0x80 + (d2 & 0x3f)))
186+
end
187+
end
188+
return table.concat(temp)
189+
end
190+
191+
--[[
192+
解析PDU短信
193+
194+
返回值:
195+
发送者号码
196+
短信内容
197+
接收时间
198+
是否为长短信
199+
如果为长短信,分了几包
200+
如果为长短信,当前是第几包
201+
--]]
202+
function pdu_helper.decode_pdu(pdu, len)
203+
collectgarbage("collect")
204+
205+
log.debug("pdu_helper", "原始PDU信息:\""..pdu.."\",长度:"..len)
206+
207+
--[[
208+
不包括短信息中心号码的PDU数据
209+
210+
计算:
211+
1. #pdu / 2
212+
字符串中每两个字符为1位16进制数,即计算完整PDU的字节数
213+
214+
2. (#pdu / 2 - len)
215+
len为从PDU报头开始计算的报文字节数,如此相减即可得到PDU First Octet的上一字节的位置
216+
217+
3. (#pdu / 2 - len) * 2 + 1
218+
乘二,得到PDU First Octet上一字节在PDU字符串中的位置;加一将偏移量指向PDU First Octet的第一个字符
219+
--]]
220+
pdu = pdu:sub((#pdu / 2 - len) * 2 + 1)
221+
222+
local long_sms = false
223+
-- TP-Message-Type-Indicator
224+
-- https://www.cnblogs.com/dajianshi/archive/2013/01/25/2876151.html
225+
local first_octet = tonumber("0x"..pdu:sub(1, 1))
226+
log.debug("pdu_helper", "First Octet: "..first_octet)
227+
if first_octet & 0x4 ~= 0 then
228+
long_sms = true
229+
log.debug("pdu_helper", "Long SMS")
230+
end
231+
232+
local offset = 3
233+
234+
-- 源地址数字个数
235+
local sender_address_length = tonumber(string.format("%d", "0x"..pdu:sub(offset, offset + 1)))
236+
log.debug("pdu_helper", "sender address length: "..sender_address_length)
237+
offset = offset + 2
238+
239+
-- 加上号码类型2位,如果号码长度为奇数,那么再加1位F
240+
sender_address_length = sender_address_length % 2 == 0 and sender_address_length + 2 or sender_address_length + 3
241+
local sender_number_bcd = pdu:sub(offset, offset + sender_address_length - 1)
242+
local sender_number = bcd_number_to_ascii(sender_number_bcd)
243+
log.debug("pdu_helper", "sender_number: "..sender_number)
244+
245+
offset = offset + sender_address_length
246+
247+
-- 协议标识 (TP-PID)
248+
local protocol_identifier = tonumber(string.format("%d", "0x"..pdu:sub(offset, offset + 1)))
249+
log.debug("pdu_helper", "TP-PID: "..protocol_identifier)
250+
offset = offset + 2
251+
252+
-- 用户信息编码方式
253+
local dcs = tonumber(string.format("%d", "0x"..pdu:sub(offset, offset + 1)))
254+
log.debug("pdu_helper", "Data Coding Scheme: "..dcs)
255+
offset = offset + 2
256+
257+
local timestamp = pdu:sub(offset, offset + 13)--时区7个字节
258+
log.debug("pdu_helper", "timestamp: "..timestamp)
259+
offset = offset + 14
260+
261+
local sms_receive_time = ""
262+
for i = 1, 7 do
263+
sms_receive_time = sms_receive_time .. timestamp:sub(i * 2, i * 2) .. timestamp:sub(i * 2 - 1, i * 2 - 1)
264+
265+
if i <= 3 then
266+
sms_receive_time = i < 3 and (sms_receive_time .. "/") or (sms_receive_time .. ",")
267+
elseif i <= 6 then
268+
sms_receive_time = i < 6 and (sms_receive_time .. ":") or (sms_receive_time .. "+")
269+
end
270+
end
271+
272+
-- 短信文本长度
273+
local content_length = tonumber(string.format("%d", "0x"..pdu:sub(offset, offset + 1)))
274+
log.debug("pdu_helper", "Content Length: "..content_length)
275+
offset = offset + 2
276+
277+
local current_idx
278+
local total_message_count
279+
if long_sms then
280+
local header_length = tonumber("0x"..pdu:sub(offset, offset + 1))
281+
log.debug("pdu_helper", "Header length: "..header_length)
282+
283+
-- 指针走到header中的长短信总条数
284+
-- header有两种,6位header的剩余协议头长度为5,7位header的剩余协议头长度为6
285+
if header_length == 5 then
286+
offset = offset + 8
287+
else
288+
offset = offset + 10
289+
end
290+
291+
total_message_count = tonumber("0x"..pdu:sub(offset, offset + 1))
292+
offset = offset + 2
293+
current_idx = tonumber("0x"..pdu:sub(offset, offset + 1))
294+
offset = offset + 2
295+
296+
log.debug("pdu_helper", "current index: "..current_idx..", total: "..total_message_count)
297+
end
298+
299+
-- 短信文本
300+
local data = pdu:sub(offset, offset + content_length * 2 - 1)
301+
302+
local decoded_sms_content
303+
local sms_content_in_utf8
304+
if dcs == 0x00 then -- 7bit encode
305+
log.debug("pdu_helper", "Incoming GSM-7 data: "..data..", is long SMS: "..tostring(long_sms))
306+
decoded_sms_content = gsm_7bit_decode(data, long_sms)
307+
log.debug("pdu_helper", "GSM-7 decoded, data: \""..decoded_sms_content.."\"")
308+
decoded_sms_content = decoded_sms_content:fromHex()
309+
sms_content_in_utf8 = ucs2_to_utf8(decoded_sms_content)
310+
log.debug("pdu_helper", "SMS content in UTF-8: "..sms_content_in_utf8)
311+
elseif dcs == 0x04 then -- 8bit encode
312+
log.debug("pdu_helper", "Incoming 8 bit data:", data)
313+
decoded_sms_content = gsm_8bit_decode(data)
314+
log.debug("pdu_helper", "GSM-8 decoded, data: \""..decoded_sms_content.."\"")
315+
sms_content_in_utf8 = decoded_sms_content:fromHex()
316+
log.debug("pdu_helper", "SMS content in UTF-8: "..sms_content_in_utf8)
317+
elseif dcs == 0x08 then -- UCS2
318+
log.debug("pdu_helper", "Incoming UCS2 data: "..data)
319+
sms_content_in_utf8 = data:fromHex()
320+
sms_content_in_utf8 = ucs2_to_utf8(sms_content_in_utf8)
321+
log.debug("pdu_helper", "Decoded UCS2 data: "..sms_content_in_utf8)
322+
end
323+
324+
return sender_number, sms_content_in_utf8, sms_receive_time, long_sms, total_message_count, current_idx
325+
end
326+
327+
return pdu_helper

‎utils.lua

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
local utils = {}
2+
3+
function utils.bool_to_number(value)
4+
return value and 1 or 0
5+
end
6+
7+
function utils.is_empty(str)
8+
return str == nil or str == ""
9+
end
10+
11+
function utils.clear_table(table)
12+
for i = 0, #table do
13+
table[i] = nil
14+
end
15+
end
16+
17+
return utils

0 commit comments

Comments
 (0)
Please sign in to comment.