diff --git a/AGENTS.md b/AGENTS.md index a45d6e5..5b6242f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -459,6 +459,29 @@ Two constraints are not negotiable, both from untimed spin loops in the firmware measuring. Not hypothetical: the first test run decoded 48 registers correctly and still reported RSSI as 0 for precisely this reason. +### PTT, and the transmit level bar + +PTT is not a matrix key. `GPIO_IsPttPressed` reads PB10 directly +(`driver/gpio.h:31`, active low), so the model gives it its own GPIO line rather than a +column/row intersection, exposed as a boolean `ptt` property on the keypad device. + +That is what makes the transmit level bar reachable. `app/app.c:1700` draws it only +while `gCurrentFunction == FUNCTION_TRANSMIT` and `gSetting_mic_bar` is set — the +latter is `Data[7]` bit 4 at flash `0xA0A8` (`settings.c:423`), and blank flash reads +`0xFF`, so it is already on. The level itself comes from `REG_64` via +`BK4819_GetVoiceAmplitudeOut`. + +**Treat the release as the important half.** A stuck PTT leaves the emulated radio +keyed, and every later test then runs against a transmitting radio. The web UI releases +on `pointerleave`, `pointercancel` and `pagehide`; `/api/release-all` clears PTT +explicitly, because an empty `press` does not touch it; and the endpoint rejects +non-boolean bodies so `{"held": "false"}` cannot key the transmitter by truthiness. +`tools/test_ptt.py` asserts the release, not just the press. + +One trap worth knowing if you add another non-key button: the browser wired handlers +over `.key`, which matched the PTT button as well, and it has no `data-key` — so it +would have sent the key `"undefined"`. Use `.key[data-key]`. + ### Reads were shifted one bit, and it hid everything else Fixed in `ad88ee1`, but worth reading because of how long it stayed invisible. diff --git a/README.md b/README.md index f476d48..94d0ccd 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ has no public datasheet, so its driver is the only specification available. | Serial input, CPS programming protocol | works, `-serial` any chardev | | BK4819 register interface | works, RSSI and status readable | | S-meter | works via monitor (SIDE1); reads -53 dBm, S9+40 | +| PTT and transmit | works; TX annunciator, timer, and mic level bar | | Timing accuracy | deliberately wrong, see [Timing](#timing) | | Analogue RF behaviour | **not modelled and never will be**, see [AGENTS.md](AGENTS.md#the-bk4819-and-where-modelling-it-stops) | @@ -83,6 +84,7 @@ keypresses silently stop working. Run the test after touching that code; test_bk4819.py BK4819 register interface, RSSI not stuck at zero test_bk4819_readback.sh register reads come back bit-aligned test_smeter.py the S-meter reads a signal when monitoring + test_ptt.py PTT keys the radio and releases cleanly lib_kill_emulator.sh cleanup that only ever kills emulators webui.py web remote control: live LCD plus clickable keypad dn42_firewall.sh restrict the web UI port to DN42 sources @@ -129,6 +131,7 @@ Then check the build actually works, which takes about a minute: python3 tools/test_bk4819.py bash tools/test_bk4819_readback.sh python3 tools/test_smeter.py + python3 tools/test_ptt.py This matters more than it looks. The keypad can break silently under -O2 without any compiler warning -- see the `volatile` note in [Status](#status) -- so a clean @@ -250,8 +253,17 @@ watching the counters rather than by assuming: The rules do not survive a reboot. Re-run `apply`, or persist them with `iptables-persistent`. -There is no PTT button: the keypad model has no PTT line, so the `press` property -rejects the name. Unknown keys are rejected with 400 rather than forwarded. +PTT is separate from the keypad grid, because the firmware reads its own pin (PB10) +rather than scanning it as a matrix key. It has its own button in the UI and its own +endpoint, and it is held rather than tapped: + + curl -X POST -H 'Content-Type: application/json' \ + -d '{"held": true}' http://127.0.0.1:8080/api/ptt + +Anything that ends a session releases it — dragging off the button, closing the tab, +or `POST /api/release-all` — so a client going away cannot leave the radio keyed. +The `press` property still rejects "PTT" as a key name; unknown keys get a 400 rather +than being forwarded. ## How the machine is put together diff --git a/tools/test_uvk5_keys.py b/tools/test_uvk5_keys.py index 5d629ec..50f8807 100644 --- a/tools/test_uvk5_keys.py +++ b/tools/test_uvk5_keys.py @@ -31,8 +31,9 @@ class TestKeys(unittest.TestCase): names = set(re.findall(r'"([^"]+)"', block.group(1))) self.assertEqual(names, set(KEYS)) - def test_ptt_is_not_offered(self): - # The keypad model has no PTT line; qom-set would error on it. + def test_ptt_is_not_offered_as_a_key(self): + # PTT works, but not through the key table: the firmware reads its own pin, so + # it is a separate boolean property. Setting "press" to PTT would error. self.assertFalse(is_valid("PTT")) def test_normalise_is_case_insensitive_and_strips(self): diff --git a/tools/uvk5_keys.py b/tools/uvk5_keys.py index 7662803..a6b49b6 100644 --- a/tools/uvk5_keys.py +++ b/tools/uvk5_keys.py @@ -4,9 +4,10 @@ Mirrors keypad_key_names in qemu/py32f071.c. test_uvk5_keys.py parses that array out of the model source and fails if the two drift apart. -PTT is absent on purpose: the keypad model has no PTT line -- it is wired -separately on GPIOC -- so the "press" property rejects the name. Offering a PTT -button would produce a QMP error rather than a transmission. +PTT is absent on purpose, and still is now that the model supports it: the firmware +reads PB10 directly rather than scanning PTT as a matrix key, so it is exposed as its +own boolean property and its own endpoint (POST /api/ptt) instead of a name here. The +"press" property rejects "PTT". """ KEYS = ( diff --git a/tools/webui.py b/tools/webui.py index ba79141..dd60939 100644 --- a/tools/webui.py +++ b/tools/webui.py @@ -471,8 +471,8 @@ def render_index(scale: int) -> str:

How long you hold a key is measured here and sent as one number, so the firmware sees exactly the press you made. Hold past 400 ms for a long press, which the firmware treats as a separate event and which repeats. - Arrows move, Enter is MENU, Esc is EXIT, digits map straight through. No PTT - button -- the keypad model has no PTT line.

+ Arrows move, Enter is MENU, Esc is EXIT, digits map straight through. PTT is held + rather than measured, and releases if you drag off it or close the tab.