startup: cardless start is a normal state, not an init failure (v2.2.17)

- __main__ catches NoCardError from the startup init (fast and stock paths,
  and the stock fallback) and reports it with one line - 'INIT: no card in
  the reader - server ready; insert a card or press Equip' - without a
  traceback and without the stock-pysim fallback. Any other init failure
  still falls back with its traceback.
- The cardless PysimApp construction no longer prints pySim's 'Waiting for
  card...' or pySim-shell's 'pySim-shell not equipped!': a new _LineFilter
  (server.py) drops whole matching lines while proxying encoding/isatty/
  fileno (the cmd2 Rich console probes them on its stdout).
- Removed the now-duplicate 'INIT: card not initialized' tail line and the
  cat_cla assignment when no card was detected (scc exists but is cardless).
- Tests: tests/test_startup_cardless.py (line filter semantics incl. partial
  lines, one-line cardless report, no fallback call, real failures keep the
  fallback + traceback, muted PysimApp construction). Live-verified against
  an empty SCR 3310: two init lines, no traceback, API serves 2.2.17.
- Docs: READMEs quick start, help EN+RU (Card reader), AGENTS (equip flow).
  SW cache otaman-v184.
This commit is contained in:
2026-09-18 23:41:12 +03:00
parent 6d5d23487a
commit 235388a5d0
10 changed files with 247 additions and 16 deletions
+46 -1
View File
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE
VERSION = '2.2.16'
VERSION = '2.2.17'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -59,6 +59,51 @@ def _tlog(msg):
sys.stderr.write('TIMING [+%7.3fs] %s\n' % (time.time() - _T0, msg))
class _LineFilter:
"""Text stream that drops whole lines matching any of the given substrings
and forwards everything else to the wrapped stream. Used to mute pySim/
pySim-shell internals we report ourselves (e.g. 'Waiting for card...' or
'pySim-shell not equipped!'). Lines arrive through write(); the dropped
lines are written as one call each by pySim's print/logger and by cmd2's
Rich console, so a substring check per line is reliable here."""
def __init__(self, stream, patterns):
self._stream = stream
self._patterns = list(patterns)
self._pending = ''
def write(self, text):
text = self._pending + text
self._pending = ''
if not text:
return
if not text.endswith('\n'):
# Keep a trailing partial line so a match is not missed when the
# line is completed by the next write().
nl = text.rfind('\n')
if nl < 0:
self._pending = text
return
self._pending = text[nl + 1:]
text = text[:nl + 1]
for line in text.splitlines(True):
if not any(p in line for p in self._patterns):
self._stream.write(line)
self._stream.flush()
def flush(self):
if self._pending:
line, self._pending = self._pending, ''
if not any(p in line for p in self._patterns):
self._stream.write(line)
self._stream.flush()
def __getattr__(self, name):
# encoding/isatty/fileno: the Rich console probes these on the stream
# (cmd2's self.stdout), so they must keep working.
return getattr(self._stream, name)
_APDU_TIMES = []
_APDU_TIME_COLLECT = False