FLEX TX: pager transmitter with long address support (1600/2FSK) (#3132)

External app for encoding and transmitting FLEX pager frames at
1600bps/2FSK. Supports short (1-1,933,312) and long (2,101,249-
4,297,068,542) addresses across all encoding sets (1-2, 1-3/1-4, 2-3).

Message types:
- Alphanumeric (vector type 5): 7-bit chars, signature, K checksum
- Standard Numeric (vector type 3): 4-bit BCD, K checksum
- Short/tone-only (vector type 2): space-filled BCD in vector word
- Short numeric (vector type 2): BCD digits in vector word (3/8 digits)

Frame structure:
- FIW (Frame Information Word): cycle, frame, roaming flag
- BIW (Block Information Word) support with up to 3 extra words:
  Date, Time, SysInfo/timezone+DST, SSID1 (Simulcast System ID:
  Local ID + Coverage Zone), SSID2 (Country Code)
- BCH(31,21) encoding with bit reversal, block interleaving
- Two frames per TX: message R=1 (new) then R=0 (duplicate)
- Message number N (0-63): random seed from RTC, auto-increments
- ERS (Emergency Re-Sync) burst: configurable N x 42-cycle bursts
  with 8-cycle preamble per frame

UI:
- Capcode info line: address type, computed frame/phase (reference)
- TX status: step counter with ERS/frame progress
- Set params view: Date/Time/TZ/DST, LocID/CovZone, CountryCode,
  Roaming, message number, ERS count
- Date/time auto-populated from RTC with calendar-equivalent year
  mapping for years beyond 2025 (per ARIB STD-43A)
- Year validation with equiv-year popup for out-of-range values
- Capcode and BIW flags persisted via SettingsManager

Serial interface:
- sendflex <capcode> <type> <msglen> + payload
- Types: 0=alpha, 1=numeric, 2=short/tone, 3=short numeric
- FlexTosendMessage (ID 103) for serial-to-app communication
- App must be open to receive serial commands
This commit is contained in:
VasylSamoilov
2026-04-10 12:17:52 +03:00
committed by GitHub
parent cf44076a34
commit 4dce613172
7 changed files with 1292 additions and 0 deletions
+48
View File
@@ -1353,6 +1353,53 @@ static void cmd_sendpocsag(BaseSequentialStream* chp, int argc, char* argv[]) {
chprintf(chp, "ok\r\n");
}
static void cmd_sendflex(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: sendflex <capcode> <type> <msglen>\r\n type: 0=alpha 1=numeric 2=short/tone 3=short numeric\r\n";
if (argc < 3) {
chprintf(chp, usage);
return;
}
uint64_t capcode = strtoull(argv[0], nullptr, 10);
if (capcode < 1 || capcode > 4297068542ULL) {
chprintf(chp, "error, capcode 1-4297068542\r\n");
return;
}
int type = atoi(argv[1]);
if (type < 0 || type > 3) {
chprintf(chp, "error, type 0=alpha 1=numeric 2=short/tone 3=short numeric\r\n");
return;
}
int msglen = atoi(argv[2]);
if (msglen < 0 || msglen > 240) {
chprintf(chp, "error, msglen max 240\r\n");
return;
}
uint8_t msg[240] = {0};
if (msglen > 0) {
chprintf(chp, "send %d bytes\r\n", msglen);
int offset = 0;
do {
size_t bytes_to_read = (msglen - offset) > USB_BULK_BUFFER_SIZE ? USB_BULK_BUFFER_SIZE : (msglen - offset);
size_t bytes_read = chSequentialStreamRead(chp, &msg[offset], bytes_to_read);
if (bytes_read != bytes_to_read)
return;
offset += bytes_read;
} while (offset < msglen);
}
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
FlexTosendMessage message{capcode, (uint8_t)type, (uint8_t)msglen, msg};
EventDispatcher::send_message(message);
chprintf(chp, "ok\r\n");
}
static void cmd_asyncmsg(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: asyncmsg x, x can be enable or disable\r\n";
if (argc != 1) {
@@ -1500,6 +1547,7 @@ static const ShellCommand commands[] = {
{"pmemreset", cmd_pmemreset},
{"settingsreset", cmd_settingsreset},
{"sendpocsag", cmd_sendpocsag},
{"sendflex", cmd_sendflex},
{"asyncmsg", cmd_asyncmsg},
{"setfreq", cmd_setfreq},
{"getres", cmd_getres},