BIP was only reachable through the SCP81 listener; cards that use TCP for
other purposes (an applet's OPEN CHANNEL, a push trigger without HTTP OTA)
now get a generic control surface, independent of SCP81.
Server:
- /api/bip/control starts a plain BIP session in three modes: sink
(default; a local TcpDumpServer that accepts the card's channels and only
logs conn/sink-rx/conn-close, never answering; port 0 = ephemeral, the
bound port is reported), passthru (dial the OPEN CHANNEL destination, TCP
client only) and redirect (fixed host:port).
- /api/bip/status returns {owner, bip, listener}; /api/bip/log and
/api/bip/log-clear share the BIP event log with /api/scp81/log.
- The session state is shared with the SCP81 listener and only one session
runs at a time: _bip_session_stop() stops whichever control started it
and the control responses report it as `replaced` (both directions,
SCP81 <-> BIP). State renamed _SCP81_MODE/_TARGET/_LISTENER/_LINK_EVENTS
-> _BIP_* plus _BIP_OWNER.
- TcpDumpServer logs conn-close and counts accepted connections; stop()
joins the accept thread so the port is really free on restart.
- The new control endpoints are blocked during test-script runs.
PWA:
- New Simulator pill BIP (after TR Config): mode select with notes,
Host/Port rules, Start/Stop with a "replaced" notice, status line (mode,
bound address, owner, channels), a Channels box and the shared BIP log
(reuses the SCP81 log renderer, now showing `peer`).
- The SCP81 status line marks a session owned by the BIP pill.
Help EN/RU 8.10, docs/api.md, AGENTS; CAT_TP/UDP recorded as not
implemented.
Tests: tests/test_bip.py (9) and frontend/tests/bip.test.js (6).
600 frontend / 482 Python green; version 3.6.0; sw simple-v273.
- Phone simulator tab: a fourth pill **Test script** - script list
(localStorage `simple_tests`) with New/Clone/Delete/Export/Import, a
form-based step editor (modal) and a run panel.
- Actions: ENVELOPE (Event Download), Menu Selection, UPDATE/READ file by
path, raw APDU, SCP80 (C-APDU or a pre-built packet, per-step TAR/SPI1/
SPI2 overrides; the preset is matched by the equipped card's ICCID and
the final counter is written back) and STATUS with an optional poll.
Every action has SW/data checks (exact or ?-mask) and a PoR check for
SCP80, with an error (stops) / warning (continues) fail level.
- Expectations: FETCH the command announced by the previous step, verify
type/qualifier/text/item/raw and answer with the scripted TERMINAL
RESPONSE (result, item id, text, extra TLVs); templates for STK menu
browsing and an applet via SCP80.
- Run panel: progress banner (plus a marker on the pill), per-step
OK/Warning/Error badges with expected vs actual values, sent/received
hex, decoded commands and the SCP80 counter; 500 ms polling while a run
is active, and a running script disables other card controls
(data-needs gate; the server answers 409 to other card endpoints).
- Help EN/RU (8.9 Test script), AGENTS; version 3.5.11; sw simple-v264.
579 frontend / 467 Python green.
New eSIM pill (Phone simulator) for SGP.22/32 cards, built on pySim's
ES10 static API — no lpac, no new dependencies, no pysim patches, no
SM-DP+ interaction:
- Chip: EID, EUICCInfo1/2, configured addresses (ES10a/b).
- Profiles: GetProfilesInfo with metadata (state, nickname, provider,
ICCID, ISD-P AID, class, owner, icon).
- Notifications: read-only ListNotification viewer.
- Switch: Enable/DisableProfile with RefreshFlag=1; the card's REFRESH
(fetched/answered/logged by the transport's proactive handler) or an ok
result triggers _esim_reinit() — reset + equip + _apply_equipped_card —
so the ICCID, network state and cached views are re-read.
- Server: pysim_simple_server/esim.py, GET /api/esim/chip|profiles|
notifications, POST /api/esim/profile (all under _CARD_LOCK; 400
not_an_euicc), euicc/eid in /api/status.
- Tests: tests/test_esim.py (fake scc, monkeypatched store_data_tlv),
frontend/tests/esim.test.js; help EN/RU, docs/api.md, AGENTS.
server:
- netsim: LOCI/PSLOCI dummy builders take a status; the EPSLOCI dummy is
wiped to 0B F6 + FF per UICC_NAA.md C3; roaming_denied now emulates the
permanent 'PLMN not allowed' rejection (C3a): status 010, EPSNSC dropped,
EF.FPLMN append with TS 31.102 4.2.16 shift semantics and a home-PLMN
guard (HPLMNwAcT/EHPLMN, IMSI fallback), optional 'Rejection: write FPLMN'
toggle; write steps carry the logical key; SCENARIO_SERVICE map;
insert_fplmn/fplmn_entries/parse_imsi helpers
- netstate.py: cached per-session monitor state for the 12 network EFs,
step-based patch, simulated service state, derived location with country/
operator (optional MCC/MNC list) and roaming class
- server: monitor read at equip right after a readable ICCID (skipped
otherwise), cleared on card removal; GET /api/net-state and POST
/api/net-state-refresh; net-sim patches the cache from the written bytes
and re-reads EF.IMSI; Location-status events set the service state and
re-read EF.IMSI (multi-IMSI applets)
frontend:
- Phone tab: 'Network state' panel next to Network simulation with the
simulated service badge (Undefined until simulated; normal/limited/no
service + rejection marker), location/roaming line, compact per-file
summaries with full-decode tooltips and a Refresh button; no card polling
- EF decoders: EF.FPLMN (FFFFFF gaps are not terminators) and EF.EHPLMN
- i18n EN/RU, help updated; SW cache simple-v203
tests: 295 Python / 453 frontend
TERMINAL PROFILE:
- GET /api/terminal-profile returns the profile in effect + the CLI default;
POST /api/terminal-profile validates ({profile}, hex, even, 1-255 bytes),
stores it in memory and re-sends it, resetting the STK session like
/api/rescue (the shared _resend_terminal_profile helper; rescue now
delegates to it). __main__ keeps server.cli_terminal_profile.
- Phone tab: a TERMINAL PROFILE block next to STATUS and Polling with the
current hex/byte count, Send (re-send) and Configure. The Configure
dialog has a device-model preset selector, a hex field and a per-bit
form generated from a 264-entry table for TS 102 223 V18.3.0 5.2 bytes
1-33 (pySim's table as scaffold, later bytes/3GPP bits added from the
spec; beyond the table generic RFU labels). Form <-> hex sync both
ways, hex authoritative, bits preserved. Apply posts the new value.
- Presets: Xiaomi Mi A1 (project default), Quectel GSM module example.
In-memory only, no persistence.
Custom files:
- Canonical paths rooted at MF / ADF.USIM / ADF.ISIM; entries are
{path, name, kind}. The editor now uses root + parent-DF selector +
4-hex FID + alias, requires the parent DF to be defined first, rejects
duplicates, rewrites descendants when a DF's path changes and cascades
deletes after a confirmation.
- Legacy forms are normalized on load/import (3F00/... -> MF/..., relative
a153/4954 resolved against the custom DFs); unresolvable entries are
dropped and reported in the list.
- Tree injection matches by exact parent path via the new pysimFsNodePath
(same-FID DFs under different parents no longer collide);
profilerCustomNameForPath uses the same normalization.
SW cache otaman-v167; help EN/RU + docs/api.md + AGENTS updated.
Tests: 236 Python + 371 frontend.
- Phone pill: STK menu, STATUS and polling and the subscribed-events list
in one row, proactive command log full-width below (room for more
elements)
- TR Config pill: response data injected into TERMINAL RESPONSEs;
currently the PROVIDE LOCAL INFORMATION dictionary, structured as
heading + body blocks for future proactive-command responses
- phoneSwitchSubtab() mirrors the other sub-tab switchers and sets help
anchors stk-menu / pli-dict; switchTab('phone') always opens Phone
- refreshDynamicI18n renders only the visible phone panel
- help EN/RU section 6 regrouped (6.4 STATUS polling under Phone, 6.5
TR Config response data), READMEs and AGENTS.md updated
- structural and behavioral tests (html.test.js, phone_tabs.test.js);
SW cache v93 -> v94