Compare commits
No commits in common. "63c3167d9f552cb30ee1d480e2d0649c5c2e62ec" and "3cf13f14f2d51ef96006b8cf8e351ea14b96cb57" have entirely different histories.
63c3167d9f
...
3cf13f14f2
@ -177,7 +177,6 @@ jobs:
|
||||
- name: Zip release asset
|
||||
run: |
|
||||
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")
|
||||
|
||||
- name: Publish to Gitea release
|
||||
|
||||
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Iterable, Iterator
|
||||
|
||||
from . import decode, device, petscii
|
||||
from . import decode, device
|
||||
|
||||
# bracketed state tag, padded so the content columns line up
|
||||
_TAG_WIDTH = 8
|
||||
@ -32,19 +32,12 @@ class TraceFormatter:
|
||||
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.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:
|
||||
"""Format one record. Returns ``None`` for records to be skipped."""
|
||||
if self.plain:
|
||||
return self._format_plain(rec)
|
||||
if self.raw:
|
||||
# raw mode: just the byte value of command/data records
|
||||
if rec.is_command or rec.is_data:
|
||||
@ -67,23 +60,6 @@ class TraceFormatter:
|
||||
yield line
|
||||
|
||||
# -- 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:
|
||||
text = decode.describe_event(rec)
|
||||
if rec.value == device.EV_IDLE:
|
||||
|
||||
@ -33,10 +33,6 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
"--replay", metavar="FILE",
|
||||
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(
|
||||
"--raw", action="store_true",
|
||||
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,
|
||||
out: TextIO, logfile: TextIO | None = None) -> None:
|
||||
"""Format ``records`` through ``fmt`` and write lines to ``out`` (+ logfile)."""
|
||||
line_end = "" if fmt.plain else "\n"
|
||||
try:
|
||||
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:
|
||||
print(line, end=line_end, file=logfile, flush=True)
|
||||
print(line, file=logfile, flush=True)
|
||||
except BrokenPipeError:
|
||||
# downstream (e.g. `head`) closed the pipe; redirect stdout to devnull so
|
||||
# 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:
|
||||
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
|
||||
try:
|
||||
|
||||
@ -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
|
||||
[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
|
||||
`modules/` (and a few fallback locations) and selects the `.ko` whose **vermagic**
|
||||
matches the running kernel. Matching is by vermagic rather than filename because
|
||||
|
||||
@ -19,13 +19,11 @@
|
||||
# sudo ./launch.sh [--address N] [--device PATH] [--ko FILE] [-- <iecpoc args>]
|
||||
#
|
||||
# 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 -- --debug # full annotated trace (verbose)
|
||||
# sudo ./launch.sh -- --raw # forward --raw to iecpoc
|
||||
# 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.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@ -16,11 +16,7 @@ from . import fixtures
|
||||
|
||||
|
||||
def _trace(records, my_address=4, raw=False):
|
||||
return list(TraceFormatter(my_address=my_address, raw=raw, debug=True).format_stream(records))
|
||||
|
||||
|
||||
def _plain(records, my_address=4):
|
||||
return list(TraceFormatter(my_address=my_address).format_stream(records))
|
||||
return list(TraceFormatter(my_address=my_address, raw=raw).format_stream(records))
|
||||
|
||||
|
||||
# --- wire-format round trip -------------------------------------------------
|
||||
@ -105,90 +101,3 @@ def test_raw_mode_is_hex_only():
|
||||
assert trace[0] == "24"
|
||||
# data bytes present as hex
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user