feat: 添加移动数据控制
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
"probeTimeout": 1200,
|
||||
"_comment": "Windows使用COM1/COM2/COM3等,也可以直接写数字如3; Linux使用/dev/ttyUSB0"
|
||||
},
|
||||
"mobileData": {
|
||||
"cid": 1,
|
||||
"_comment": "每次启动都会强制关闭移动数据,管理台开关只影响当前运行期; ML307 cid8为IMS专用,请勿使用"
|
||||
},
|
||||
"smtp": {
|
||||
"server": "smtp.qq.com",
|
||||
"port": 465,
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
<dt>信号</dt>
|
||||
<dd id="signalQuality">--</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>移动数据</dt>
|
||||
<dd id="mobileDataSummary">--</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>运行时长</dt>
|
||||
<dd id="uptime">--</dd>
|
||||
@@ -80,6 +84,15 @@
|
||||
<h3 id="signalValue">--</h3>
|
||||
<p id="signalHint">RSSI 与误码率将在刷新后显示。</p>
|
||||
</article>
|
||||
<article class="feature-card feature-card--control">
|
||||
<span class="eyebrow">mobile data</span>
|
||||
<h3 id="mobileDataState">--</h3>
|
||||
<p id="mobileDataHint">启动时会自动关闭移动数据。</p>
|
||||
<div class="feature-card__actions">
|
||||
<button class="button button--secondary button--compact" id="mobileDataToggle" type="button" data-action="disable">强制关闭流量</button>
|
||||
</div>
|
||||
<p class="form-status mobile-data-status" id="mobileDataStatus" role="status"></p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<span class="eyebrow">module</span>
|
||||
<h3 id="moduleModel">--</h3>
|
||||
|
||||
+15
-1
@@ -363,7 +363,7 @@ h3 {
|
||||
|
||||
.status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@@ -378,12 +378,26 @@ h3 {
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
.feature-card--control {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr) auto auto;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
margin-top: 14px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.feature-card__actions {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.mobile-data-status {
|
||||
min-height: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: block;
|
||||
margin-bottom: 18px;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const state = {
|
||||
busy: false,
|
||||
mobileDataBusy: false,
|
||||
pushChannels: [],
|
||||
selectedPushChannel: -1,
|
||||
pushLoaded: false
|
||||
@@ -29,11 +30,16 @@ const elements = {
|
||||
modelName: document.querySelector('#modelName'),
|
||||
operator: document.querySelector('#operator'),
|
||||
signalQuality: document.querySelector('#signalQuality'),
|
||||
mobileDataSummary: document.querySelector('#mobileDataSummary'),
|
||||
uptime: document.querySelector('#uptime'),
|
||||
connectionState: document.querySelector('#connectionState'),
|
||||
connectionHint: document.querySelector('#connectionHint'),
|
||||
signalValue: document.querySelector('#signalValue'),
|
||||
signalHint: document.querySelector('#signalHint'),
|
||||
mobileDataState: document.querySelector('#mobileDataState'),
|
||||
mobileDataHint: document.querySelector('#mobileDataHint'),
|
||||
mobileDataToggle: document.querySelector('#mobileDataToggle'),
|
||||
mobileDataStatus: document.querySelector('#mobileDataStatus'),
|
||||
moduleModel: document.querySelector('#moduleModel'),
|
||||
moduleVersion: document.querySelector('#moduleVersion'),
|
||||
iccid: document.querySelector('#iccid'),
|
||||
@@ -152,6 +158,55 @@ function formatModel(model = {}) {
|
||||
return [model.manufacturer, model.model].filter(Boolean).join(' ') || '--';
|
||||
}
|
||||
|
||||
function formatMobileDataMode(mode) {
|
||||
if (mode === 'mipcall') {
|
||||
return 'MIPCALL';
|
||||
}
|
||||
if (mode === 'cgact') {
|
||||
return 'CGACT';
|
||||
}
|
||||
return 'AT';
|
||||
}
|
||||
|
||||
function formatMobileDataContexts(contexts = []) {
|
||||
const active = contexts
|
||||
.filter(item => item.active)
|
||||
.map(item => `CID ${item.cid}`)
|
||||
.join('、');
|
||||
|
||||
return active || '';
|
||||
}
|
||||
|
||||
function renderMobileData(mobileData = {}) {
|
||||
const unknown = mobileData.status === 'unknown';
|
||||
const enabled = Boolean(mobileData.enabled || mobileData.anyActive);
|
||||
const cid = mobileData.cid || 1;
|
||||
const mode = formatMobileDataMode(mobileData.mode);
|
||||
const activeContexts = formatMobileDataContexts(mobileData.contexts || []);
|
||||
|
||||
if (unknown) {
|
||||
setText(elements.mobileDataSummary, '未知');
|
||||
setText(elements.mobileDataState, '状态未知');
|
||||
setText(elements.mobileDataHint, mobileData.error || '未能确认移动数据状态,可先强制关闭。');
|
||||
elements.mobileDataToggle.textContent = '强制关闭流量';
|
||||
elements.mobileDataToggle.dataset.action = 'disable';
|
||||
} else if (enabled) {
|
||||
setText(elements.mobileDataSummary, '已开启');
|
||||
setText(elements.mobileDataState, '流量已开启');
|
||||
setText(elements.mobileDataHint, activeContexts ? `${mode} 检测到 ${activeContexts} 处于活动状态。` : `CID ${cid} 处于活动状态。`);
|
||||
elements.mobileDataToggle.textContent = '关闭流量';
|
||||
elements.mobileDataToggle.dataset.action = 'disable';
|
||||
} else {
|
||||
setText(elements.mobileDataSummary, '已关闭');
|
||||
setText(elements.mobileDataState, '流量已关闭');
|
||||
setText(elements.mobileDataHint, `${mode} CID ${cid} 未建立移动数据连接,重启后也会先保持关闭。`);
|
||||
elements.mobileDataToggle.textContent = '开启流量';
|
||||
elements.mobileDataToggle.dataset.action = 'enable';
|
||||
}
|
||||
|
||||
elements.mobileDataToggle.disabled = state.mobileDataBusy;
|
||||
}
|
||||
|
||||
function renderStatus(status, info) {
|
||||
const data = status.data || {};
|
||||
const infoData = info.data || {};
|
||||
@@ -165,6 +220,7 @@ function renderStatus(status, info) {
|
||||
setText(elements.operator, data.operator || '未知');
|
||||
setText(elements.signalQuality, signal.quality || '未知');
|
||||
setText(elements.uptime, formatUptime(data.uptime));
|
||||
renderMobileData(data.mobileData || {});
|
||||
|
||||
setText(elements.connectionState, ready ? '运行中' : '未就绪');
|
||||
setText(elements.connectionHint, ready ? '串口、SIM、驻网和短信配置均已通过启动检查。' : '请检查启动日志和模组初始化状态。');
|
||||
@@ -594,6 +650,38 @@ async function refreshMessages() {
|
||||
renderMessages(messages.data || []);
|
||||
}
|
||||
|
||||
async function toggleMobileData() {
|
||||
if (state.mobileDataBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = elements.mobileDataToggle.dataset.action || 'disable';
|
||||
const enabled = action === 'enable';
|
||||
|
||||
if (enabled && !window.confirm('开启移动数据可能产生流量费用,确定要开启吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.mobileDataBusy = true;
|
||||
elements.mobileDataToggle.disabled = true;
|
||||
setStatusMessage(elements.mobileDataStatus, enabled ? '正在开启...' : '正在关闭...');
|
||||
|
||||
try {
|
||||
const result = await requestJSON('/api/modem/mobile-data', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ enabled })
|
||||
});
|
||||
renderMobileData(result.data || {});
|
||||
setStatusMessage(elements.mobileDataStatus, result.message || (enabled ? '移动数据已开启。' : '移动数据已关闭。'), 'success');
|
||||
await refreshLogs();
|
||||
} catch (err) {
|
||||
setStatusMessage(elements.mobileDataStatus, err.message, 'error');
|
||||
} finally {
|
||||
state.mobileDataBusy = false;
|
||||
elements.mobileDataToggle.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
if (state.busy) {
|
||||
return;
|
||||
@@ -607,6 +695,7 @@ async function refreshAll() {
|
||||
const status = await requestJSON('/api/status');
|
||||
const info = await requestJSON('/api/modem/info');
|
||||
renderStatus(status, info);
|
||||
setStatusMessage(elements.mobileDataStatus, '');
|
||||
await refreshMessages();
|
||||
await refreshLogs();
|
||||
} catch (err) {
|
||||
@@ -694,6 +783,7 @@ elements.pushChannelForm.addEventListener('submit', savePushChannels);
|
||||
elements.pushTestButton.addEventListener('click', testPushChannel);
|
||||
elements.pushChannelForm.elements.type.addEventListener('change', handlePushTypeChange);
|
||||
elements.refreshButton.addEventListener('click', refreshAll);
|
||||
elements.mobileDataToggle.addEventListener('click', toggleMobileData);
|
||||
elements.logsButton.addEventListener('click', refreshLogs);
|
||||
elements.messagesButton.addEventListener('click', refreshMessages);
|
||||
elements.smsForm.addEventListener('submit', sendSMS);
|
||||
|
||||
+44
@@ -364,6 +364,7 @@ class APIServer {
|
||||
try {
|
||||
const signal = await this.modem.getSignalQuality();
|
||||
const operator = await this.modem.getOperator();
|
||||
const mobileData = await this.modem.getMobileDataStatus();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -372,6 +373,7 @@ class APIServer {
|
||||
model: this.modem.modelInfo,
|
||||
signal,
|
||||
operator,
|
||||
mobileData,
|
||||
uptime: process.uptime()
|
||||
}
|
||||
});
|
||||
@@ -466,6 +468,48 @@ class APIServer {
|
||||
}
|
||||
});
|
||||
|
||||
// 查询移动数据连接状态
|
||||
this.app.get('/api/modem/mobile-data', async (req, res) => {
|
||||
try {
|
||||
const mobileData = await this.modem.getMobileDataStatus();
|
||||
res.json({
|
||||
success: true,
|
||||
data: mobileData
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('查询移动数据状态失败:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 开启或关闭移动数据连接
|
||||
this.app.post('/api/modem/mobile-data', async (req, res) => {
|
||||
try {
|
||||
if (typeof req.body?.enabled !== 'boolean') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '缺少必要参数: enabled'
|
||||
});
|
||||
}
|
||||
|
||||
const mobileData = await this.modem.setMobileDataEnabled(req.body.enabled);
|
||||
res.json({
|
||||
success: true,
|
||||
message: req.body.enabled ? '移动数据已开启' : '移动数据已关闭',
|
||||
data: mobileData
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('设置移动数据状态失败:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 发送AT命令
|
||||
this.app.post('/api/modem/at', async (req, res) => {
|
||||
try {
|
||||
|
||||
+17
-9
@@ -46,7 +46,7 @@ async function main() {
|
||||
logger.info('配置加载完成');
|
||||
|
||||
// 创建模组管理器
|
||||
const modem = new ModemManager(config.serial);
|
||||
const modem = new ModemManager(config.serial, config.mobileData);
|
||||
|
||||
// 创建长短信管理器
|
||||
const concatManager = new ConcatManager();
|
||||
@@ -111,19 +111,27 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 优雅退出
|
||||
process.on('SIGINT', async () => {
|
||||
logger.info('\n收到 SIGINT 信号,正在关闭...');
|
||||
async function shutdown(signal) {
|
||||
logger.info(`\n收到 ${signal} 信号,正在关闭...`);
|
||||
concatManager.stopTimeoutChecker();
|
||||
await modem.forceMobileDataOff(`${signal}退出保护`);
|
||||
await modem.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 优雅退出
|
||||
process.on('SIGINT', () => {
|
||||
shutdown('SIGINT').catch((err) => {
|
||||
logger.error('关闭失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
logger.info('\n收到 SIGTERM 信号,正在关闭...');
|
||||
concatManager.stopTimeoutChecker();
|
||||
await modem.close();
|
||||
process.exit(0);
|
||||
process.on('SIGTERM', () => {
|
||||
shutdown('SIGTERM').catch((err) => {
|
||||
logger.error('关闭失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
// 捕获未处理的异常
|
||||
|
||||
+279
-40
@@ -7,9 +7,12 @@ import path from 'path';
|
||||
import logger from './logger.js';
|
||||
|
||||
class ModemManager extends EventEmitter {
|
||||
constructor(config) {
|
||||
constructor(config, mobileDataConfig = {}) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.mobileDataConfig = {
|
||||
cid: this.normalizeMobileDataCid(mobileDataConfig.cid)
|
||||
};
|
||||
this.port = null;
|
||||
this.parser = null;
|
||||
this.ready = false;
|
||||
@@ -18,6 +21,18 @@ class ModemManager extends EventEmitter {
|
||||
model: '未知',
|
||||
version: '未知'
|
||||
};
|
||||
this.mobileData = {
|
||||
cid: this.mobileDataConfig.cid,
|
||||
desiredEnabled: false,
|
||||
enabled: false,
|
||||
targetEnabled: false,
|
||||
anyActive: false,
|
||||
status: 'disabled',
|
||||
mode: 'unknown',
|
||||
contexts: [],
|
||||
lastCheckedAt: null,
|
||||
error: ''
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +371,262 @@ class ModemManager extends EventEmitter {
|
||||
return result.reason;
|
||||
}
|
||||
|
||||
normalizeMobileDataCid(value) {
|
||||
const cid = Number.parseInt(value, 10);
|
||||
if (Number.isInteger(cid) && cid > 0 && cid <= 15 && cid !== 8) {
|
||||
return cid;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
getMobileDataMode() {
|
||||
return this.isML307Family() ? 'mipcall' : 'cgact';
|
||||
}
|
||||
|
||||
getMobileDataCommand(mode = this.getMobileDataMode(), enabled = false, cid = this.mobileDataConfig.cid) {
|
||||
if (mode === 'mipcall') {
|
||||
return `AT+MIPCALL=${enabled ? 1 : 0},${cid}`;
|
||||
}
|
||||
|
||||
return `AT+CGACT=${enabled ? 1 : 0},${cid}`;
|
||||
}
|
||||
|
||||
getMobileDataStatusCommand(mode = this.getMobileDataMode()) {
|
||||
return mode === 'mipcall' ? 'AT+MIPCALL?' : 'AT+CGACT?';
|
||||
}
|
||||
|
||||
getCachedMobileDataStatus() {
|
||||
return {
|
||||
...this.mobileData,
|
||||
contexts: this.mobileData.contexts.map(item => ({ ...item }))
|
||||
};
|
||||
}
|
||||
|
||||
async getMobileDataStatus() {
|
||||
const mode = this.getMobileDataMode();
|
||||
const command = this.getMobileDataStatusCommand(mode);
|
||||
|
||||
try {
|
||||
const resp = await this.sendATCommand(command, 5000);
|
||||
const status = this.parseMobileDataStatusResponse(resp, mode);
|
||||
this.updateMobileDataStatus(status);
|
||||
return this.getCachedMobileDataStatus();
|
||||
} catch (err) {
|
||||
logger.warn(`查询移动数据状态失败: ${err.message}`);
|
||||
this.updateMobileDataStatus({
|
||||
mode,
|
||||
cid: this.mobileDataConfig.cid,
|
||||
status: 'unknown',
|
||||
enabled: false,
|
||||
targetEnabled: false,
|
||||
anyActive: false,
|
||||
contexts: [],
|
||||
error: err.message
|
||||
});
|
||||
return this.getCachedMobileDataStatus();
|
||||
}
|
||||
}
|
||||
|
||||
parseMobileDataStatusResponse(resp, mode) {
|
||||
const contexts = [];
|
||||
const lines = resp.split('\n').map(line => line.trim()).filter(Boolean);
|
||||
|
||||
for (const line of lines) {
|
||||
const payload = this.extractMobileDataPayload(line, mode);
|
||||
if (!payload) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = payload.match(/^(\d+)\s*,\s*(\d+)(.*)$/);
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cid = Number.parseInt(match[1], 10);
|
||||
const state = Number.parseInt(match[2], 10);
|
||||
const addresses = [...match[3].matchAll(/"([^"]+)"/g)].map(item => item[1]).filter(Boolean);
|
||||
|
||||
contexts.push({
|
||||
cid,
|
||||
active: state === 1,
|
||||
state,
|
||||
addresses
|
||||
});
|
||||
}
|
||||
|
||||
const target = contexts.find(item => item.cid === this.mobileDataConfig.cid);
|
||||
const anyActive = contexts.some(item => item.active);
|
||||
const targetEnabled = target ? target.active : false;
|
||||
|
||||
return {
|
||||
mode,
|
||||
cid: this.mobileDataConfig.cid,
|
||||
enabled: anyActive,
|
||||
targetEnabled,
|
||||
anyActive,
|
||||
status: anyActive ? 'enabled' : 'disabled',
|
||||
contexts,
|
||||
error: ''
|
||||
};
|
||||
}
|
||||
|
||||
extractMobileDataPayload(line, mode) {
|
||||
if (mode === 'mipcall') {
|
||||
if (line.startsWith('+MIPCALL:')) {
|
||||
return line.slice('+MIPCALL:'.length).trim();
|
||||
}
|
||||
|
||||
if (/^\d+\s*,\s*\d+/.test(line)) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.startsWith('+CGACT:')) {
|
||||
return line.slice('+CGACT:'.length).trim();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
updateMobileDataStatus(status) {
|
||||
this.mobileData = {
|
||||
...this.mobileData,
|
||||
...status,
|
||||
cid: this.mobileDataConfig.cid,
|
||||
lastCheckedAt: new Date().toISOString(),
|
||||
contexts: Array.isArray(status.contexts) ? status.contexts : []
|
||||
};
|
||||
}
|
||||
|
||||
getMobileDataTargetCids(status) {
|
||||
const cids = new Set([this.mobileDataConfig.cid]);
|
||||
for (const context of status.contexts || []) {
|
||||
if (context.active && context.cid !== 8) {
|
||||
cids.add(context.cid);
|
||||
}
|
||||
}
|
||||
|
||||
return [...cids].filter(cid => this.normalizeMobileDataCid(cid) === cid);
|
||||
}
|
||||
|
||||
async setMobileDataEnabled(enabled, options = {}) {
|
||||
this.mobileData.desiredEnabled = Boolean(enabled);
|
||||
if (enabled) {
|
||||
return await this.enableMobileData(options);
|
||||
}
|
||||
|
||||
return await this.disableMobileData(options);
|
||||
}
|
||||
|
||||
async enableMobileData(options = {}) {
|
||||
const mode = this.getMobileDataMode();
|
||||
const cid = this.mobileDataConfig.cid;
|
||||
const command = this.getMobileDataCommand(mode, true, cid);
|
||||
const label = mode === 'mipcall' ? '开启移动数据拨号' : '激活PDP数据连接';
|
||||
|
||||
logger.warn(`准备开启移动数据(CID ${cid}),可能产生流量费用`);
|
||||
const resp = await this.sendATWithRetry(command, {
|
||||
timeout: 30000,
|
||||
retries: 1,
|
||||
label,
|
||||
required: false
|
||||
});
|
||||
|
||||
const status = await this.waitForMobileDataState(true, options.timeout || 20000);
|
||||
if (!resp || !status.anyActive) {
|
||||
throw new Error('移动数据开启失败,未检测到已激活的数据连接');
|
||||
}
|
||||
|
||||
logger.info(`✓ 已开启移动数据(CID ${cid})`);
|
||||
return status;
|
||||
}
|
||||
|
||||
async disableMobileData(options = {}) {
|
||||
const mode = this.getMobileDataMode();
|
||||
const before = await this.getMobileDataStatus();
|
||||
const cids = this.getMobileDataTargetCids(before);
|
||||
const label = mode === 'mipcall' ? '关闭移动数据拨号' : '停用PDP数据连接';
|
||||
let commandSucceeded = false;
|
||||
|
||||
logger.info(`${options.reason || '手动操作'}: 关闭移动数据,避免流量消耗`);
|
||||
|
||||
for (const cid of cids) {
|
||||
const command = this.getMobileDataCommand(mode, false, cid);
|
||||
const resp = await this.sendATWithRetry(command, {
|
||||
timeout: 8000,
|
||||
retries: 1,
|
||||
label: `${label}(CID ${cid})`,
|
||||
required: false
|
||||
});
|
||||
|
||||
commandSucceeded = commandSucceeded || Boolean(resp);
|
||||
}
|
||||
|
||||
const status = await this.waitForMobileDataState(false, options.timeout || 8000);
|
||||
if (status.anyActive) {
|
||||
const message = `移动数据关闭后仍检测到活动连接: ${this.formatMobileDataContexts(status.contexts)}`;
|
||||
if (options.required === false) {
|
||||
logger.warn(message);
|
||||
return status;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
if (status.status === 'unknown') {
|
||||
const message = commandSucceeded ? '移动数据关闭命令已发送,但状态未确认' : '移动数据关闭命令未确认成功';
|
||||
if (options.required === false) {
|
||||
logger.warn(message);
|
||||
return status;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
if (!commandSucceeded && before.status === 'unknown' && options.required !== false) {
|
||||
throw new Error('移动数据关闭命令未确认成功');
|
||||
}
|
||||
|
||||
logger.info('✓ 移动数据已关闭');
|
||||
return status;
|
||||
}
|
||||
|
||||
async forceMobileDataOff(stage) {
|
||||
this.mobileData.desiredEnabled = false;
|
||||
try {
|
||||
return await this.disableMobileData({
|
||||
reason: stage,
|
||||
required: false,
|
||||
timeout: 5000
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`${stage}: 移动数据关闭未确认: ${err.message}`);
|
||||
return this.getCachedMobileDataStatus();
|
||||
}
|
||||
}
|
||||
|
||||
async waitForMobileDataState(enabled, timeout = 10000) {
|
||||
const startedAt = Date.now();
|
||||
let status = await this.getMobileDataStatus();
|
||||
|
||||
while (status.status !== 'unknown' && status.anyActive !== enabled && Date.now() - startedAt < timeout) {
|
||||
await this.sleep(1000);
|
||||
status = await this.getMobileDataStatus();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
formatMobileDataContexts(contexts = []) {
|
||||
const active = contexts
|
||||
.filter(item => item.active)
|
||||
.map(item => `CID ${item.cid}`)
|
||||
.join(', ');
|
||||
|
||||
return active || '无';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置URC监听器
|
||||
*/
|
||||
@@ -521,7 +792,10 @@ class ModemManager extends EventEmitter {
|
||||
await this.waitSIMReady();
|
||||
await this.ensureFullFunctionality();
|
||||
|
||||
// 4. 等待网络注册
|
||||
// 4. 启动保护:先关闭移动数据拨号,再等待短信所需的网络注册。
|
||||
await this.forceMobileDataOff('启动保护(CFUN=1后)');
|
||||
|
||||
// 5. 等待网络注册
|
||||
retries = 0;
|
||||
while (!(await this.waitCEREG()) && retries < 30) {
|
||||
logger.info('等待网络注册...');
|
||||
@@ -536,10 +810,10 @@ class ModemManager extends EventEmitter {
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
// 5. 数据连接处理。ML307文档不建议用CGACT做PDP激活/去激活。
|
||||
await this.disableDataConnection();
|
||||
// 6. 驻网后再关一次,防止模组自动拨号在驻网完成后重新拉起。
|
||||
await this.forceMobileDataOff('启动保护(驻网后)');
|
||||
|
||||
// 6. 按文档配置短信功能,并启用本项目需要的PDU模式
|
||||
// 7. 按文档配置短信功能,并启用本项目需要的PDU模式
|
||||
await this.configureSMS();
|
||||
|
||||
logger.info('模组初始化完成');
|
||||
@@ -593,41 +867,6 @@ class ModemManager extends EventEmitter {
|
||||
logger.info('✓ 已设置协议栈功能模式(CFUN=1)');
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模组型号处理数据连接,避免ML307系列使用文档不推荐的CGACT。
|
||||
*/
|
||||
async disableDataConnection() {
|
||||
if (this.isML307Family()) {
|
||||
logger.info('ML307系列跳过AT+CGACT,按文档尝试断开应用层拨号(AT+MIPCALL=0,1)');
|
||||
const resp = await this.sendATWithRetry('AT+MIPCALL=0,1', {
|
||||
timeout: 5000,
|
||||
retries: 1,
|
||||
label: '断开应用层拨号',
|
||||
required: false
|
||||
});
|
||||
|
||||
if (resp) {
|
||||
logger.info('✓ 已断开应用层拨号连接');
|
||||
} else {
|
||||
logger.warn('应用层拨号未断开或当前未激活,继续启动');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = await this.sendATWithRetry('AT+CGACT=0,1', {
|
||||
timeout: 5000,
|
||||
retries: 3,
|
||||
label: '禁用数据连接',
|
||||
required: false
|
||||
});
|
||||
|
||||
if (resp) {
|
||||
logger.info('✓ 已禁用数据连接(AT+CGACT=0,1),防止流量消耗');
|
||||
} else {
|
||||
logger.warn('设置CGACT失败,可能会消耗流量');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按ML307A文档配置短信功能;项目接收逻辑依赖PDU模式下的+CMT上报。
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user