From c8267f5eb7eb41ddcd1d07a33bb5ccd592c14401 Mon Sep 17 00:00:00 2001 From: ChRonic37 <29154777+ChRonicen@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:31:27 +0800 Subject: [PATCH] feat: add configurable manual operator selection --- README.md | 5 +++++ code/config.cpp | 5 +++++ code/config_types.h | 2 ++ code/modem.cpp | 18 +++++++++++++++++- code/web_handlers.cpp | 36 +++++++++++++++++++++++++++++++++++- code/web_html.cpp | 20 ++++++++++++++++++++ 6 files changed, 84 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7864bf30a..df94817ab 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ - 支持通过WEB界面进行Ping测试,以极低的成本消耗余额 - 支持长短信自动合并(30秒超时) - 支持管理员短信远程发送短信和重启设备 +- 支持自动选网或按 PLMN 手动指定运营商 + +## 手动运营商选择 + +默认使用模组自动选网。如需固定到特定运营商,可在 WEB 管理页的“选网配置”中选择手动模式并填写 5 或 6 位数字 PLMN(MCC + MNC)。保存后设备会重启并应用 LTE 选网配置。 ## 推送通道支持 diff --git a/code/config.cpp b/code/config.cpp index 873687c57..412cb11b0 100644 --- a/code/config.cpp +++ b/code/config.cpp @@ -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++) { @@ -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++) { diff --git a/code/config_types.h b/code/config_types.h index 6292e2815..f603e1ea0 100644 --- a/code/config_types.h +++ b/code/config_types.h @@ -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管理账号密码 diff --git a/code/modem.cpp b/code/modem.cpp index d5fed3637..ff4d34ee4 100644 --- a/code/modem.cpp +++ b/code/modem.cpp @@ -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(); @@ -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失败,重试...")); @@ -115,7 +131,7 @@ void modemInit() { } if (ceregRetry < 30) { logCaptureLn(String("网络已注册")); - modemReady = true; + modemReady = operatorReady; } else { logCaptureLn(String("⚠️ 网络注册超时(无SIM卡或信号差),模组功能不可用")); modemReady = false; diff --git a/code/web_handlers.cpp b/code/web_handlers.cpp index 47358f7e9..0beaf3dfd 100644 --- a/code/web_handlers.cpp +++ b/code/web_handlers.cpp @@ -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 && @@ -852,6 +855,8 @@ void handlePing() { void handleSave() { if (!checkAuth()) return; + bool operatorConfigChanged = false; + // 账号管理表单:只在字段存在时更新 if (server.hasArg("webUser")) { String newWebUser = server.arg("webUser"); @@ -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); @@ -919,6 +945,7 @@ void handleSave() { saveConfig(); configValid = isConfigValid(); + String saveMessage = operatorConfigChanged ? "正在重启并应用选网配置..." : "3秒后返回配置页面..."; String html = R"rawliteral( @@ -935,13 +962,20 @@ void handleSave() {
3秒后返回配置页面...
+%SAVE_MESSAGE%
如果修改了账号密码,请使用新的账号密码登录