From e4201a7812d955d2390df75e578a140b94f29f9e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 20:49:20 +0000 Subject: [PATCH] feat: every profile winds the Tx timer out to its 250s maximum Networks > Basic Settings > Basic Network Settings holds the Tx Timer Duration in the network table (item 0x15): an 8-bit count of seconds at bit 52 of the first network's entry, plus a flag at bit 7 that the CPS sets whenever a duration is programmed and clears when it is zero. Three CPS saves of one TM8100 codeplug (DBVer 0095) - the duration at its 60 s default, wound down, and off - differ in that record and in no other byte of the file, which is what pins both fields. Writing the duration maintains the flag, so a write here reproduces the CPS's own save byte for byte: setting the field on the default file gives each of the other two captures exactly, and restores the default again. 0 means no time-out at all and 250 is the CPS's maximum, both enforced. Only the first network is mapped. A default codeplug carries exactly one, and nothing in the captures pins where the next entry begins, so a codeplug with several networks has only its first one read and written. It goes in at audio-and-ptt, which the other three profiles all pass through, the same way the F1 key does: a default codeplug drops the carrier after 60 seconds, which is short enough to cut a long packet transmission in half. Like that key it is an operator setting rather than modem wiring, so the profiles now touch item 0x15 as well - noted in the changelog and both READMEs. One capture is named for 20 seconds but holds 30, which is why the fixture and the docs call it the 30 s save: the default file reads exactly its own label's 60, and 0..250 is exactly a byte, so seconds per count is the reading both other captures agree with. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LNh7AjusLzuqgPg8FH39SU --- CHANGELOG.md | 5 ++ README.md | 6 ++ src/M0LTE.Tait.Codeplug.Cli/Program.cs | 3 +- src/M0LTE.Tait.Codeplug.Cli/Tui.cs | 2 +- src/M0LTE.Tait.Codeplug/FieldConsole.cs | 8 ++ .../Fields/CodeplugFields.cs | 65 ++++++++++++++- src/M0LTE.Tait.Codeplug/README.md | 36 ++++++-- tests/M0LTE.Tait.Codeplug.Tests/Fixtures.cs | 32 ++++++-- .../PdnProfileTests.cs | 82 +++++++++++++++++++ 9 files changed, 222 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab5fbee..37f3848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ What changed in each release. The section for a version is lifted into that vers Newest first. Add a section before tagging. +## 0.15.0 - 2026-09-20 + +- **The Tx timer is readable and settable**: `txtimer`, the CPS's **Tx Timer Duration** on Networks > Basic Settings > Basic Network Settings - how long the radio may transmit before the timer drops the carrier. It reads and writes in seconds; **`0` means no time-out at all**, and `250` is the CPS's maximum. `get radio.m8p txtimer` reads it, `set radio.m8p txtimer 250` sets it, and `patch txtimer 250` does it live. It is two fields of the network table (0x15) that the CPS keeps in step - an 8-bit duration at bit 52 of the first network's entry, and a flag at bit 7 that it clears when the duration is zero - so writing one maintains the other. Three CPS saves of one codeplug (the duration at its 60 s default, at 30 s, and at 0) differ in that record and in **no other byte of the file**, and setting the field on the first reproduces each of the other two byte for byte, and restores the first. Only the first network is mapped: a default codeplug carries exactly one, and no capture pins where the next entry begins. +- **Every profile now winds the Tx timer out to 250 seconds**, the maximum. A default codeplug drops the carrier after 60, which is short enough to cut a long packet transmission in half - so `audio-and-ptt` sets it, and `pdn-basic`, `pdn-extra` and `pdn-internal` all carry it the same way they carry the `F1` key. Like that key it is an operator setting rather than part of the modem wiring, so note it if you apply `audio-and-ptt` for the wiring alone: the profiles now touch the network table (0x15) as well. Still no RF or channel config touched. + ## 0.14.1 - 2026-09-20 A packaging fix. The tool itself is unchanged. diff --git a/README.md b/README.md index 6e545ab..3a06ca1 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,12 @@ are settable individually: `set radio.m8p key.f1 SquelchOverride` (or `Unassigne grep key.` lists all four. Like the digital I/O lines, a key is written as a whole pattern validated against a CPS save, and a function this tool has not captured reads as `Other` and is left untouched. +And it winds the **Tx timer out to 250 seconds**, the CPS's maximum. A default codeplug drops the +carrier after 60, which is short enough to cut a long packet transmission in half; every profile +carries the longer timer for the same reason it carries the key. It is settable on its own in seconds: +`set radio.m8p txtimer 250`, and **`0` means no time-out at all**. `get radio.m8p txtimer` reads it +back. It is the first network's `Tx Timer Duration` on the CPS's Networks > Basic Settings page. + `pdn-basic` adds the CCDI command channel on top of that wiring - CCDI on, the radio in Command mode at power-up, progress messages on, the command baud at 28800, and the data port on Mic, the front-panel connector the host's serial lead plugs into - and `pdn-extra` adds the transparent FFSK modem and SDM diff --git a/src/M0LTE.Tait.Codeplug.Cli/Program.cs b/src/M0LTE.Tait.Codeplug.Cli/Program.cs index 8ef5b72..69f910e 100644 --- a/src/M0LTE.Tait.Codeplug.Cli/Program.cs +++ b/src/M0LTE.Tait.Codeplug.Cli/Program.cs @@ -382,7 +382,8 @@ static void PrintUsage() Console.WriteLine("upgrade profiles (leave RF/channels untouched; adjust the bauds for your setup):"); Console.WriteLine(" audio-and-ptt the aux-connector modem wiring: audio taps R1/split/except-on-PTT + T13,"); Console.WriteLine(" AUX_GPI1 = External PTT 1, EPTT1 transmits data from the tap in, F1 = squelch"); - Console.WriteLine(" override. Carried by all three below, so every profile sets that key."); + Console.WriteLine(" override, Tx timer 250s (the max). Carried by all three below, so every"); + Console.WriteLine(" profile sets that key and that timer."); Console.WriteLine(" pdn-basic CCDI telemetry + control (RSSI, fwd/rev power, status, PTT, DCD) out of the"); Console.WriteLine(" Mic connector (data port Mic), plus audio-and-ptt"); Console.WriteLine(" pdn-extra pdn-basic + the TNC-less internal FFSK packet modem and SDM mode signalling"); diff --git a/src/M0LTE.Tait.Codeplug.Cli/Tui.cs b/src/M0LTE.Tait.Codeplug.Cli/Tui.cs index 7de59df..431afbd 100644 --- a/src/M0LTE.Tait.Codeplug.Cli/Tui.cs +++ b/src/M0LTE.Tait.Codeplug.Cli/Tui.cs @@ -293,7 +293,7 @@ private static Window Build() Y = 5, Width = Dim.Fill(), Height = Dim.Fill(), - Text = "Applied when you\nwrite. No preset\ntouches RF or channel\nconfig. All set F1\nto squelch override.\n\n" + Text = "Applied when you\nwrite. No preset\ntouches RF or channel\nconfig. All set F1 to\nsquelch override and\nTx timer 250s.\n\n" + "audio-and-ptt: the\naux-connector modem\nwiring - audio taps\nand PTT line.\n\n" + "basic: that, plus\nCCDI control (RSSI,\npower, status, PTT,\nDCD) on the Mic\ndata port.\n\n" + "extra: adds the\nTNC-less FFSK modem\nand SDM signalling.\n\n" diff --git a/src/M0LTE.Tait.Codeplug/FieldConsole.cs b/src/M0LTE.Tait.Codeplug/FieldConsole.cs index 2aef8f2..2e37400 100644 --- a/src/M0LTE.Tait.Codeplug/FieldConsole.cs +++ b/src/M0LTE.Tait.Codeplug/FieldConsole.cs @@ -30,6 +30,12 @@ public static class FieldConsole rows.Add(("ctcsstable", string.Join(",", f.CtcssTable.Select(hz => hz.ToString("0.0", CultureInfo.InvariantCulture))))); rows.Add(("dcstable", string.Join(",", f.DcsTable))); + // Networks > Basic Settings, Basic Network Settings tab (record 0x15; only if present) + if (f.HasNetworks) + { + rows.Add(("txtimer", f.TxTimerSeconds.ToString(CultureInfo.InvariantCulture))); + } + rows.Add(("sdm", f.SdmEnabled ? "true" : "false")); rows.Add(("thsd", f.ThsdModemEnabled ? "true" : "false")); rows.Add(("transparent", f.TransparentModeEnabled ? "true" : "false")); @@ -201,6 +207,8 @@ public static void Set(CodeplugFields f, string name, string value) switch (name.ToLowerInvariant()) { + // Networks > Basic Settings tab (seconds; 0 is no time-out, 250 the CPS's maximum) + case "txtimer": f.TxTimerSeconds = int.Parse(value, CultureInfo.InvariantCulture); return; case "sdm": f.SdmEnabled = Bool(value); return; case "thsd": f.ThsdModemEnabled = Bool(value); return; case "transparent": f.TransparentModeEnabled = Bool(value); return; diff --git a/src/M0LTE.Tait.Codeplug/Fields/CodeplugFields.cs b/src/M0LTE.Tait.Codeplug/Fields/CodeplugFields.cs index b24ae9a..cff3008 100644 --- a/src/M0LTE.Tait.Codeplug/Fields/CodeplugFields.cs +++ b/src/M0LTE.Tait.Codeplug/Fields/CodeplugFields.cs @@ -418,9 +418,11 @@ public void ApplyPacketAudioDefaults() /// puts the modem's audio on air. /// /// It also programs the front-panel F1 key to Squelch Override, so the operator can open - /// the speaker and hear the channel whatever the squelch and subaudible signalling are doing. - /// That is an operator convenience rather than part of the modem wiring, but it is wanted on - /// every radio these profiles provision, and this is the one they all pass through. + /// the speaker and hear the channel whatever the squelch and subaudible signalling are doing, and + /// winds the Tx timer out to seconds, the CPS's maximum - + /// a default codeplug drops the carrier after 60, which is short enough to cut a packet + /// transmission in half. Neither is part of the modem wiring, but both are wanted on every radio + /// these profiles provision, and this is the one they all pass through. /// /// /// applies it as part of a full packet upgrade; it is exposed on its @@ -429,7 +431,8 @@ public void ApplyPacketAudioDefaults() /// record nor the RF config: it changes the audio block (0x3B), one line of the digital I/O table /// (0x37) and one entry of the PTT table (0x19) - those three reproduce, byte for byte, a CPS save /// of that configuration on an otherwise default TM8100 codeplug (DBVer 0095) - plus the three key - /// items (0x0F, 0x03, 0x18), which likewise reproduce a CPS save of that key edit (DBVer 0094). + /// items (0x0F, 0x03, 0x18), which likewise reproduce a CPS save of that key edit (DBVer 0094), + /// and the first network's entry (0x15), which reproduces a CPS save of the timer edit. /// /// public void ApplyAudioAndPtt() @@ -438,6 +441,7 @@ public void ApplyAudioAndPtt() SetDigitalIoRole(DigitalIoLine.AuxGpi1, DigitalIoRole.ExternalPtt1Input); SetPttTransmission(PttSource.ExternalPtt1, PttTransmission.DataFromAudioTapIn); SetFunctionKeyRole(FunctionKey.F1, FunctionKeyRole.SquelchOverride); + TxTimerSeconds = MaxTxTimerSeconds; } /// @@ -1732,6 +1736,59 @@ public void SetCustomerNetworkByte(int network, int index, byte value) Image.Require(0x4D, (byte)(network - 1)).Data[index - 1] = value; } + // ---- Networks > Basic Settings, Basic Network Settings tab (record 0x15/0) ---------- + // + // Item 0x15 is the network table: one entry per network, packed LSB-first, the first entry + // starting at bit 0 of record 0x15/0. Only that first network is mapped - a factory-default + // codeplug carries exactly one, and no capture pins where the next one starts. + + private const byte NetworkSection = 0x15; + + // The transmit time-out timer is two fields the CPS keeps in step: the duration in seconds, and a + // flag set whenever a duration is programmed. Three CPS saves of the same codeplug - the duration + // at its 60 s default, at 30 s, and at 0 - differ in these bits and in no other byte of the file. + private const int NetworkTxTimerEnabled = 7; // 1 bit + private const int NetworkTxTimerDuration = 52; // 8 bits, seconds + + /// The longest transmission can be set to, in seconds: the + /// CPS's own maximum for the field. + public const int MaxTxTimerSeconds = 250; + + /// True when the codeplug carries the network table (record 0x15/0). + public bool HasNetworks => HasRecord(NetworkSection, 0); + + private byte[] Network => Image.Require(NetworkSection, 0).Data; + + /// + /// How long the radio may transmit before the Tx timer drops the carrier, in seconds (the CPS + /// "Tx Timer Duration", Networks > Basic Settings > Basic Network Settings), for the first + /// network. 0 means no time-out at all; is the CPS's + /// maximum, and a factory-default codeplug carries 60. + /// + /// + /// The duration is a byte at bit 52 of the network entry. The CPS also clears a flag bit when the + /// duration is zero and sets it whenever one is programmed; writing here keeps the two in step, so + /// the result is byte-identical to the CPS's own save of the same edit. + /// + /// The value is negative or above + /// . + public int TxTimerSeconds + { + get => (int)GetBits(Network, NetworkTxTimerDuration, 8); + set + { + if (value is < 0 or > MaxTxTimerSeconds) + { + throw new ArgumentOutOfRangeException( + nameof(value), value, $"0..{MaxTxTimerSeconds} s (0 is no time-out)"); + } + + byte[] p = Network; + SetBits(p, NetworkTxTimerDuration, 8, value); + SetBits(p, NetworkTxTimerEnabled, 1, value == 0 ? 0 : 1); + } + } + // ---- Audio tap block (record 0x3B/0) ------------------------------------------------ // ---- Programmable I/O: digital lines (record 0x37, item 55) ---------------------------------- diff --git a/src/M0LTE.Tait.Codeplug/README.md b/src/M0LTE.Tait.Codeplug/README.md index 7ed5e00..3aea2cc 100644 --- a/src/M0LTE.Tait.Codeplug/README.md +++ b/src/M0LTE.Tait.Codeplug/README.md @@ -59,9 +59,9 @@ CodeplugImage live = programmer.ReadImage(); Composable patches that *upgrade a radio to the Packet.NET feature set* without touching its RF config (channels, frequencies, power), so they layer safely onto a radio already provisioned for its environment. They change the data record (0x09), the audio block (0x3B), the digital I/O lines they -program (0x37), the PTT table (0x19) and the three Key Settings items (0x0F, 0x03, 0x18). For a radio -arriving from a foreign application, prefer a clean flash of a full codeplug first, then apply a -profile. +program (0x37), the PTT table (0x19), the three Key Settings items (0x0F, 0x03, 0x18) and the network +table (0x15). For a radio arriving from a foreign application, prefer a clean flash of a full codeplug +first, then apply a profile. They nest: `audio-and-ptt` is the aux-connector modem wiring, `pdn-basic` is that plus the CCDI command channel, `pdn-extra` is that plus the FFSK modem, and `pdn-internal` is `pdn-extra` moved onto @@ -76,8 +76,10 @@ settings are already right, or one being set up for an external modem without th rather than Voice from the aux mic - without that last one the line keys the radio but puts the wrong audio on air. Those three records - 0x19, 0x37 and 0x3B - come out byte-identical to a CPS save of the same configuration on a default TM8100 codeplug. It never touches the data record. It also programs - the front-panel **F1 key to Squelch Override** (see [Function keys](#function-keys)) - not part of the - wiring, but wanted on every radio these profiles provision, and this is the one they all pass through. + the front-panel **F1 key to Squelch Override** (see [Function keys](#function-keys)) and winds the + **Tx timer out to its 250-second maximum** (see [Tx timer](#tx-timer)) - neither is part of the + wiring, but both are wanted on every radio these profiles provision, and this is the one they all + pass through. - **`pdn-basic`** is `audio-and-ptt` plus the CCDI command channel that carries `Packet.Radio.Tait`'s telemetry and control: averaged/instantaneous RSSI, forward/reverse power, PA temperature, status/identity, transmitter keying, and the PROGRESS stream for carrier-sense (DCD) and external-PTT @@ -159,6 +161,30 @@ Squelch Override onto another key. `GetFunctionKeyRole` / `SetFunctionKeyRole` take a `FunctionKey`; the console names them `key.f1` .. `key.f4`. `get radio.m8p | grep key.` lists all four; `set radio.m8p key.f1 SquelchOverride` sets one. +## Tx timer + +`TxTimerSeconds` is the CPS's **Tx Timer Duration**, Networks > Basic Settings > Basic Network Settings +tab: how long the radio may transmit before the timer drops the carrier. It reads and writes in +**seconds**, `0` means **no time-out at all**, and `MaxTxTimerSeconds` (250) is the CPS's maximum. A +factory-default codeplug carries 60. The console names it `txtimer`. + +Item 0x15 is the network table - one entry per network, packed LSB-first, the first starting at bit 0 +of record 0x15/0. The timer is two fields of that entry, which the CPS keeps in step: + +| field | where | what it holds | +|---|---|---| +| duration | bits 52..59 | the time-out in seconds, `0`..`250` | +| flag | bit 7 | set whenever a duration is programmed, cleared when it is zero | + +Writing the duration maintains the flag, so the result is byte-identical to the CPS's own save of the +same edit. Three CPS saves of one TM8100 codeplug (DBVer 0095) - the duration at its 60 s default, at +30 s, and at 0 - differ in this record and in **no other byte of the file**, which is what pins both +fields; `set txtimer` on the first reproduces each of the other two exactly, and restores the first. + +Only the **first network** is mapped. A default codeplug carries exactly one, and no capture pins where +the next network's entry starts, so a codeplug with several networks has only its first one read and +written here. + ## Status and safety The read + write path is hardware-validated against a real TM8100 (a same-image write round-tripped diff --git a/tests/M0LTE.Tait.Codeplug.Tests/Fixtures.cs b/tests/M0LTE.Tait.Codeplug.Tests/Fixtures.cs index 7a48263..dfd4a5e 100644 --- a/tests/M0LTE.Tait.Codeplug.Tests/Fixtures.cs +++ b/tests/M0LTE.Tait.Codeplug.Tests/Fixtures.cs @@ -65,16 +65,35 @@ internal static class Fixtures /// codeplug carries no 0x18 record at all and an item count of zero. public const string SquelchOverrideKeyList = "100000"; + // The network table (0x15) as three CPS saves of one TM8100 codeplug (DBVer 0095) hold it - the + // Tx Timer Duration at its 60 s default, wound down to 30 s, and off. Those three files differ in + // this record and in no other byte, which is what makes them a byte-exact target for the timer. + + /// Network table (0x15): one network, Tx timer at the 60 s factory default. + public const string DefaultNetworkTable = + "800017000008C403E0018218A478A00F0C0080001888130008"; + + /// The same table with the Tx timer wound down to 30 seconds. + public const string TxTimer30sNetworkTable = + "800017000008E401E0018218A478A00F0C0080001888130008"; + + /// The same table with the Tx timer at zero - no time-out. The CPS clears its flag bit + /// (byte 0) as well as the duration. + public const string TxTimerOffNetworkTable = + "0000170000080400E0018218A478A00F0C0080001888130008"; + // Item index entries (7 bytes each) for the items the profiles touch: the function table - // (0x03, 14 bits x 65), the key table (0x0F, 20 bits x 4), the programmed-key list (0x18, 22 bits - // x 0), the audio block (0x3B, 95 bits x 4) and the digital line table (0x37, 132 bits x 15), as a - // real readout has them. + // (0x03, 14 bits x 65), the key table (0x0F, 20 bits x 4), the network table (0x15, one network), + // the programmed-key list (0x18, 22 bits x 0), the audio block (0x3B, 95 bits x 4) and the digital + // line table (0x37, 132 bits x 15), as a real readout has them. private const string ItemIndex = - "030E0041000200" + "0F140004000100" + "18160000000300" + "3B5F0004000900" + "3784000F000600"; + "030E0041000200" + "0F140004000100" + "15140101001200" + + "18160000000300" + "3B5F0004000900" + "3784000F000600"; /// A codeplug carrying every block the PDN profiles write: the data/signalling record, - /// the item index, the PTT table, the audio block, the digital I/O line table, the function table - /// and the front-panel key table. Every block but the data record is the real factory-default one. + /// the item index, the PTT table, the audio block, the digital I/O line table, the function table, + /// the front-panel key table and the network table. Every block but the data record is the real + /// factory-default one. public static CodeplugFields Open(string digitalIoTableHex) { var image = new CodeplugImage( @@ -83,6 +102,7 @@ [new KeyValuePair("DBVer", "0095")], new CodeplugRecord(0x01, 0, Convert.FromHexString(ItemIndex)), new CodeplugRecord(0x09, 0, new byte[37]), new CodeplugRecord(0x0F, 0, Convert.FromHexString(DefaultFunctionKeyTable)), + new CodeplugRecord(0x15, 0, Convert.FromHexString(DefaultNetworkTable)), new CodeplugRecord(0x19, 0, Convert.FromHexString(DefaultPttTable)), new CodeplugRecord(0x3B, 0, Convert.FromHexString(DefaultAudioBlock)), ]); diff --git a/tests/M0LTE.Tait.Codeplug.Tests/PdnProfileTests.cs b/tests/M0LTE.Tait.Codeplug.Tests/PdnProfileTests.cs index 1ecfebd..034f137 100644 --- a/tests/M0LTE.Tait.Codeplug.Tests/PdnProfileTests.cs +++ b/tests/M0LTE.Tait.Codeplug.Tests/PdnProfileTests.cs @@ -363,6 +363,88 @@ public void The_console_exposes_the_keys_by_their_front_panel_names() FieldConsole.Get(f, "key.f4").Should().Be("Unassigned"); } + /// + /// The timer's counterpart to the wiring and key tests: winding the Tx Timer Duration down to + /// 30 seconds, and off, on the factory-default network table must produce the CPS's own bytes for + /// each. The three codeplugs those constants come from differ in this record and nothing else. + /// + [Fact] + public void The_tx_timer_reproduces_the_cps_save_byte_for_byte() + { + CodeplugFields f = Fixtures.Open(Fixtures.DefaultDigitalIoTable); + f.TxTimerSeconds.Should().Be(60); // the factory default the fixture carries + + f.TxTimerSeconds = 30; + + Convert.ToHexString(f.Image.Require(0x15, 0).Data).Should().Be(Fixtures.TxTimer30sNetworkTable); + + // zero is "no time-out", and the CPS clears its flag bit with the duration + f.TxTimerSeconds = 0; + Convert.ToHexString(f.Image.Require(0x15, 0).Data).Should().Be(Fixtures.TxTimerOffNetworkTable); + f.TxTimerSeconds.Should().Be(0); + + // and back to the default, byte for byte - the flag comes back with it + f.TxTimerSeconds = 60; + Convert.ToHexString(f.Image.Require(0x15, 0).Data).Should().Be(Fixtures.DefaultNetworkTable); + } + + [Fact] + public void The_tx_timer_holds_the_cps_range() + { + CodeplugFields f = Fixtures.Open(Fixtures.DefaultDigitalIoTable); + + CodeplugFields.MaxTxTimerSeconds.Should().Be(250); + f.TxTimerSeconds = CodeplugFields.MaxTxTimerSeconds; + f.TxTimerSeconds.Should().Be(250); + + ((Action)(() => f.TxTimerSeconds = 251)).Should().Throw(); + ((Action)(() => f.TxTimerSeconds = -1)).Should().Throw(); + + // a rejected write leaves the record exactly as it was + f.TxTimerSeconds.Should().Be(250); + } + + [Fact] + public void Every_profile_winds_the_tx_timer_out_to_the_maximum() + { + Action[] profiles = + [ + f => f.ApplyAudioAndPtt(), + f => f.ApplyPdnBasic(), + f => f.ApplyPdnExtra(), + f => f.ApplyPdnInternal(), + ]; + + foreach (Action profile in profiles) + { + CodeplugFields f = Fixtures.Open(Fixtures.DefaultDigitalIoTable); + f.TxTimerSeconds.Should().Be(60); + + profile(f); + + f.TxTimerSeconds.Should().Be(CodeplugFields.MaxTxTimerSeconds); + + // the rest of the network entry is the default one, untouched + byte[] network = f.Image.Require(0x15, 0).Data; + byte[] @default = Convert.FromHexString(Fixtures.DefaultNetworkTable); + network[1..6].Should().Equal(@default[1..6]); + network[8..].Should().Equal(@default[8..]); + } + } + + [Fact] + public void The_console_exposes_the_tx_timer_in_seconds() + { + CodeplugFields f = Fixtures.Open(Fixtures.DefaultDigitalIoTable); + + FieldConsole.Get(f, "txtimer").Should().Be("60"); + FieldConsole.Set(f, "txtimer", "250"); + f.TxTimerSeconds.Should().Be(250); + FieldConsole.Get(f, "txtimer").Should().Be("250"); + FieldConsole.Set(f, "txtimer", "0"); + FieldConsole.Get(f, "txtimer").Should().Be("0"); + } + private static void AudioAndPttAreWiredForTheAuxConnector(CodeplugFields f) { Convert.ToHexString(f.Image.Require(0x3B, 0).Data).Should().Be(PacketAudioBlock);