Compare commits

...
23 changed files with 125 additions and 51 deletions

No files matched your search

+3 -3
View File
@@ -11,7 +11,7 @@ Status:
| Audio path an app can sample | **Done** by EPIRB 406: RX audio on PA4, ADC channel 4 at 9.6 kHz |
| Integer demodulator, modelled on synthetic audio (`test/model_rx.py`) | **Done** (see below) |
| Flipper Zero test transmitter (`test/flipper_aprs.py`) | **Done**, files in `test/flipper/` |
| Radio app (`aprsrx_app.c`) | **v0.1 receives the Flipper frames on the radio**; v0.3 decodes Mic-E (on air: F5RAV via F1PRY-14); v0.4 decodes continuously with 3 slicers (on air: F1PRY-14 via F5KTR-3); v0.5 shows standard positions; v0.6 adds the speaker key; v0.7 adds the corrected 20x20 APRS symbol bitmaps; v0.8 saves the speaker setting; v0.9 scrolls long compact frames by 1 px |
| Radio app (`aprsrx_app.c`) | **v0.1 receives the Flipper frames on the radio**; v0.3 decodes Mic-E (on air: F5RAV via F1PRY-14); v0.4 decodes continuously with 3 slicers (on air: F1PRY-14 via F5KTR-3); v0.5 shows standard positions; v0.6 adds the speaker key; v0.7 adds the corrected 20x20 APRS symbol bitmaps; v0.8 saves the speaker setting; v0.9 scrolls long compact frames; v0.10 fixes the UP/DOWN direction |
| Bench test with the Flipper on 433.650 MHz | **Done** (2026-09-30, v0.1: `_long` 10/10 in STD; `_badfcs` not shown) |
| Real station: FT3D beacon on 144.800 MHz | **Done** (2026-09-30, v0.1: Mic-E frame `>TXUPX9` received, FCS good, = 48°50.89' N 2°16.25' E) |
| Mic-E decoding (`test/mice.py`: spec encoder vs the app's decoder) | **Done** in the model: 6 cases + the FT3D frame |
@@ -54,7 +54,7 @@ rows occupy eight pixels in normal mode (3 rows at rest) or six in compact mode
| Screen | Content |
|---|---|
| Status bar | `APRS RX` title, then a `WAIT` capsule until the first frame, the speaker icon while the speaker is on, then ▲ while rows or a newer frame are hidden above and ▼ while rows or an older frame are hidden below (x = 72-76 as in APRS TX, the speaker icon at x = 59-68; both drawn in one asset read) |
| Lines 0-3 | One frame: source call in bold, fixed on line 0, with two capsules at the right (the frames received, then with several frames kept `2/5`, frame shown / frames kept, 1 = newest; x >= 89); under it the body in the small font (18 characters a row, non-ASCII shown as `.`), 3 rows at a time, scrolled pixel by pixel with UP/DOWN (v0.7): `>DEST,DIGI*,...` (as many path entries as fit two rows), then the info field, whole. For Mic-E (most Yaesu/Kenwood beacons, v0.3): latitude `48 50.89N`, longitude `002 16.25E`, speed km/h, course and symbol, the message type (`Off Duty`, `En Route`...), then altitude and comment without the device markers; for an uncompressed position (`!` `=` `/` `@`, v0.5): latitude, longitude, then the timestamp if any, the symbol (table + code) and the comment. Compressed positions stay raw text. A new frame is shown at once. Key * switches to the compact view (below) |
| Lines 0-3 | One frame: source call in bold, fixed on line 0, with two capsules at the right (the frames received, then with several frames kept `2/5`, frame shown / frames kept, 1 = newest; x >= 89); under it the body in the small font (18 characters a row, non-ASCII shown as `.`), 3 rows at a time, scrolled pixel by pixel with UP/DOWN: `>DEST,DIGI*,...` (as many path entries as fit two rows), then the info field, whole. For Mic-E (most Yaesu/Kenwood beacons, v0.3): latitude `48 50.89N`, longitude `002 16.25E`, speed km/h, course and symbol, the message type (`Off Duty`, `En Route`...), then altitude and comment without the device markers; for an uncompressed position (`!` `=` `/` `@`, v0.5): latitude, longitude, then the timestamp if any, the symbol (table + code) and the comment. Compressed positions stay raw text. A new frame is shown at once. Key * switches to the compact view (below) |
| Lines 4-6 | Under a dotted separator (y=31), the frequency drawn as the main screen draws it (big digits up to the kHz, the last two in the small font), aligned left. A matching 20x20 bitmap from the 48-symbol Yaesu set is shown at x=108, y=33-52, on the right for decoded Mic-E and uncompressed positions; an overlay symbol (table `0`-`9` or `A`-`Z`, as `L&` or `D&`) without a bitmap of its own (`E0`, `YY`) shows the alternate-table symbol it is drawn over (`\&`), the overlay character staying readable in the body text; the area stays blank for an unknown or unavailable symbol |
| Bottom row y=49 | `-89dBm`: RSSI at the end of the frame shown (the count of frames received moved to the capsule on line 0). (v0.4-v0.5 also showed frames per slicer, `sl a/b/c`: on air the outer slicers found frames too; dropped in v0.6 to fit the speaker key) |
@@ -62,7 +62,7 @@ Keys (UV-K5 and UV-K1):
| Key | Action |
|---|---|
| UP/DOWN (held) | Scroll the body of the frame shown 1 px per 50 ms slot (UV-K1: LEFT/RIGHT, as `nav_dir`), down to its last row |
| UP/DOWN (held) | Scroll the body of the frame shown 1 px per 50 ms slot, according to `SET_NAV` (UV-K1: LEFT/RIGHT), down to its last row |
| UP/DOWN (pressed again at the first / last row) | Show the newer / older frame, from its first row |
| * | Normal / compact view, saved on exit, returns to the first row of the frame shown. The callsign stays large and bold on line 0. Body rows use the normal font (18 characters) or 3x5 font (32 characters), continuous 1 px scrolling |
| 1 | Speaker on/off, saved on exit (v0.8; off by default); FoxHunt's speaker icon in the status bar while on |
+3 -2
View File
@@ -694,8 +694,9 @@ static void handleKeys(void){
uint8_t key=A->get_key();
/* UP/DOWN, held: scroll the body 1 px per slot (20 px/s) while rows overflow.
* Pressed again once at an end: the newer (UP) or older (DOWN) frame, from its
* first row; holding the key never leaves the frame. */
int d=A->nav_dir(key);
* first row; holding the key never leaves the frame. Raw KEY_UP is UP on
* K5 and LEFT on K1; KEY_DOWN is DOWN / RIGHT. */
int d=(key==APP_KEY_DOWN)-(key==APP_KEY_UP);
unsigned top=(unsigned)(g.top+d); /* wraps past the first row */
if(top<=g.lim){
if(top!=g.top){ g.top=(uint16_t)top; g.redraw|=REDRAW_ON; }
+1 -1
View File
@@ -8,7 +8,7 @@ set -euo pipefail
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
APP_NAME="APRS RX" # <-- the only per-app line
APP_VER="0.9"
APP_VER="0.10"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
+6 -1
View File
@@ -81,7 +81,12 @@ def check_scroll():
def check_keys():
# Transcription of handleKeys(): held UP/DOWN scrolls inside the frame; a
# new press at an end changes frame.
# new press at an end changes frame. Raw UP/LEFT moves towards the start and
# raw DOWN/RIGHT towards the end, in either SET_NAV mode.
for set_nav in (False, True):
for key, raw in (('UP', -1), ('DOWN', 1)):
nav = (1 if key == 'UP' else -1) * (1 if set_nav else -1)
assert raw == (-nav if set_nav else nav)
rng = random.Random(59)
for _ in range(300):
count = rng.randrange(1, HISTORY + 1)
+2 -2
View File
@@ -7,7 +7,7 @@ which is also the test receiver.
| Step | State |
|---|---|
| Frame and bit stream model (`test/tx_model.py`) | **Done**: identical to the RX app's AX.25 reference, decoded by the RX model |
| Radio app (`aprstx_app.c`) | **v0.1 works on the radio** (2026-09-30); v0.2 edits the position; v0.3 adds the SSID and path; v0.4 adds the corrected 20x20 bitmap and the 48-symbol selector; v0.5 keeps the source fixed while the frame body scrolls (not built here) |
| Radio app (`aprstx_app.c`) | **v0.1 works on the radio** (2026-09-30); v0.2 edits the position; v0.3 adds the SSID and path; v0.4 adds the corrected 20x20 bitmap and the 48-symbol selector; v0.5 keeps the source fixed while the frame body scrolls; v0.6 fixes the UP/DOWN direction |
| On-air test: APRS RX on a second radio, FT3D | **Done** (2026-09-30, v0.1: frames received by a UV-K1 running APRS RX and by the FT3D, first try, default level 66 and twist 0) |
## Station settings (`gen_assets.py`, then rebuild)
@@ -36,7 +36,7 @@ Yaesu bitmap set is stored in the app assets; the editor keeps the full width.
| Key | Action |
|---|---|
| PTT or MENU | Send one frame |
| UP / DOWN (held) | Scroll the frame body above the separator 1 px per 40 ms loop (UV-K1: LEFT/RIGHT, as `nav_dir`), while the source callsign stays fixed in bold on line 0 as in APRS RX. In the status bar (x = 72-76), ▲ while rows are hidden above and ▼ while rows are hidden below; while F is armed its icon (x = 70-78, centred on them) takes their place, and they are not drawn while transmitting (the `TRANSMIT` capsule ends at x = 72). The body uses the small font, 18 characters a row, 3 visible rows at a time: `>DEST,WIDE…` (a row breaks after a comma), latitude, longitude, then symbol and comment |
| UP / DOWN (held) | Scroll the frame body above the separator 1 px per 40 ms loop, according to `SET_NAV` (UV-K1: LEFT/RIGHT), while the source callsign stays fixed in bold on line 0 as in APRS RX. In the status bar (x = 72-76), ▲ while rows are hidden above and ▼ while rows are hidden below; while F is armed its icon (x = 70-78, centred on them) takes their place, and they are not drawn while transmitting (the `TRANSMIT` capsule ends at x = 72). The body uses the small font, 18 characters a row, 3 visible rows at a time: `>DEST,WIDE…` (a row breaks after a comma), latitude, longitude, then symbol and comment |
| 1 / F then 1 | Tone level (deviation) up / down: REG_70 gain 10-127, step 4, default 66 (the firmware's tone gain) |
| 2 / F then 2 | Twist `tw` up / down, -4..+8: 2200 Hz gain = level × (8 + tw) / 8 (-6..+6 dB) |
| F | Arm the next key's down direction (1, 2), as FoxHunt's F; icon in the status bar while armed |
+2 -2
View File
@@ -501,9 +501,9 @@ static void handleKeys(void){
uint8_t key=A->get_key();
/* UP/DOWN, held, out of the editor: scroll 1 px per loop (25 px/s), from
* the source row to the last row fully shown (its blank bit 7 on the
* separator) */
* separator). Raw KEY_UP is UP on K5 and LEFT on K1; KEY_DOWN is DOWN / RIGHT. */
if(!g.edit){
int8_t d=A->nav_dir(key); /* g.lim: draw(), 0 when compact */
int8_t d=(key==APP_KEY_DOWN)-(key==APP_KEY_UP);
if((d<0 && g.top) || (d>0 && g.top<g.lim)){ g.top=(uint8_t)(g.top+d); g.redraw=true; }
}
if(key==APP_KEY_INVALID||key==g.prevKey){ g.prevKey=key; return; }
+1 -1
View File
@@ -8,7 +8,7 @@ set -euo pipefail
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
APP_NAME="APRS TX" # <-- the only per-app line
APP_VER="0.5"
APP_VER="0.6"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
+19 -6
View File
@@ -41,7 +41,7 @@ Keys (UV-K5 and UV-K1):
| Key | Action |
|---|---|
| UP / DOWN (held) | Scroll the selected message one pixel per 50 ms slot (direction follows the firmware navigation setting, so K1 LEFT/RIGHT work as on the main screen) |
| UP / DOWN (held) | Scroll the selected message one pixel per 50 ms slot, according to `SET_NAV` (UV-K1: LEFT/RIGHT) |
| UP / DOWN (pressed again at an end) | Select the newer / older message, from its first detail row |
| * | Normal / compact view, saved on exit; returns to the first detail row. Normal view is the default |
| 4 / 6 | Tune −5 / +5 kHz from the VFO, up to ±50 kHz |
@@ -134,6 +134,14 @@ identification, 65-85 coarse position (N/S, degrees, 15' steps), 107-110 `1101`,
111 position source, 112 121.5 MHz homing, 113-132 offsets (±minutes, 4 s steps).
The 15-hex ID is bits 26-85 with the position replaced by the default pattern.
ELT(DT) and RLS location protocols (codes 1001, 1101): bits 41-66
identification, 67-85 coarse position (N/S, 30' steps, E/W, 30' steps), 107-114
protocol data (ELT(DT): activation, altitude, location freshness; not displayed),
115-132 offsets (±, 4-bit minutes, 4 s steps). Same sign / latitude / sign /
longitude structure as the standard location, two bits later and one bit
shorter, so one parser covers both (v2.0). Layout taken from the reference
decoder (moricef/Decode_sarsat_406_v1g_v2g), to confirm against T.001.
## Decoder (`dec406.c`)
Streaming, one call per ADC sample, integer only, no division per sample,
@@ -199,8 +207,10 @@ noise, +/-4 kHz offset, 2000 ppm clock error), then on the radio.
test/flipper406.py test/flipper
```
writes `epirb406_long.sub` (reference frame), `_selftest`, `_short` and
`_bch_err` (bit 50 flipped, BCH-1 fails), on 433.650 MHz (433 MHz SRD band). Copy
writes `epirb406_long.sub` (reference frame), `_selftest`, `_short`,
`_bch_err` (bit 50 flipped, BCH-1 fails), `_eltdt` and `_eltdt_selftest`
(ELT(DT) location protocol, same position, 15-hex ID `1C72091A2B3FDFF`), on
433.650 MHz (433 MHz SRD band). Copy
them to `subghz/` on the Flipper, set the K1 to 433.650 MHz FM wide, launch the
app, then Send one file per burst, at least 2 s apart. If the Flipper's crystal
puts the carrier off the channel, tune with UP/DOWN. Never transmit these files
@@ -314,8 +324,11 @@ a real bias reproduced on the host at the measured level.
- **BCH generators** are the T.001 ones. They are only checked against frames
built with the same code; the bench generator's real frame
(`FFFE2F8E3E12345631401FB07DF58521EDA3` expected) will confirm them.
- **Protocol name table** to check against T.001; national location, RLS and
ELT-DT positions are not decoded, and their ID is shown as raw bits 26-85.
- **Protocol name table** to check against T.001; national location positions
are not decoded, and their ID is shown as raw bits 26-85.
- **ELT(DT) and RLS positions** (v2.0, issue #616): position and 15-hex ID
decoded on the radio with `epirb406_eltdt.sub` (2026-10-07); to confirm
against a real ELT(DT) frame, and RLS is untested.
- The synthetic chain is a model: the first real captures from PA4 may need the
integrator or DC constants retuned (`dec406_init(..., integrate)` also allows a
phase-like input if the hardware turns out to integrate already).
@@ -325,4 +338,4 @@ a real bias reproduced on the host at the measured level.
`APP_VER` in `build.sh` is bumped for every build that goes on a radio and is
stored in the `.app` metadata. The status-bar title stays simply `EPIRB 406`.
Current: **v1.8**.
Current: **v2.0**.
+1 -1
View File
@@ -8,7 +8,7 @@ set -euo pipefail
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
APP_NAME="EPIRB 406" # <-- the only per-app line
APP_VER="1.8"
APP_VER="2.0"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
+37 -23
View File
@@ -35,11 +35,6 @@
#define BCH1_GEN 0x26D9E3u /* x^21+x^18+x^17+x^15+x^14+x^12+x^11+x^8+x^7+x^6+x^5+x+1 */
#define BCH2_GEN 0x1539u /* x^12+x^10+x^8+x^5+x^4+x^3+1 */
/* Standard location default position, bits 65-85, as used for the 15-hex ID.
* Matches the reference decoder output for ID 1C7C2468ACFFBFF; to be confirmed
* against T.001. */
#define STD_DEFAULT_POS 0x0FFBFFu /* 0 1111111 11 0 11111111 11 */
/* Bit count, one pass per set bit: at most 4 x 32 passes per half-bit (every
* 12 samples, and only while searching), far inside the 5000 cycles of a
* sample period; 36 bytes smaller than the branch-free version. */
@@ -170,11 +165,16 @@ static uint32_t syndrome(const dec406_t *d, uint8_t first, uint8_t last, uint32_
return reg;
}
static bool isStdLoc(uint8_t code)
/* First bit of the position field in PDF-1, 0 when the position is not decoded:
* 65 = standard location (15' steps), 67 = ELT(DT) and RLS (30' steps). */
static uint8_t posBase(uint8_t code)
{
/* 0010 EPIRB MMSI, 0011 ELT 24-bit, 0100 ELT serial, 0101 ELT op. designator,
* 0110 EPIRB serial, 0111 PLB serial, 1100 ship security, 1110 test */
return (code >= 2u && code <= 7u) || code == 12u || code == 14u;
if ((code >= 2u && code <= 7u) || code == 12u || code == 14u) return 65u;
/* 1001 ELT(DT), 1101 RLS */
if (code == 9u || code == 13u) return 67u;
return 0u;
}
void dec406_parse(const dec406_t *d, dec406_info_t *o)
@@ -187,43 +187,57 @@ void dec406_parse(const dec406_t *d, dec406_info_t *o)
o->userProto = bit(d, 26);
o->country = (uint16_t)field(d, 27, 10);
o->proto = (uint8_t)(o->userProto ? field(d, 37, 3) : field(d, 37, 4));
o->stdLoc = !o->userProto && isStdLoc(o->proto);
o->idRaw = !o->stdLoc;
/* Both layouts are: sign, latitude, sign, longitude (one bit longer), up to
* bit 85. Latitude is 9 bits from bit 66 or 8 bits from bit 68. */
uint8_t base = o->userProto ? 0u : posBase(o->proto);
uint8_t latLen = (uint8_t)(base == 65u ? 9u : 8u);
uint8_t lonSign = (uint8_t)(base + 1u + latLen);
o->stdLoc = base == 65u;
o->idRaw = !base;
o->idData = field(d, 41, 24);
o->hasPos = o->hasFine = o->internalPos = o->homing = 0;
o->latS = o->lonS = 0;
/* 15-hex ID: bits 26-85, with the position replaced by its default value
* (signs 0, latitude and longitude all ones) when it is a decoded one */
for (uint8_t k = 0; k < 15; k++) {
uint8_t nib = 0;
for (uint8_t j = 0; j < 4; j++) {
uint8_t n = (uint8_t)(26u + 4u * k + j);
uint8_t b = (o->stdLoc && n >= 65u) ? (uint8_t)((STD_DEFAULT_POS >> (85u - n)) & 1u) : bit(d, n);
uint8_t b = (base && n >= base) ? (uint8_t)(n != base && n != lonSign) : bit(d, n);
nib = (uint8_t)((nib << 1) | b);
}
o->id[k] = (char)(nib < 10u ? '0' + nib : 'A' - 10 + nib); /* no hex table */
}
o->id[15] = '\0';
if (!o->stdLoc) return;
if (!base) return;
if (field(d, 65, 21) != STD_DEFAULT_POS) {
int32_t lat = (int32_t)field(d, 66, 7) * 3600 + (int32_t)field(d, 73, 2) * 900;
int32_t lon = (int32_t)field(d, 76, 8) * 3600 + (int32_t)field(d, 84, 2) * 900;
if (o->longMsg && o->bch2 && field(d, 107, 4) == 0xDu) {
uint32_t am = field(d, 114, 5), as = field(d, 119, 4);
uint32_t om = field(d, 124, 5), os = field(d, 129, 4);
if (am <= 30u && om <= 30u) {
uint32_t la = field(d, (uint8_t)(base + 1u), latLen);
if (la != (1u << latLen) - 1u) { /* not the default position */
int32_t unit = o->stdLoc ? 900 : 1800; /* arc seconds per step */
int32_t lat = (int32_t)la * unit;
int32_t lon = (int32_t)field(d, (uint8_t)(lonSign + 1u), (uint8_t)(latLen + 1u)) * unit;
/* PDF-2 offsets: sign (1 = +), minutes, seconds in 4 s steps, twice.
* Standard location: from bit 113, 5-bit minutes, behind the fixed
* bits 107-110 = 1101. ELT(DT) and RLS: from bit 115, 4-bit minutes.
* Seconds 1111 is the default (no offset) value. */
if (o->longMsg && o->bch2 && (!o->stdLoc || field(d, 107, 4) == 0xDu)) {
uint8_t p = (uint8_t)(base + 48u), m = (uint8_t)(latLen - 4u);
uint32_t am = field(d, (uint8_t)(p + 1u), m), as = field(d, (uint8_t)(p + 1u + m), 4);
uint32_t om = field(d, (uint8_t)(p + 6u + m), m), os = field(d, (uint8_t)(p + 6u + 2u * m), 4);
if (am <= 30u && om <= 30u && as < 15u && os < 15u) {
int32_t dlat = (int32_t)(am * 60u + as * 4u), dlon = (int32_t)(om * 60u + os * 4u);
lat += bit(d, 113) ? dlat : -dlat;
lon += bit(d, 123) ? dlon : -dlon;
lat += bit(d, p) ? dlat : -dlat;
lon += bit(d, (uint8_t)(p + 5u + m)) ? dlon : -dlon;
o->hasFine = 1;
}
}
o->latS = bit(d, 65) ? -lat : lat;
o->lonS = bit(d, 75) ? -lon : lon;
o->latS = bit(d, base) ? -lat : lat;
o->lonS = bit(d, lonSign) ? -lon : lon;
o->hasPos = 1;
}
if (o->longMsg && o->bch2) { o->internalPos = bit(d, 111); o->homing = bit(d, 112); }
if (o->stdLoc && o->longMsg && o->bch2) { o->internalPos = bit(d, 111); o->homing = bit(d, 112); }
}
const char *dec406_proto_name(const dec406_info_t *in)
+2 -2
View File
@@ -56,12 +56,12 @@ typedef struct {
uint8_t bch1, bch2; /* 1 = BCH ok (bch2 is 1 on short messages) */
uint8_t userProto; /* bit 26: 1 = user / user-location protocols */
uint8_t proto; /* protocol code: bits 37-40, or 37-39 if user */
uint8_t stdLoc; /* standard location protocol (position decoded) */
uint8_t stdLoc; /* standard location protocol (source, homing) */
uint8_t hasPos; /* position present (not the default pattern) */
uint8_t hasFine; /* PDF-2 offsets applied */
uint8_t internalPos; /* bit 111: 1 = internal navigation device */
uint8_t homing; /* bit 112: 121.5 MHz homing */
uint8_t idRaw; /* 1 = ID is bits 26-85 as sent (not std location) */
uint8_t idRaw; /* 1 = ID is bits 26-85 as sent (position not decoded) */
uint16_t country; /* bits 27-36 */
uint32_t idData; /* std location: bits 41-64 */
int32_t latS, lonS; /* position in arc seconds, N and E positive */
+1 -1
View File
@@ -364,7 +364,7 @@ static void draw(void){
/* ---- input ---- */
static void handleKey(uint8_t key){
int d=g.A->nav_dir(key);
int d=(key==APP_KEY_DOWN)-(key==APP_KEY_UP);
unsigned top=(unsigned)(g.top+d);
if(top<=g.lim) g.top=(uint16_t)top;
else if(key!=g.prevKey && (unsigned)(g.cur+d)<g.count){ g.cur=(uint8_t)(g.cur+d); g.top=0; }
@@ -0,0 +1,6 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 161250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -21250
@@ -0,0 +1,6 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 161250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -21250
+4 -1
View File
@@ -11,7 +11,8 @@ sign (checked with a pure-Python model of the receive chain and dec406, down to
NEVER transmit these files on 406.0-406.1 MHz (distress band).
flipper406.py <out_dir> -> epirb406_long.sub, _selftest, _short, _bch_err
flipper406.py <out_dir> -> epirb406_long.sub, _selftest, _short, _bch_err,
epirb406_eltdt.sub, _eltdt_selftest
One burst per file: press Send once, wait at least 2 s before the next one
(the app waits for the carrier to drop before re-arming).
"""
@@ -31,6 +32,8 @@ FRAMES = {
"selftest": build(selftest=True),
"short": build(short=True),
"bch_err": build(flip=50), # BCH-1 must show ERR
"eltdt": build(eltdt=True), # ELT(DT) location protocol
"eltdt_selftest": build(eltdt=True, selftest=True),
}
def raw_durations(frame_hex):
+26 -2
View File
@@ -10,6 +10,9 @@ position 49 deg 16'16" N, 0 deg 46'56" E, external source, 121.5 homing),
frame406.py --selftest same message, self-test frame sync
frame406.py --short short message (112 bits, 28 hex)
frame406.py --flip N invert message bit N (25..144), BCH must fail
frame406.py --eltdt ELT(DT) location protocol (code 1001), same
position: 24-bit address 0x123456, G-switch
activation, altitude <= 400 m, fresh location
"""
import argparse
@@ -32,7 +35,27 @@ def bch(data, gen, r):
return bits(reg, r)
def build(short=False, selftest=False, flip=None):
def build_eltdt():
"""PDF-1 and PDF-2 (before BCH) of a long ELT(DT) location message."""
# bits 41-42 ID type (00 = aircraft 24-bit address), 43-66 ID data,
# 67-75 latitude and 76-85 longitude in 0.5 deg steps (sign: 1 = S / W)
pdf1 = [1, 0] + bits(227, 10) + bits(0b1001, 4) + [0, 0] + bits(0x123456, 24) \
+ [0] + bits(99, 8) + [0] + bits(2, 9) # 49.5 N, 1.0 E
# 107-108 activation, 109-112 altitude, 113-114 freshness, then the offsets
# (sign: 1 = +, minutes 0-15, seconds in 4 s steps): -13'44" and -13'04"
pdf2 = bits(1, 2) + bits(0, 4) + bits(3, 2) \
+ [0] + bits(13, 4) + bits(44 // 4, 4) + [0] + bits(13, 4) + bits(4 // 4, 4)
return pdf1, pdf2
def build(short=False, selftest=False, flip=None, eltdt=False):
if eltdt:
pdf1, pdf2 = build_eltdt()
msg = pdf1 + bch(pdf1, G1, 21) + pdf2 + bch(pdf2, G2, 12)
if flip is not None:
msg[flip - 25] ^= 1
frame = [1] * 15 + (SYNC_SELFTEST if selftest else SYNC_NORMAL) + msg
return "%0*X" % (len(frame) // 4, int("".join(map(str, frame)), 2))
country, proto, data = 227, 0b1110, 0x123456
lat_c = [0] + bits(49, 7) + bits(1, 2) # N, 49 deg, 1 x 15'
lon_c = [0] + bits(0, 8) + bits(3, 2) # E, 0 deg, 3 x 15'
@@ -55,5 +78,6 @@ if __name__ == "__main__":
ap.add_argument("--short", action="store_true")
ap.add_argument("--selftest", action="store_true")
ap.add_argument("--flip", type=int)
ap.add_argument("--eltdt", action="store_true")
a = ap.parse_args()
print(build(a.short, a.selftest, a.flip))
print(build(a.short, a.selftest, a.flip, a.eltdt))
+2
View File
@@ -15,6 +15,7 @@ LONG=$(python3 "$HERE/frame406.py")
SELF=$(python3 "$HERE/frame406.py" --selftest)
SHORT=$(python3 "$HERE/frame406.py" --short)
FLIP=$(python3 "$HERE/frame406.py" --flip 50)
ELTDT=$(python3 "$HERE/frame406.py" --eltdt)
REF_LONG=("long (144 bits), normal frame" "country : 227" "Std test" "0x123456"
"position : 49.27111, 0.78222" "external, homing yes" "BCH : ok / ok"
@@ -52,6 +53,7 @@ run "AC coupling 150 Hz" "$LONG" "--hpf 150" "${REF_LONG[@]
run "combined worst case" "$LONG" "--cnr 15 --foff 3000 --invert --clock-ppm 2000 --audio-lpf 3500" "${REF_LONG[@]}"
run "self-test frame" "$SELF" "" "self-test frame" "15-hex ID : 1C7C2468ACFFBFF" "BCH : ok / ok"
run "short message" "$SHORT" "" "short (112 bits)" "BCH : ok / ok" "15-hex ID : 1C7C2468ACFFBFF" "(coarse)"
run "ELT(DT) location" "$ELTDT" "" "long (144 bits), normal frame" "ELT-DT" "position : 49.27111, 0.78222" "BCH : ok / ok" "15-hex ID : 1C72091A2B3FDFF" "frames : 1"
run "corrupted bit 50" "$FLIP" "" "BCH : FAIL / ok"
run "3 bursts in a row" "$LONG" "--bursts 3" "frames : 3"
# Measured PA4 level (~150 LSB peak) at low CNR and with bit-rate error, several
+1 -1
View File
@@ -3,7 +3,7 @@ set -euo pipefail
APP="$(basename "$PWD")"
APP_NAME="SystemInfo"
APP_VER="1.1"
APP_VER="1.2"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280}
OUT="${APP_NAME// /}"
+2 -2
View File
@@ -789,7 +789,7 @@ void app_main(const app_api_t *api)
previous = key;
repeat_ms = 0u;
} else if (key != previous) {
const int8_t direction = A->nav_dir(key);
const int8_t direction = (key == APP_KEY_DOWN) - (key == APP_KEY_UP);
A->backlight_on();
if (key == APP_KEY_EXIT)
g.running = false;
@@ -800,7 +800,7 @@ void app_main(const app_api_t *api)
previous = key;
repeat_ms = direction != 0 ? NAV_REPEAT_DELAY_MS : 0u;
} else {
const int8_t direction = A->nav_dir(key);
const int8_t direction = (key == APP_KEY_DOWN) - (key == APP_KEY_UP);
if (direction != 0) {
if (repeat_ms > INPUT_TICK_MS) {
repeat_ms -= INPUT_TICK_MS;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.