Compare commits

..

No commits in common. "63c3167d9f552cb30ee1d480e2d0649c5c2e62ec" and "3cf13f14f2d51ef96006b8cf8e351ea14b96cb57" have entirely different histories.

6 changed files with 7 additions and 148 deletions

View File

@ -177,7 +177,6 @@ jobs:
- name: Zip release asset - name: Zip release asset
run: | run: |
repo="${{ github.event.repository.name }}" repo="${{ github.event.repository.name }}"
chmod +x "dist/$repo/launch.sh" "dist/$repo/selftest.sh"
(cd dist && zip -r "../${repo}-${{ github.ref_name }}.zip" "$repo") (cd dist && zip -r "../${repo}-${{ github.ref_name }}.zip" "$repo")
- name: Publish to Gitea release - name: Publish to Gitea release

View File

@ -10,7 +10,7 @@ from __future__ import annotations
from typing import Iterable, Iterator from typing import Iterable, Iterator
from . import decode, device, petscii from . import decode, device
# bracketed state tag, padded so the content columns line up # bracketed state tag, padded so the content columns line up
_TAG_WIDTH = 8 _TAG_WIDTH = 8
@ -32,19 +32,12 @@ class TraceFormatter:
the symbolic annotations and state events. the symbolic annotations and state events.
""" """
def __init__(self, my_address: int = 4, raw: bool = False, debug: bool = False): def __init__(self, my_address: int = 4, raw: bool = False):
self.my_address = my_address self.my_address = my_address
self.raw = raw self.raw = raw
self.debug = debug
@property
def plain(self) -> bool:
return not self.debug and not self.raw
def format(self, rec: device.IecRecord) -> str | None: def format(self, rec: device.IecRecord) -> str | None:
"""Format one record. Returns ``None`` for records to be skipped.""" """Format one record. Returns ``None`` for records to be skipped."""
if self.plain:
return self._format_plain(rec)
if self.raw: if self.raw:
# raw mode: just the byte value of command/data records # raw mode: just the byte value of command/data records
if rec.is_command or rec.is_data: if rec.is_command or rec.is_data:
@ -67,23 +60,6 @@ class TraceFormatter:
yield line yield line
# -- per-kind formatters ------------------------------------------------- # -- per-kind formatters -------------------------------------------------
def _format_plain(self, rec: device.IecRecord) -> str | None:
if rec.is_event:
return None
if rec.is_command:
cmd = decode.decode_command(rec.value)
if cmd.mnemonic == decode.LISTEN:
return f"\n--- LISTEN {cmd.primary} ---\n"
if cmd.mnemonic == decode.UNLISTEN:
return "\n--- UNLISTEN ---\n"
return None
if rec.is_data:
if rec.value in (0x0D, 0x0A, 0x8D): # CR, LF, SHIFT-CR
return "\n"
g = petscii.to_glyph(rec.value)
return g.text if g.printable else None
return None
def _format_event(self, rec: device.IecRecord) -> str: def _format_event(self, rec: device.IecRecord) -> str:
text = decode.describe_event(rec) text = decode.describe_event(rec)
if rec.value == device.EV_IDLE: if rec.value == device.EV_IDLE:

View File

@ -33,10 +33,6 @@ def build_parser() -> argparse.ArgumentParser:
"--replay", metavar="FILE", "--replay", metavar="FILE",
help="replay a captured record file instead of opening the device", help="replay a captured record file instead of opening the device",
) )
p.add_argument(
"--debug", action="store_true",
help="print the full annotated trace instead of plain received text",
)
p.add_argument( p.add_argument(
"--raw", action="store_true", "--raw", action="store_true",
help="dump a bare hex stream instead of the annotated trace", help="dump a bare hex stream instead of the annotated trace",
@ -51,12 +47,11 @@ def build_parser() -> argparse.ArgumentParser:
def run(records: Iterable[device.IecRecord], fmt: TraceFormatter, def run(records: Iterable[device.IecRecord], fmt: TraceFormatter,
out: TextIO, logfile: TextIO | None = None) -> None: out: TextIO, logfile: TextIO | None = None) -> None:
"""Format ``records`` through ``fmt`` and write lines to ``out`` (+ logfile).""" """Format ``records`` through ``fmt`` and write lines to ``out`` (+ logfile)."""
line_end = "" if fmt.plain else "\n"
try: try:
for line in fmt.format_stream(records): for line in fmt.format_stream(records):
print(line, end=line_end, file=out, flush=True) print(line, file=out, flush=True)
if logfile is not None: if logfile is not None:
print(line, end=line_end, file=logfile, flush=True) print(line, file=logfile, flush=True)
except BrokenPipeError: except BrokenPipeError:
# downstream (e.g. `head`) closed the pipe; redirect stdout to devnull so # downstream (e.g. `head`) closed the pipe; redirect stdout to devnull so
# the interpreter's shutdown flush doesn't re-raise, then exit quietly. # the interpreter's shutdown flush doesn't re-raise, then exit quietly.
@ -66,7 +61,7 @@ def run(records: Iterable[device.IecRecord], fmt: TraceFormatter,
def main(argv: list[str] | None = None) -> int: def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv) args = build_parser().parse_args(argv)
fmt = TraceFormatter(my_address=args.address, raw=args.raw, debug=args.debug) fmt = TraceFormatter(my_address=args.address, raw=args.raw)
logfile = open(args.logfile, "a") if args.logfile else None logfile = open(args.logfile, "a") if args.logfile else None
try: try:

View File

@ -43,24 +43,6 @@ actually capture and decode C64 traffic, use **`launch.sh`**, which installs the
frontend, loads the matching module, runs `iecpoc`, and unloads on exit — see the frontend, loads the matching module, runs `iecpoc`, and unloads on exit — see the
[top-level README](../README.md#running-the-frontend-on-the-pi). [top-level README](../README.md#running-the-frontend-on-the-pi).
### Frontend output modes
`iecpoc` has three output modes, selected by flags passed after `--` to `launch.sh`
(or directly on the command line when running `iecpoc` standalone):
| Mode | Flag | Output |
|---|---|---|
| Plain text (default) | *(none)* | Received characters only; LISTEN/UNLISTEN print a divider; CR/LF become real newlines |
| Debug / annotated trace | `--debug` | Full `[CMD]` / `[DATA]` / `[ATN]` / `[IDLE]` tag-per-record trace |
| Raw hex | `--raw` | Bare uppercase hex bytes of command and data records, no annotations |
```bash
sudo ./launch.sh # plain text — what the C64 is printing
sudo ./launch.sh -- --debug # full annotated trace
sudo ./launch.sh -- --raw # bare hex stream
sudo ./launch.sh -- --logfile trace.txt # also write output to a file
```
You don't pick the file yourself: the script reads `uname -r`, then scans You don't pick the file yourself: the script reads `uname -r`, then scans
`modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic** `modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic**
matches the running kernel. Matching is by vermagic rather than filename because matches the running kernel. Matching is by vermagic rather than filename because

View File

@ -19,13 +19,11 @@
# sudo ./launch.sh [--address N] [--device PATH] [--ko FILE] [-- <iecpoc args>] # sudo ./launch.sh [--address N] [--device PATH] [--ko FILE] [-- <iecpoc args>]
# #
# Examples: # Examples:
# sudo ./launch.sh # address 4, /dev/iec0, plain text (default) # sudo ./launch.sh # address 4, /dev/iec0, annotated trace
# sudo ./launch.sh --address 8 # listen as device 8 # sudo ./launch.sh --address 8 # listen as device 8
# sudo ./launch.sh -- --debug # full annotated trace (verbose)
# sudo ./launch.sh -- --raw # forward --raw to iecpoc # sudo ./launch.sh -- --raw # forward --raw to iecpoc
# sudo ./launch.sh --ko modules/iec_listener_1-6.12.93-1+rpt1.ko # sudo ./launch.sh --ko modules/iec_listener_1-6.12.93-1+rpt1.ko
# #
# Any flag after -- is forwarded verbatim to iecpoc (--debug, --raw, --logfile FILE, …).
# Defaults: address=4 (printer), device=/dev/iec0. # Defaults: address=4 (printer), device=/dev/iec0.
set -euo pipefail set -euo pipefail

View File

@ -16,11 +16,7 @@ from . import fixtures
def _trace(records, my_address=4, raw=False): def _trace(records, my_address=4, raw=False):
return list(TraceFormatter(my_address=my_address, raw=raw, debug=True).format_stream(records)) return list(TraceFormatter(my_address=my_address, raw=raw).format_stream(records))
def _plain(records, my_address=4):
return list(TraceFormatter(my_address=my_address).format_stream(records))
# --- wire-format round trip ------------------------------------------------- # --- wire-format round trip -------------------------------------------------
@ -105,90 +101,3 @@ def test_raw_mode_is_hex_only():
assert trace[0] == "24" assert trace[0] == "24"
# data bytes present as hex # data bytes present as hex
assert "48" in trace # 'H' assert "48" in trace # 'H'
# --- plain mode -------------------------------------------------------------
def _make_record(kind, value, flags=0):
from iecpoc.device import IecRecord
return IecRecord(kind, value, flags, 0)
def test_plain_property():
assert TraceFormatter(debug=False).plain is True
assert TraceFormatter(debug=True).plain is False
assert TraceFormatter(raw=True).plain is False
def test_plain_listen_divider():
rec = _make_record(device.KIND_COMMAND, 0x24, device.FLAG_ADDRESSED) # LISTEN 4
out = _plain([rec])
assert len(out) == 1
assert "LISTEN 4" in out[0]
def test_plain_unlisten_divider():
rec = _make_record(device.KIND_COMMAND, 0x3F) # UNLISTEN
out = _plain([rec])
assert len(out) == 1
assert "UNLISTEN" in out[0]
def test_plain_data_printable():
rec = _make_record(device.KIND_DATA, 0x48, device.FLAG_ADDRESSED) # 'H'
assert _plain([rec]) == ["H"]
def test_plain_data_cr_yields_newline():
rec = _make_record(device.KIND_DATA, 0x0D, device.FLAG_ADDRESSED)
assert _plain([rec]) == ["\n"]
def test_plain_data_lf_yields_newline():
rec = _make_record(device.KIND_DATA, 0x0A, device.FLAG_ADDRESSED)
assert _plain([rec]) == ["\n"]
def test_plain_data_shift_cr_yields_newline():
rec = _make_record(device.KIND_DATA, 0x8D, device.FLAG_ADDRESSED)
assert _plain([rec]) == ["\n"]
def test_plain_events_are_silent():
recs = [
_make_record(device.KIND_EVENT, device.EV_IDLE),
_make_record(device.KIND_EVENT, device.EV_ATN_ASSERTED),
_make_record(device.KIND_EVENT, device.EV_ATN_RELEASED),
]
assert _plain(recs) == []
def test_plain_non_listen_commands_silent():
recs = [
_make_record(device.KIND_COMMAND, 0xF0), # OPEN SA=0
_make_record(device.KIND_COMMAND, 0xE0), # CLOSE SA=0
_make_record(device.KIND_COMMAND, 0x60), # SECOND/DATA SA=0
_make_record(device.KIND_COMMAND, 0x48), # TALK 8
_make_record(device.KIND_COMMAND, 0x5F), # UNTALK
]
assert _plain(recs) == []
def test_plain_graphic_bytes_silent():
rec = _make_record(device.KIND_DATA, 0x70, device.FLAG_ADDRESSED) # graphic range
assert _plain([rec]) == []
def test_plain_control_bytes_silent():
rec = _make_record(device.KIND_DATA, 0x12, device.FLAG_ADDRESSED) # RVS-ON
assert _plain([rec]) == []
def test_plain_full_session():
out = _plain(fixtures.build_session())
joined = "".join(out)
assert "HELLO WORLD" in joined
assert "LINE TWO" in joined
assert "LISTEN 4" in joined
assert "UNLISTEN" in joined
# each PRINT# record ends with CR → two newlines from data, plus divider newlines
assert joined.count("\n") >= 2