Add PTT, which brings the transmit level bar with it

PTT was the one input the model never had, so the radio could not be keyed and the
mic level bar was unreachable. It is not a matrix key -- GPIO_IsPttPressed reads
PB10 directly (driver/gpio.h:31, active low) -- so it gets its own GPIO line rather
than a column/row intersection.

With it the transmit screen is complete: TX annunciator, a running timer, and a
level bar at roughly 80% of scale, fed from REG_64 via BK4819_GetVoiceAmplitudeOut.
app/app.c:1700 draws that only while gCurrentFunction == FUNCTION_TRANSMIT with
gSetting_mic_bar set; the setting is Data[7] bit 4 at flash 0xA0A8, and blank flash
reads 0xFF, so it is already on.

Exposed as a boolean on the keypad device and as POST /api/ptt with an explicit
held flag, plus a button in the browser UI. Held rather than tapped, because
transmitting is a state the operator stays in and a fixed duration would be wrong
for it.

Releasing is treated as the important half:
  - pointerleave and pointercancel release, so dragging off the button cannot leave
    the radio keyed
  - pagehide releases, so closing the tab cannot either
  - /api/release-all clears PTT too, since it is outside the matrix and an empty
    press does not touch it
  - non-boolean bodies are rejected, so {"held": "false"} cannot key the transmitter
    by truthiness

Two things the test suite caught, both real:

The browser wired '.key' handlers over every styled button, and the PTT button
carries no data-key, so it would have sent the key "undefined". Narrowed to
'.key[data-key]'.

StubClient.presses() collected every qom-set regardless of property, so PTT's
booleans landed among the key names and presses()[-1] reported False after a
release-all. Now filtered by property, with a matching ptts() accessor.

tools/test_ptt.py covers the path end to end and asserts the release as well as the
press: a PTT that stuck would leave every later test running against a transmitting
radio.
This commit is contained in:
mckero committed 2026-08-29 05:16:42 +01:00
1 parent 39195b8028
commit 88994b1bd0
4 files changed
+371 -4

No files matched your search

+46
View File
@@ -433,6 +433,14 @@ struct UVK5KeypadState {
* emitted. See AGENTS.md.
*/
qemu_irq volatile row_out[KEYPAD_ROWS];
/*
* PTT, which is not part of the matrix: GPIO_IsPttPressed reads its own pin
* (PB10, active low), so it needs its own line. volatile for the same reason as
* row_out -- the board fills this in after init, invisibly to the compiler.
*/
qemu_irq volatile ptt_out;
bool ptt;
};
/*
@@ -494,6 +502,9 @@ static void keypad_reset(DeviceState *dev)
{
UVK5KeypadState *s = UVK5_KEYPAD(dev);
s->ptt = false;
qemu_set_irq(s->ptt_out, 1); /* released: idle high */
memset(s->pressed, 0, sizeof(s->pressed));
for (int c = 0; c < KEYPAD_COLS; c++) {
s->col_high[c] = true;
@@ -517,6 +528,12 @@ static void keypad_init(Object *obj)
* keeps -Wdiscarded-qualifiers quiet.
*/
qdev_init_gpio_out_named(dev, (qemu_irq *)s->row_out, "row", KEYPAD_ROWS);
/*
* PTT is not part of the matrix. GPIO_IsPttPressed reads its own pin, so it gets
* its own line rather than a column/row intersection.
*/
qdev_init_gpio_out_named(dev, (qemu_irq *)&s->ptt_out, "ptt", 1);
}
/*
@@ -584,6 +601,20 @@ static char *keypad_get_press(Object *obj, Error **errp)
return g_strdup("");
}
static bool keypad_get_ptt(Object *obj, Error **errp)
{
return UVK5_KEYPAD(obj)->ptt;
}
static void keypad_set_ptt(Object *obj, bool value, Error **errp)
{
UVK5KeypadState *s = UVK5_KEYPAD(obj);
s->ptt = value;
/* Active low: pressed pulls the pin down. */
qemu_set_irq(s->ptt_out, value ? 0 : 1);
}
static void keypad_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@@ -596,6 +627,11 @@ static void keypad_class_init(ObjectClass *klass, void *data)
object_class_property_set_description(klass, "press",
"hold the named key (MENU, UP, DOWN, EXIT, F, STAR, 0-9, SIDE1, SIDE2); "
"empty string releases");
object_class_property_add_bool(klass, "ptt",
keypad_get_ptt, keypad_set_ptt);
object_class_property_set_description(klass, "ptt",
"hold the push-to-talk key, which puts the radio into transmit");
}
/* ---------------------------------------------------- BK4819 transceiver */
@@ -2562,6 +2598,16 @@ static void uvk5_machine_init(MachineState *machine)
KEYPAD_ROW_PIN(r)));
}
/*
* PTT on PB10, active low. Not a matrix key: GPIO_IsPttPressed reads the pin
* directly, so it is wired straight to the port.
*/
qdev_connect_gpio_out_named(DEVICE(&s->keypad), "ptt", 0,
qdev_get_gpio_in_named(DEVICE(&s->soc.gpio[1]),
"pin-in", 10));
/* Released, now that the line exists to carry it. */
qemu_set_irq(qdev_get_gpio_in_named(DEVICE(&s->soc.gpio[1]), "pin-in", 10), 1);
/*
* Drive the initial row levels now that the lines exist. The device reset
* ran before wiring, so its qemu_set_irq calls went nowhere; without this