Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
- 支持通过WEB界面进行Ping测试,以极低的成本消耗余额
- 支持长短信自动合并(30秒超时)
- 支持管理员短信远程发送短信和重启设备
- 支持自动选网或按 PLMN 手动指定运营商

## 手动运营商选择

默认使用模组自动选网。如需固定到特定运营商,可在 WEB 管理页的“选网配置”中选择手动模式并填写 5 或 6 位数字 PLMN(MCC + MNC)。保存后设备会重启并应用 LTE 选网配置。

## 推送通道支持

Expand Down
5 changes: 5 additions & 0 deletions code/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ void saveConfig() {
preferences.putString("webUser", config.webUser);
preferences.putString("webPass", config.webPass);
preferences.putString("numBlkList", config.numberBlackList);
preferences.putUChar("operatorMode", config.operatorMode);
preferences.putString("operatorCode", config.operatorCode);

// 保存推送通道配置
for (int i = 0; i < MAX_PUSH_CHANNELS; i++) {
Expand Down Expand Up @@ -42,6 +44,9 @@ void loadConfig() {
config.webUser = preferences.getString("webUser", DEFAULT_WEB_USER);
config.webPass = preferences.getString("webPass", DEFAULT_WEB_PASS);
config.numberBlackList = preferences.getString("numBlkList", "");
config.operatorMode = preferences.getUChar("operatorMode", 0);
config.operatorCode = preferences.getString("operatorCode", "");
if (config.operatorMode > 1) config.operatorMode = 0;

// 加载推送通道配置
for (int i = 0; i < MAX_PUSH_CHANNELS; i++) {
Expand Down
2 changes: 2 additions & 0 deletions code/config_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ struct Config {
String webUser; // Web管理账号
String webPass; // Web管理密码
String numberBlackList; // 号码黑名单(换行符分隔)
uint8_t operatorMode; // 0=自动选网, 1=手动选网
String operatorCode; // PLMN(MCC+MNC,例如 46000)
};

// 默认Web管理账号密码
Expand Down
18 changes: 17 additions & 1 deletion code/modem.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
#include "modem.h"
#include "web_handlers.h"

static bool configureOperator() {
if (config.operatorMode == 0) {
logCaptureLn(String("选网模式:自动选择"));
return sendATandWaitOK("AT+COPS=0", 60000);
}

char command[32];
snprintf(command, sizeof(command), "AT+COPS=1,2,\"%s\",7",
config.operatorCode.c_str());
logCaptureLn(String("按配置执行手动选网"));
return sendATandWaitOK(command, 60000);
}

// 发送AT命令并获取响应
String sendATCommand(const char* cmd, unsigned long timeout) {
while (Serial1.available()) Serial1.read();
Expand Down Expand Up @@ -88,6 +101,9 @@ void modemInit() {
if(model == "ML307Y") need_set_CGACT = false;
}

bool operatorReady = configureOperator();
if (!operatorReady) logCaptureLn(String("⚠️ 选网命令未成功完成"));

if(need_set_CGACT) {
while (!sendATandWaitOK("AT+CGACT=0,1", 5000)) {
logCaptureLn(String("设置CGACT失败,重试..."));
Expand Down Expand Up @@ -115,7 +131,7 @@ void modemInit() {
}
if (ceregRetry < 30) {
logCaptureLn(String("网络已注册"));
modemReady = true;
modemReady = operatorReady;
} else {
logCaptureLn(String("⚠️ 网络注册超时(无SIM卡或信号差),模组功能不可用"));
modemReady = false;
Expand Down
36 changes: 35 additions & 1 deletion code/web_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ void handleRoot() {
html.replace("%SMTP_SEND_TO%", config.smtpSendTo);
html.replace("%ADMIN_PHONE%", config.adminPhone);
html.replace("%NUMBER_BLACK_LIST%", config.numberBlackList);
html.replace("%OP_MODE_AUTO%", config.operatorMode == 0 ? " selected" : "");
html.replace("%OP_MODE_MANUAL%", config.operatorMode == 1 ? " selected" : "");
html.replace("%OPERATOR_CODE%", config.operatorCode);

// 概览页面的配置状态
bool emailOk = config.smtpServer.length() > 0 && config.smtpUser.length() > 0 &&
Expand Down Expand Up @@ -852,6 +855,8 @@ void handlePing() {
void handleSave() {
if (!checkAuth()) return;

bool operatorConfigChanged = false;

// 账号管理表单:只在字段存在时更新
if (server.hasArg("webUser")) {
String newWebUser = server.arg("webUser");
Expand Down Expand Up @@ -890,6 +895,27 @@ void handleSave() {
config.numberBlackList = server.arg("numberBlackList");
}

if (server.hasArg("operatorMode")) {
uint8_t newMode = (uint8_t)server.arg("operatorMode").toInt();
if (newMode > 1) newMode = 0;
String newCode = server.arg("operatorCode");
newCode.trim();

bool validPlmn = newCode.length() == 5 || newCode.length() == 6;
for (unsigned int i = 0; validPlmn && i < newCode.length(); i++) {
validPlmn = isDigit(newCode.charAt(i));
}
if (newMode == 1 && !validPlmn) {
server.send(400, "text/plain; charset=utf-8", "手动选网需要填写 5 或 6 位数字 PLMN");
return;
}

operatorConfigChanged = config.operatorMode != newMode ||
config.operatorCode != newCode;
config.operatorMode = newMode;
config.operatorCode = newCode;
}

// 推送通道配置:只在对应通道的字段存在时更新
for (int i = 0; i < MAX_PUSH_CHANNELS; i++) {
String idx = String(i);
Expand Down Expand Up @@ -919,6 +945,7 @@ void handleSave() {

saveConfig();
configValid = isConfigValid();
String saveMessage = operatorConfigChanged ? "正在重启并应用选网配置..." : "3秒后返回配置页面...";

String html = R"rawliteral(
<!DOCTYPE html>
Expand All @@ -935,13 +962,20 @@ void handleSave() {
<body>
<div class="success">
<h2>✅ 配置保存成功!</h2>
<p>3秒后返回配置页面...</p>
<p>%SAVE_MESSAGE%</p>
<p>如果修改了账号密码,请使用新的账号密码登录</p>
</div>
</body>
</html>
)rawliteral";
html.replace("%SAVE_MESSAGE%", saveMessage);
server.send(200, "text/html", html);

if (operatorConfigChanged) {
delay(800);
ESP.restart();
return;
}

// 如果配置有效,发送启动通知
if (configValid) {
Expand Down
20 changes: 20 additions & 0 deletions code/web_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ const char* htmlPage = R"rawliteral(
<a data-panel="email"><span class="ico">📧</span> <span>邮件通知</span></a>
<a data-panel="push"><span class="ico">🔗</span> <span>推送通道</span></a>
<a data-panel="admin"><span class="ico">👤</span> <span>管理员 &amp; 黑名单</span></a>
<a data-panel="operator"><span class="ico">📶</span> <span>选网配置</span></a>
<div class="sidebar-divider"></div>
<div class="sidebar-section-label">工具</div>
<a data-panel="sendsms"><span class="ico">📤</span> <span>发送短信</span></a>
Expand Down Expand Up @@ -335,6 +336,19 @@ const char* htmlPage = R"rawliteral(
</form>
</div>

<!-- ===== Operator selection ===== -->
<div class="panel" id="panel-operator">
<h1 class="page-title">选网配置</h1>
<p class="page-subtitle">默认使用自动选择;手动模式按 PLMN 指定运营商</p>
<form action="/save" method="POST">
<div class="card"><div class="card-body">
<div class="form-group"><label class="form-label">选网模式</label><select class="form-input" name="operatorMode" id="operatorMode" onchange="toggleOperatorFields()"><option value="0"%OP_MODE_AUTO%>自动选择</option><option value="1"%OP_MODE_MANUAL%>手动指定</option></select></div>
<div id="manualOperatorFields"><div class="form-group"><label class="form-label">PLMN(MCC + MNC)</label><input class="form-input" name="operatorCode" value="%OPERATOR_CODE%" inputmode="numeric" pattern="[0-9]{5,6}" maxlength="6" placeholder="例如 46000"><p class="form-hint">按指定运营商接入 LTE 网络</p></div></div>
</div></div>
<button type="submit" class="btn btn-primary btn-block btn-save">保存并应用</button>
</form>
</div>

<!-- ===== Send SMS ===== -->
<div class="panel" id="panel-sendsms">
<h1 class="page-title">发送短信</h1>
Expand Down Expand Up @@ -505,8 +519,14 @@ const char* htmlPage = R"rawliteral(
else if (type == 9) { hint.innerHTML = 'Gotify<br>填写服务器地址 + 应用 Token'; extra.style.display='block'; document.getElementById('key1label'+idx).innerText='Token(应用 Token)'; document.getElementById('key1'+idx).placeholder='A...'; }
else if (type == 10) { hint.innerHTML = 'Telegram Bot<br>Chat ID(参数1)+ Bot Token(参数2)'; extra.style.display='block'; document.getElementById('key1label'+idx).innerText='Chat ID'; document.getElementById('key1'+idx).placeholder='123456789'; if(kg)kg.style.display='block'; document.getElementById('key2label'+idx).innerText='Bot Token'; document.getElementById('key2'+idx).placeholder='12345678:ABC...'; }
}
function toggleOperatorFields() {
var mode = document.getElementById('operatorMode');
var fields = document.getElementById('manualOperatorFields');
if (mode && fields) fields.style.display = mode.value === '1' ? 'block' : 'none';
}
document.addEventListener('DOMContentLoaded', function() {
for (var i = 0; i < 5; i++) { toggleChannel(i); updateTypeHint(i); }
toggleOperatorFields();
});

// ---- Send SMS ----
Expand Down