cards: read EF.ICCID at equip and auto-select the matching preset (v2.2.16)
- Server: _decode_iccid (nibble-swapped E.118 digits, trailing-F pad) and _read_iccid (best-effort MF/2FE2 read through the parent-scoped select helper; the previous selection is restored, the read never raises). The equip path records the digit string before the TERMINAL PROFILE, i.e. before any CAT session is active; a startup with a card does the same. The value is cleared on card removal and exposed as /api/status 'iccid' (only while connected). - PWA: when a connected status update reports a *new* ICCID, the matching card preset is selected in both SCP80 views - Secured Packet (sp-card-sel + form fill) and RAM (ram-card-sel + _ramCardIdx). Matching normalizes digits and accepts the raw EF hex form, leading zeros ignored; a manual choice for the same card is kept until the next equip, and a card removal re-arms the auto-selection. The status line shows the ICCID. - Tests: tests/test_iccid.py (decode variants, model + probe read, equip recording, disconnect clearing) and frontend/tests/cards_iccid.test.js (normalize, find, select, no-override, card swap); the card_state test harness stubs the new hook and covers the guard reset. - Docs: api.md /api/status fields, help EN+RU (SCP80 intro + Cards tab), READMEs, AGENTS. SW cache otaman-v183.
This commit is contained in:
+42
-1
@@ -18,7 +18,7 @@
|
||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.15</span></h1>
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.16</span></h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
||||
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
||||
@@ -4859,6 +4859,7 @@ async function pysimRefresh() {
|
||||
(data.sel_ctrl ? ' | SEL: <b>' + esc(data.sel_ctrl) + '</b>' : '') +
|
||||
' | Channels: <b>' + (data.channels && data.channels.length ? data.channels.join(', ') : 'none') + '</b><br>' +
|
||||
'ADM: <b>' + (data.adm_verified ? 'verified' : 'not verified') + '</b>' +
|
||||
(data.iccid ? ' | ICCID: <b>' + esc(data.iccid) + '</b>' : '') +
|
||||
' | Selection: <b>' + (data.current_selection
|
||||
? (data.current_selection.name || data.current_selection.fid || '?')
|
||||
: 'none') + '</b>' +
|
||||
@@ -6114,6 +6115,44 @@ function cardsRebuildSelect() {
|
||||
if (prev !== '' && cards[parseInt(prev, 10)]) sel.value = prev;
|
||||
}
|
||||
|
||||
// ===== ICCID-based preset auto-selection =====
|
||||
// After every equip /api/status carries the card's EF.ICCID (2FE2); when a
|
||||
// preset holds the same number it is selected in both SCP80 views (Secured
|
||||
// packet + RAM). Users may store the ICCID as digits or as the raw EF hex,
|
||||
// so both forms are normalized to plain digits.
|
||||
function cardsNormIccid(v) {
|
||||
let s = String(v == null ? '' : v).trim().toUpperCase().replace(/[\s-]/g, '');
|
||||
if (!s) return '';
|
||||
if (/[A-F]/.test(s)) s = decIccid(s); // raw EF content (nibble-swapped)
|
||||
return String(s || '').replace(/\D/g, '').replace(/^0+/, '');
|
||||
}
|
||||
|
||||
function cardsFindByIccid(iccid) {
|
||||
const norm = cardsNormIccid(iccid);
|
||||
if (!norm) return -1;
|
||||
return cards.findIndex(c => cardsNormIccid(c.iccid) === norm);
|
||||
}
|
||||
|
||||
let _cardsAutoIccid = null;
|
||||
|
||||
function cardsAutoSelectByIccid(iccid) {
|
||||
// Act only when the equip reports a *new* ICCID: a manual preset choice
|
||||
// for the same card must not be overridden by later status updates.
|
||||
const norm = cardsNormIccid(iccid);
|
||||
if (!norm) { _cardsAutoIccid = null; return -1; }
|
||||
if (norm === _cardsAutoIccid) return -1;
|
||||
_cardsAutoIccid = norm;
|
||||
const idx = cardsFindByIccid(norm);
|
||||
if (idx < 0) return -1;
|
||||
const sp = document.getElementById('sp-card-sel');
|
||||
if (sp) sp.value = String(idx);
|
||||
const ram = document.getElementById('ram-card-sel');
|
||||
if (ram) ram.value = String(idx);
|
||||
_ramCardIdx = idx;
|
||||
cardsApply(String(idx));
|
||||
return idx;
|
||||
}
|
||||
|
||||
function cardsApply(idx) {
|
||||
const c = cards[parseInt(idx)];
|
||||
if (!c) return;
|
||||
@@ -7622,9 +7661,11 @@ function pysimCardStateUpdate(status) {
|
||||
if (status.connected) {
|
||||
pysimSetConnected(true);
|
||||
pysimResetCardData(true);
|
||||
cardsAutoSelectByIccid(status.iccid);
|
||||
return;
|
||||
}
|
||||
pysimSetConnected(false);
|
||||
_cardsAutoIccid = null;
|
||||
const statusEl = document.getElementById('pysim-status');
|
||||
if (statusEl && statusEl.textContent.trim() !== '') {
|
||||
if (status.equipping || (status.card_present && status.auto_equip)) {
|
||||
|
||||
Reference in New Issue
Block a user