diff --git a/public/admin.html b/public/admin.html index 94cc966..02d1cde 100644 --- a/public/admin.html +++ b/public/admin.html @@ -115,7 +115,7 @@
mobile data

--

-

启动时会自动关闭移动数据。

+

启动时会自动断开应用层拨号。

@@ -379,4 +379,4 @@ - \ No newline at end of file + diff --git a/public/assets/admin.js b/public/assets/admin.js index d5dcf77..64ac881 100644 --- a/public/assets/admin.js +++ b/public/assets/admin.js @@ -184,25 +184,26 @@ function renderMobileData(mobileData = {}) { const enabled = Boolean(mobileData.enabled || mobileData.anyActive); const cid = mobileData.cid || 1; const mode = formatMobileDataMode(mobileData.mode); + const isMipcall = mobileData.mode === 'mipcall'; const activeContexts = formatMobileDataContexts(mobileData.contexts || []); if (unknown) { setText(elements.mobileDataSummary, '未知'); setText(elements.mobileDataState, '状态未知'); - setText(elements.mobileDataHint, mobileData.error || '未能确认移动数据状态,可先强制关闭。'); - elements.mobileDataToggle.textContent = '强制关闭流量'; + setText(elements.mobileDataHint, mobileData.error || (isMipcall ? '未能确认应用层拨号状态,可先强制断开。' : '未能确认移动数据状态,可先强制关闭。')); + elements.mobileDataToggle.textContent = isMipcall ? '强制断开拨号' : '强制关闭流量'; elements.mobileDataToggle.dataset.action = 'disable'; } else if (enabled) { - setText(elements.mobileDataSummary, '已开启'); - setText(elements.mobileDataState, '流量已开启'); + setText(elements.mobileDataSummary, isMipcall ? '已连接' : '已开启'); + setText(elements.mobileDataState, isMipcall ? '应用拨号已连接' : '流量已开启'); setText(elements.mobileDataHint, activeContexts ? `${mode} 检测到 ${activeContexts} 处于活动状态。` : `CID ${cid} 处于活动状态。`); - elements.mobileDataToggle.textContent = '关闭流量'; + elements.mobileDataToggle.textContent = isMipcall ? '断开拨号' : '关闭流量'; elements.mobileDataToggle.dataset.action = 'disable'; } else { - setText(elements.mobileDataSummary, '已关闭'); - setText(elements.mobileDataState, '流量已关闭'); - setText(elements.mobileDataHint, `${mode} CID ${cid} 未建立移动数据连接,重启后也会先保持关闭。`); - elements.mobileDataToggle.textContent = '开启流量'; + setText(elements.mobileDataSummary, isMipcall ? '已断开' : '已关闭'); + setText(elements.mobileDataState, isMipcall ? '应用拨号已断开' : '流量已关闭'); + setText(elements.mobileDataHint, isMipcall ? `${mode} CID ${cid} 未建立应用层拨号连接;已保护性尝试停用PDP。` : `${mode} CID ${cid} 未建立移动数据连接,重启后也会先保持关闭。`); + elements.mobileDataToggle.textContent = isMipcall ? '开启拨号' : '开启流量'; elements.mobileDataToggle.dataset.action = 'enable'; } @@ -674,7 +675,7 @@ async function toggleMobileData() { body: JSON.stringify({ enabled }) }); renderMobileData(result.data || {}); - setStatusMessage(elements.mobileDataStatus, result.message || (enabled ? '移动数据已开启。' : '移动数据已关闭。'), 'success'); + setStatusMessage(elements.mobileDataStatus, result.message || (enabled ? '已开启。' : '已关闭。'), 'success'); await refreshLogs(); } catch (err) { setStatusMessage(elements.mobileDataStatus, err.message, 'error'); diff --git a/src/api.js b/src/api.js index 6460cd3..6c55876 100644 --- a/src/api.js +++ b/src/api.js @@ -496,9 +496,13 @@ class APIServer { } const mobileData = await this.modem.setMobileDataEnabled(req.body.enabled); + const message = mobileData.mode === 'mipcall' + ? (req.body.enabled ? '应用层拨号已开启' : '应用层拨号已断开') + : (req.body.enabled ? '移动数据已开启' : '移动数据已关闭'); + res.json({ success: true, - message: req.body.enabled ? '移动数据已开启' : '移动数据已关闭', + message, data: mobileData }); } catch (err) { diff --git a/src/modem.js b/src/modem.js index 3edce74..9d2a695 100644 --- a/src/modem.js +++ b/src/modem.js @@ -544,19 +544,62 @@ class ModemManager extends EventEmitter { async disableMobileData(options = {}) { const mode = this.getMobileDataMode(); + if (mode === 'mipcall') { + return await this.disableMipcallMobileData(options); + } + + return await this.disableCgactMobileData(options); + } + + async disableMipcallMobileData(options = {}) { const before = await this.getMobileDataStatus(); const cids = this.getMobileDataTargetCids(before); - const label = mode === 'mipcall' ? '关闭移动数据拨号' : '停用PDP数据连接'; - let commandSucceeded = false; - logger.info(`${options.reason || '手动操作'}: 关闭移动数据,避免流量消耗`); + logger.info(`${options.reason || '手动操作'}: 断开ML307应用层拨号连接,避免流量消耗`); for (const cid of cids) { - const command = this.getMobileDataCommand(mode, false, cid); - const resp = await this.sendATWithRetry(command, { + try { + await this.sendATWithRetry(this.getMobileDataCommand('mipcall', false, cid), { + timeout: 8000, + retries: 1, + label: `断开应用层拨号(CID ${cid})`, + required: true + }); + } catch (err) { + err.mobileDataCritical = true; + throw err; + } + } + + const status = await this.waitForMobileDataState(false, options.timeout || 8000); + if (status.anyActive) { + const err = new Error(`MIPCALL断开后仍检测到活动连接: ${this.formatMobileDataContexts(status.contexts)}`); + err.mobileDataCritical = true; + throw err; + } + + if (status.status === 'unknown') { + logger.warn('MIPCALL断开命令已成功发送,但状态未确认'); + } + + await this.tryDeactivatePdpContexts(cids); + + logger.info('✓ 应用层拨号连接已断开'); + return status; + } + + async disableCgactMobileData(options = {}) { + const before = await this.getMobileDataStatus(); + const cids = this.getMobileDataTargetCids(before); + let commandSucceeded = false; + + logger.info(`${options.reason || '手动操作'}: 停用PDP数据连接,避免流量消耗`); + + for (const cid of cids) { + const resp = await this.sendATWithRetry(this.getMobileDataCommand('cgact', false, cid), { timeout: 8000, retries: 1, - label: `${label}(CID ${cid})`, + label: `停用PDP数据连接(CID ${cid})`, required: false }); @@ -592,6 +635,21 @@ class ModemManager extends EventEmitter { return status; } + async tryDeactivatePdpContexts(cids) { + for (const cid of cids) { + const resp = await this.sendATWithRetry(this.getMobileDataCommand('cgact', false, cid), { + timeout: 8000, + retries: 1, + label: `保护性停用PDP(CID ${cid})`, + required: false + }); + + if (!resp) { + logger.warn(`保护性停用PDP(CID ${cid})未成功,已按MIPCALL断开结果继续`); + } + } + } + async forceMobileDataOff(stage) { this.mobileData.desiredEnabled = false; try { @@ -601,6 +659,10 @@ class ModemManager extends EventEmitter { timeout: 5000 }); } catch (err) { + if (err.mobileDataCritical) { + throw err; + } + logger.warn(`${stage}: 移动数据关闭未确认: ${err.message}`); return this.getCachedMobileDataStatus(); } @@ -792,7 +854,7 @@ class ModemManager extends EventEmitter { await this.waitSIMReady(); await this.ensureFullFunctionality(); - // 4. 启动保护:先关闭移动数据拨号,再等待短信所需的网络注册。 + // 4. 启动保护:先断开应用层拨号,再等待短信所需的网络注册。 await this.forceMobileDataOff('启动保护(CFUN=1后)'); // 5. 等待网络注册 @@ -810,7 +872,7 @@ class ModemManager extends EventEmitter { this.ready = false; } - // 6. 驻网后再关一次,防止模组自动拨号在驻网完成后重新拉起。 + // 6. 驻网后再断开一次,防止模组自动拨号在驻网完成后重新拉起。 await this.forceMobileDataOff('启动保护(驻网后)'); // 7. 按文档配置短信功能,并启用本项目需要的PDU模式