Compare commits

..
Author SHA1 Message Date
Armel FAUVEAU 2f9d24cb77 Update donors 2026-10-10 23:26:14 +02:00
Armel FAUVEAU d7e9aa9df2 Add incomplete and experimental ACARS RX overlay app 2026-10-10 18:44:54 +02:00
Armel FAUVEAU 473253130c Fix auto keypad lock timer overflow (issue #632, thanks @BG5GVV) 2026-10-10 15:32:09 +02:00
Armel FAUVEAU 71a8f3dcb7 Update apps 2026-10-10 04:38:51 +02:00
Armel FAUVEAU a9fbe38ad9 Add CW Keyer and improve CW Decode 2026-10-10 04:37:42 +02:00
Armel FAUVEAU 7c14669a18 Merge pull request #621 from jdenoy/feature_update_v6
Sig Finder: new overlay app for direction finding, with ELT swept-tone detection
2026-10-09 22:55:30 +02:00
Armel FAUVEAU 8e9303e5a1 Update apps 2026-10-09 19:22:36 +02:00
Armel FAUVEAU 8ce5790aec Fix SysInfo battery capacity and bump version (issue #623) 2026-10-09 19:19:04 +02:00
Johan Denoyer 5de6430fde Merge pull request #2 from jdenoy/feature/signal-finder
Sig Finder: new overlay app for direction finding, with ELT swept-tone detection
2026-10-09 10:56:15 +02:00
Johan Denoyer d90b5202a9 Sig Finder: linker overflow message names the app 2026-10-09 10:53:39 +02:00
Johan Denoyer 6c9859a051 Sig Finder v1.8: README, 328 B smaller with the same behaviour (3,604 -> 3,276 B)
README.md: usage, every key, the screen, how the ELT detector works and
its limits, and the test tools (host test, Flipper files, LimeSDR).

Size, no functional change:
- detector state in its own struct, not inside g: its fields were past
  the ldrb / ldrh offset reach and each access took two more
  instructions (-152 B)
- putT(): asset text appended in one call instead of T() + put() (-76 B)
- ADC, DAC and SysTick registers as base + offset, the base hidden from
  constant folding: one literal per peripheral, same accesses in the
  same order (-60 B)
- sw_window: fields read once into locals, out of line (-16 B)
- -fno-reorder-blocks -fno-code-hoisting (-24 B)

Checked: a differential test of the old and new sweep.c on 395,854
windows of mixed sweeps, noise and tones gives identical state after
every window; sweep_test (57 cases) and the six Flipper files give the
same results and detection times; register accesses checked in the
disassembly. Not yet re-tested on the radio.
2026-10-09 10:45:53 +02:00
Johan Denoyer 81232b4aef Sig Finder v1.7: Flipper and LimeSDR homer test transmitters, AF label centred
test/flipper_homer.py writes Sub-GHz RAW files (test/flipper/) that play a
homing signal on 433.650 MHz: the Flipper keys the carrier on and off at the
tone rate, i.e. AM at 100 % with a rectangular tone (allowed by DO-183), so
the radio set to AM hears the sweep. Four homers (the default 1570>820 Hz at
2.42 Hz, the full 1600>300 Hz, and the fastest and slowest minimum-span
sweeps) and two negatives (upward sweep, steady 1 kHz). Tested on the radio.
It refuses 121.5, 243 and 406 MHz.

sweep_test now also takes .sub files: the on/off envelope through an AM
demodulator, the PA4 high-pass and the detector. The four homers show ELT
within 1.0..2.0 s, the negatives never.

test/limesdr/: homer121.py (live swept-tone AM homer, default 433.65 MHz)
and limetx.py, whose check_freq now also refuses 121.3-121.7 MHz and
242.8-243.2 MHz, besides 405.9-406.2 MHz.

App: the AM / FM label is centred in its fixed 52..66 box (text drawn with
print_tiny, box inverted as print_inverse does). 3,604 B of 4 KiB.
2026-10-09 10:24:22 +02:00
Johan Denoyer 3d6ceac753 Sig Finder v1.6: EPIRB Finder renamed, ELT swept-tone detector
The app finds any carrier, not only beacons: App/apps/epirb becomes
App/apps/sigfind, menu name "Sig Finder", title "SIG DF". Saved settings
are tied to the app name, so the first launch starts from the defaults.

ELT detector (sweep.c): with the audio off or in listen mode, the RX audio
is sampled on PA4 at 9.6 kHz during each tick's 50 ms pause (the PA4 code
of the POCSAG and EPIRB 406 apps) and its frequency tracked in 25 ms
windows. Three consecutive downward sweeps with a homer's period (2..4 Hz),
range (>= 400 Hz, bottom below 1050 Hz) and a steady top show "ELT" in the
status bar; the line above the graph shows the sweep range and rate, or the
audio frequency, amplitude and score. Beep mode mutes the AF between beeps,
so no detection there.

test/sweep_test.c (host): every legal sweep (2..4 Hz, 700..1300 Hz wide,
weak, SNR 6 dB, with and without the PA4 high-pass) is found within about
2 s; white and low-passed noise, a steady tone, voice-like tone hops and an
upward sweep never are, at draw gaps of 5, 15 and 30 ms. Not yet tested
over the air.

Also: key 6 holds the backlight on (saved), status bar items re-spaced
(lock icon at 29, audio label at 54, ELT at 70) so nothing overlaps the
speaker icon or the battery. 3,556 B of 4 KiB.
2026-10-09 10:14:08 +02:00
Johan Denoyer 1ea2ab686d Merge branch 'feature_update_v6' into feature/epirb-finder 2026-10-09 09:38:39 +02:00
Armel FAUVEAU f6afc46c1d Update apps 2026-10-09 02:33:08 +02:00
Armel FAUVEAU d26f7fcfa0 Fix APRS TX editor navigation direction 2026-10-09 02:32:12 +02:00
Armel FAUVEAU 4125a65667 Update apps 2026-10-08 20:07:52 +02:00
Armel FAUVEAU f335e0f089 Compact status labels to prevent overlap 2026-10-08 20:04:57 +02:00
Armel FAUVEAU c6c9cb8e9d Merge pull request #617 from jdenoy/feature_update_v6
EPIRB 406 v2.3: all C/S T.001 location formats in 4 KiB, ELT(DT) 3LD fix (#616)
2026-10-08 18:54:05 +02:00
Armel FAUVEAU ef9c2db0db APRS RX: gate speaker with squelch 2026-10-08 18:34:44 +02:00
Johan Denoyer 20c69f61f0 Merge pull request #1 from jdenoy/epirb-t001
EPIRB 406 v2.3: every C/S T.001 location format decoded, T.001 test frames for Flipper and LimeSDR
2026-10-08 11:16:34 +02:00
Johan Denoyer 2dcf4d212e EPIRB 406 v2.3: over-the-air validation with the LimeSDR psk files
All 61 T.001 frames sent as real 406 modulation (LimeSDR, txall.py,
433.650 MHz): FRAME 61 ERR 0. Screenshot of the last burst in
test/limesdr/validation_v2.3.png.
2026-10-08 11:14:58 +02:00
Johan Denoyer e2291f46a6 EPIRB 406 tests: LimeSDR transmitter for the T.001 frames
test/limesdr/t001lime.py writes the 61 t001frames.py frames as cs16 IQ
files (1 MS/s): psk/ with the real 406 modulation (+/-1.1 rad, 150 us
transitions), fsk/ with the Flipper signal. limetx.py and txall.py send
them (refusing 405.9-406.2 MHz); t001/README.md is the index txall.py
prints. The IQ files (181 MB per mode) are ignored: regenerate them.
All 61 psk files decode as expected through an FM discriminator and
dec406.
2026-10-08 11:03:47 +02:00
Johan Denoyer 013cf7cf08 EPIRB 406 v2.3: flags below the position, BCH in the bottom row
The source/121.5/COARSE/CANCEL row was the 5th body row in normal view,
only reachable by scrolling. It now follows the position; BCH-1/BCH-2 of
the selected message moves to the right of the bottom row, whose failed
capture label is shortened to ERR. 4084 of 4096 bytes.
2026-10-08 11:01:21 +02:00
Johan Denoyer 63500d2ba9 EPIRB 406 v2.2: decode every C/S T.001 location format in 4 KiB
The position parser is one routine driven by a format table (T.001 Rev. 11
A3.3): standard, national, RLS, ELT(DT) and user-location. New: national
and user-location positions, the 15-hex ID of national location with its
default position, source/121.5 flags for RLS and user-location, and the
ELT(DT) cancellation message (CANCEL flag). The parse writes the default
position into the message bits to build the ID, so no copy is needed.

Size 4092 of 4096 bytes: unsigned helpers, flags row without the
first-flag logic, protocol name index from the parser, RX label in the
assets, host-only fields left out (DEC406_LEAN).

Tests: t001check.py 61/61 (bits and audio chain), run_tests.sh 31/31.
host_dec406 -x HEX parses a frame without the audio chain.
2026-10-08 10:51:42 +02:00
Johan Denoyer 8f8c8afc21 EPIRB 406 tests: C/S T.001 Rev. 11 frame catalogue and Flipper files
t001frames.py builds one frame per Annex A coding option (62): user
protocols (short/long, emergency codes, self-test), user-location,
standard, national, RLS and ELT(DT) location (default position, S/W,
3LD rotating field, cancellation), with the expected 15-hex ID and
position. Checked against the Annex B example and the bench generator
frame. t001flipper.py writes flipper/t001/*.sub and an index;
t001check.py decodes every frame through gen406 + host_dec406.
2026-10-08 10:33:47 +02:00
Johan Denoyer a9d487f309 EPIRB 406 v2.1: ignore ELT(DT) PDF-2 rotating field as position offsets
ELT(DT) bits 113-114 = 00 flag a rotating field (operator 3LD) in bits
115-132 (C/S T.001 A3.3.8.3). v2.0 decoded them as offsets and showed a
wrong fine position (default 3LD ZGA: -2'08" lat, +7'32" lon) with no
COARSE flag. Offsets are now applied only when the freshness is non-zero,
as in the reference decoder.

Fits in the overlay (4084 B): idData is compiled out of the app, and the
parser flags the position source itself, removing a check in the display.
Test: --3ld frame in frame406.py, run_tests.sh and a Flipper .sub.
2026-10-08 09:53:02 +02:00
Armel FAUVEAU 7028aac1bd Update donors 2026-10-08 05:33:26 +02:00
Armel FAUVEAU 6c0e071155 Add optimized CW Decode overlay with AGC control 2026-10-08 04:53:44 +02:00
Armel FAUVEAU 907a688d17 Add optimized CW Decode overlay with AGC control 2026-10-08 04:52:35 +02:00
Armel FAUVEAU 12f5b5013a EPIRB ELT(DT) and RLS 2026-10-07 23:44:16 +02:00
Armel FAUVEAU 78ae6445bc Decode ELT(DT) and RLS positions in EPIRB 406 (issue #616) 2026-10-07 23:42:03 +02:00
Armel FAUVEAU 4207b86b74 Update app binaries 2026-10-07 20:43:45 +02:00
Armel FAUVEAU 28b137f83c Fix overlay scroll direction on K1 and K5 V3 (issue #613) 2026-10-07 20:42:19 +02:00
Johan Denoyer b8470d2219 EPIRB Finder v1.1: 1,740 B smaller, same behaviour (3,888 -> 2,148 B)
Rebased on v6.1.0 and reworked with the size techniques of the v6.1 apps:
- texts, icons, attenuator ladder and powers of ten moved to read-only
  assets (gen_assets.py, 257 B outside the overlay)
- no division linked: numbers printed by subtracting powers of ten,
  divisions by constants as exact multiply-and-shift (checked exhaustively)
- all state in one struct, history buffer on app_main's stack
- closeness bar and history graph written straight into the frame buffer
- config loaded and saved in place, locked key paths merged
- -fno-jump-tables -fno-move-loop-invariants for this app

Needs API level 2 (v6.1.0+). Verified on host against the previous build:
360 scripted scenarios, identical API calls, screens and tones.
2026-10-07 13:48:04 +02:00
Johan Denoyer 3e8b9ca4dd Add EPIRB Finder overlay app
Direction finding for 121.5 / 243 MHz AM homing beacons with a
directional antenna. Forked from FoxHunt, RX only.

Audio and visual feedback are relative to a peak instead of absolute
dBm, so guidance keeps working when the signal gets strong:
- FOLLOW mode (default): peak falls back at 0.5/1/2/4 dB/s
- SWEEP mode: peak held, MENU starts a new 360 deg sweep
- Beeps speed up and rise in pitch toward the peak, with a
  distinct chirp within 1.5 dB of it
- Auto attenuator steps before RSSI saturation and re-bases the
  peak and history by the measured offset
- 18 s history graph drawn relative to the peak (lobe shape)

Direction finding works on AM, FM and SSB (RSSI is read before
demodulation). Listen modes follow the firmware AF mapping: AM/FM
use the FM AF output with the VFO's demodulator, USB uses the
baseband output; picked at launch from REG_47 when possible.

Works on UV-K5 and UV-K1 (UP/DOWN through nav_dir).
Size: 3888 B of the 4 KiB overlay.
2026-10-07 12:17:00 +02:00
147 changed files with 8109 additions and 190 deletions

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
# ACARS RX
Receive-only VHF ACARS overlay for the Fusion firmware. Its presentation and
controls follow APRS RX: a compact status bar, the tuned frequency, signal
level, scrollable payload text and a three-message history.
## Use
1. Select an AM channel on a local ACARS frequency.
2. Launch **ACARS RX** from Apps.
3. Use **UP/DOWN** to scroll a long message and browse the last three messages,
**\*** to switch between the normal and compact views, **1** to enable or
mute the speaker, **F** to switch between decoded and raw payloads, **2** to
clear the history and counters, and **EXIT** to leave. The selected display
size and speaker state are saved on exit. To avoid noise bursts while
waiting, the speaker opens only after a complete ACARS header is recognized.
For Paris, start with **131.525 MHz**, then try **131.725 MHz** and
**131.825 MHz**. Reception depends on local traffic, antenna and obstructions.
The normal view uses the regular 18-character font and shows three payload
rows. The compact view uses the 32-character tiny font and shows four rows, as
in APRS RX. The header shows the aircraft address and flight identifier. A
leading `.` used to pad a six-character registration to the seven-character
ACARS address field is hidden. The label and printable application text follow
below. On downlinks, the four-character message sequence and duplicated flight
identifier are hidden; short blocks are shown from their first payload byte.
ACARS payloads are terse operational messages; many are not intended to be
human-friendly.
The bottom line shows the captured signal level, `RX` for the total number of
valid frames received since launch and `FIX` for the subset that required
parity-bit repair before passing the BCS check.
The decoded view identifies link tests (`Q0`), Media Advisory (`SA`), MIAM
(`MA`) and ARINC 622 ATS payloads (`A6`, `AA`, `B6`, `BA`). For `H1`, it also
extracts the standard sublabel and optional Message Function Identifier, for
example `SUB M1 MFI B6`, before showing the remaining application data. Unknown
labels fall back to their raw payload. This deliberately does not claim to
decode proprietary airline fields or the complete ASN.1 ARINC 622 payload.
While no valid message has been accepted, the main payload area stays clear and
the row below the frequency reports the ADC audio peak-to-peak level and three
counters: `SYN` for detected ACARS headers, `CRC` for checksum failures and
`PAR` for parity failures. Counters above 99 are displayed as `99+` so the
line always fits. A changing ADC value with `SYN 0` means audio reaches the
application but the modem has not locked.
## Decoder
The BK4829 supplies AM audio but does not decode ACARS. The overlay samples PA4
at 19.2 kHz, mixes the 1800 Hz MSK centre to complex baseband and applies a
half-sine matched filter.
- **Carrier loop.** The MCU runs from its internal RC oscillator, so the sample
clock may be 1 % off and the tones appear up to 18 Hz away. The loop is wide
and its integrator fast until the text starts, then both narrow.
- **Bit clock.** The pre-key is a pure tone and carries no bit timing. Once
data starts, the quadrature rail of each decision is compared with the
symbols on either side of it; its sign tells an early clock from a late one
and steps the clock by 1/6 sample before the text, 1/24 sample inside it.
- **Synchronization.** The receiver slides bit by bit onto `SYN SYN` in either
polarity, which leaves `+` and `*` for the bit clock to settle, then expects
`SOH`. It accepts ETX and ETB endings and checks odd parity and the complete
16-bit BCS. Up to two parity-marked bit errors are repaired only when the
corrected message also has a valid BCS.
- **Slot work.** A leaky run counter follows the pre-key: noise decays to
zero, the tone ramps up. While it is up, or a block is being received,
nothing else runs. The key scan costs about four samples, so it waits for
the modem only; the screen and the battery also wait one second after the
BK4829 reports an open squelch, unless a key asked for a redraw. Nothing
waits longer than three seconds.
`test/model_rx.py` mirrors the receiver line for line and is the place to try
any change first. Its tests cover every sample offset of the bit clock against
eight carrier phases, a sample clock from -1.5 % to +1.5 % on a full-length
block, a weak signal, a frame after one second of open-squelch noise, parity
repair, BCS rejection, and the share of idle slots the modem claims.
```sh
python3 -B test/model_rx.py
```
The model and the Flipper files share one transmitter, written from the ACARS
description. A block received off the air has not been checked against them
yet.
## Flipper Zero bench frames
`test/flipper_acars.py` generates Sub-GHz RAW files in `test/flipper/`. The
stock Flipper cannot transmit on the 118-137 MHz aeronautical band, so the bench
files use **433.650 MHz** and OOK. The keyed carrier follows the sign of the
ACARS MSK waveform; the radio's AM detector recovers the square-wave audio and
its audio filter keeps the useful fundamental. The bench waveform uses a
32-byte pre-key to tolerate the extra distortion introduced by OOK; the host
decoder tests continue to cover the standard 16-byte pre-key.
1. Copy the six `.sub` files from `test/flipper/` into `subghz/` on the
Flipper Zero.
2. Tune the radio to **433.650 MHz AM** and launch ACARS RX.
3. Open `Sub-GHz > Saved` on the Flipper and send one file at a time.
| File | Expected result |
|---|---|
| `acars_basic.sub` | Displays `FLIPPER ZERO ACARS TEST` |
| `acars_long.sub` | Displays a scrollable multi-row message |
| `acars_h1_arinc.sub` | Displays `SUB M1 MFI B6`, `ARINC 622` and the remaining data |
| `acars_etb.sub` | Accepts an ETB-terminated block |
| `acars_repair.sub` | Displays after repairing one parity-marked bit |
| `acars_badcrc.sub` | Remains hidden and increments `CRC` |
Regenerate them with:
```sh
python3 -B test/flipper_acars.py test/flipper
```
Use only a frequency permitted by the Flipper's configured region and keep the
test brief and local. The script rejects the live aeronautical band and values
outside the built-in CC1101 operating ranges.
+704
View File
@@ -0,0 +1,704 @@
/* Copyright 2026 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* ACARS RX - VHF ACARS (AM-MSK, 2400 bit/s) receive-only overlay.
*
* The 1200/2400 Hz MSK audio is sampled from PA4 at 19.2 kHz. A complex
* 1800 Hz mixer and a half-sine matched filter feed a carrier PLL and a
* data-aided bit clock. The 0xff pre-key pulls the PLL onto the sender's tones,
* whatever the +/-1 % error of the MCU's RC oscillator; the receiver then slides
* bit by bit onto SYN SYN, in either polarity, and expects SOH. Odd parity and
* the complete 16-bit BCS are checked before a message reaches the history. One
* or two parity-marked bit errors are corrected only when the corrected frame
* also has a valid BCS.
*
* Keys: UP/DOWN scroll and browse history, * normal/compact, F decoded/raw,
* 1 speaker, 2 clear, EXIT quit.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "../app_api.h"
#include "acarsrx_assets.h"
static inline volatile uint32_t *hw(uint32_t a){
volatile uint32_t *p=(volatile uint32_t *)a;
__asm__("" : "+l"(p));
return p;
}
/* ---- MCU registers (PY32F071, SysTick at 48 MHz) ---- */
#define SYST_LOAD (*(volatile uint32_t *)0xE000E014u)
#define SYST_VAL (*(volatile uint32_t *)0xE000E018u)
#define ADC_SR (hw(0x40012400u)[0x00/4])
#define ADC_CR2 (hw(0x40012400u)[0x08/4])
#define ADC_SMPR3 (hw(0x40012400u)[0x14/4])
#define ADC_SQR3 (hw(0x40012400u)[0x38/4])
#define ADC_DR (hw(0x40012400u)[0x50/4])
#define GPIOA_MODER (*(volatile uint32_t *)0x50000000u)
#define DAC_CR (hw(0x40007400u)[0x00/4])
#define DAC_SWTRIGR (hw(0x40007400u)[0x04/4])
#define DAC_DHR12R1 (hw(0x40007400u)[0x08/4])
#define RCC_APBENR1 (*(volatile uint32_t *)0x4002103Cu)
#define RCC_DACEN (1u << 29)
#define DAC_CR_BIAS ((1u << 0) | (1u << 1) | (1u << 2) | (7u << 3))
#define BIAS_CODE 2048u
#define ADC_SR_EOC (1u << 1)
#define ADC_START ((1u << 22) | (1u << 20))
#define SMP8_POS 24u
#define SMP4_POS 12u
#define ADC_CH_PA4 4u
/* ---- sample clock and MSK PLL ---- */
#define FS 19200u
#define SAMPLE_CYC (48000000u / FS)
#define HOUSE_EVERY 960u /* 50 ms */
#define BUSY_MAX 60u /* never starve keys for more than 3 s */
#define RF_HOLD_SLOTS 20u /* one second without SPI polling */
#define REFRESH_SLOTS 20u /* diagnostics and battery every second */
#define PHASE_NOMINAL 6144u /* 1800 / 19200 of a 16-bit turn */
#define BIT_PHASE 49152 /* 3/4 turn = one 2400 bit/s symbol */
#define PLL_I_LIMIT (192 * 16)
#define ACQ_GAIN 24 /* phase gain multiplier before the text */
#define ACQ_I 3 /* integrator step per unit of phase step */
#define TRACK_I 8 /* integrator step once in the text */
#define TIMING_ACQ 1024 /* bit clock step before the text, 1/6 sample */
#define TIMING_TRACK 256
#define PREKEY_LEAK 3 /* a bit against the run costs three with it */
#define SYNC_WORD 0x1616u /* SYN SYN in the last 16 bits */
#define SYNC_INVERTED 0xE9E9u
/* ---- ACARS frame ---- */
#define FRAME_MAX 240u
#define MESSAGE_MAX 226u /* 240 - fixed 13-byte ACARS header - ETX */
#define HISTORY 3u
#define BODY_CHARS 18u
#define BODY_ROWS 3u
#define COMPACT_EXTRA 14u
#define VIEW_COMPACT 1u
#define WAIT_CAPS_X 40u
#define SPK_X 59u
#define CAPS_END 127u
#define REDRAW_ON 1u
#define REDRAW_FRAME 2u
enum { PREKEY, SOH1, TEXT, CRC1, CRC2, END };
enum { APP_UNKNOWN, APP_TERMINAL, APP_ARINC622, APP_LINK, APP_MEDIA, APP_MIAM };
typedef struct {
char address[8];
char label[3];
char flight[7];
char text[MESSAGE_MAX+1u];
char sublabel[3], mfi[3];
uint8_t textLen, appAt, down;
int16_t rssi;
} message_t;
typedef struct {
int32_t i, q;
} iq_t;
typedef struct {
iq_t ring[16];
int32_t dc;
int32_t pllI;
int32_t clock, xq; /* bit clock; quadrature rail of the last decision */
uint32_t phase;
uint8_t ringAt, symbol;
int8_t cosine[64];
uint8_t taps[16];
} dem_t;
static struct {
uint8_t prevKey, redraw;
bool running;
uint8_t spk, view, raw, refresh, busyFor, audioHold, rfHold, audioOn; /* persistent bytes first: speaker, view */
uint8_t state, bits, inv;
int8_t prekey;
uint8_t len, parityErrors, parityAt[2];
uint8_t count, cur, top, lim;
uint16_t shift, adcMin, adcMax, ppNow, ppShown;
uint16_t syncs, crcFails, parityFails, corrected, nOk;
uint8_t crcBytes[2];
uint8_t *frame;
dem_t *dem;
message_t **history;
const app_api_t *A;
uint32_t tPrev, tCycles, savedSqr3, savedSmpr3;
} g;
/* GCC may turn simple freestanding loops into these calls. */
void *memset(void *d,int c,size_t n){ uint8_t *p=d; while(n)p[--n]=(uint8_t)c; return d; }
void *memcpy(void *d,const void *s,size_t n){
uint8_t *p=d; const uint8_t *q=s; while(n){ --n; p[n]=q[n]; } return d;
}
/* ---- formatting ---- */
static char *put(char *o,const char *s){ while(*s)*o++=*s++; return o; }
static unsigned sub(uint32_t *v,uint32_t d){ unsigned q=0; while(*v>=d){ *v-=d; q++; } return q; }
static const uint16_t P10[]={10000u,1000u,100u,10u,1u};
static char *puti(char *o,int32_t v){
uint32_t u;
if(v<0){ *o++='-'; u=(uint32_t)(-v); } else u=(uint32_t)v;
bool lead=false;
for(unsigned i=0;i<5u;i++){
unsigned c=sub(&u,P10[i]);
if(c||lead||i==4u){ *o++=(char)('0'+c); lead=true; }
}
return o;
}
/* Keep the four waiting diagnostics inside one 32-character tiny row. */
static char *putStat(char *o,uint16_t v){
if(v>99u){ o=puti(o,99); *o++='+'; return o; }
return puti(o,v);
}
static void drawFreq(uint32_t f,char *text){
const app_api_t *A=g.A;
unsigned mhz=0;
while(f>=100000u){ f-=100000u; mhz++; }
char *o=puti(text,(int32_t)mhz);
*o++='.';
for(unsigned i=0;i<5u;i++) *o++=(char)('0'+sub(&f,P10[i]));
*o='\0';
o-=2;
A->print_normal(o,(uint8_t)((o-text)*13u-10u),0,5);
*o='\0';
A->display_freq(text,0,4,false);
}
/* ---- hardware shared with the resident battery ADC ---- */
static void clkStart(void){ g.tPrev=SYST_VAL; g.tCycles=0; }
static uint32_t clkNow(void){
uint32_t v=SYST_VAL;
g.tCycles+=(v<=g.tPrev)?g.tPrev-v:g.tPrev+(SYST_LOAD+1u-v);
g.tPrev=v;
return g.tCycles;
}
static void adcSelect(void){
ADC_SMPR3=(g.savedSmpr3&~(7u<<SMP4_POS))|(((g.savedSmpr3>>SMP8_POS)&7u)<<SMP4_POS);
ADC_SQR3=(g.savedSqr3&~0x1Fu)|ADC_CH_PA4;
}
static void adcRestore(void){ ADC_SQR3=g.savedSqr3; ADC_SMPR3=g.savedSmpr3; }
static uint16_t adcRead(void){
ADC_CR2|=ADC_START;
for(uint16_t n=0;!(ADC_SR&ADC_SR_EOC)&&n<2000u;n++){}
return (uint16_t)(ADC_DR&0x0FFFu);
}
static void biasOn(uint32_t moder,uint32_t rcc){
GPIOA_MODER=moder|(3u<<8);
RCC_APBENR1=rcc|RCC_DACEN;
DAC_CR=DAC_CR_BIAS;
DAC_DHR12R1=BIAS_CODE;
DAC_SWTRIGR=1u;
}
static void biasOff(uint32_t dac,uint32_t dhr,uint32_t rcc,uint32_t moder){
DAC_CR=dac; DAC_DHR12R1=dhr; RCC_APBENR1=rcc; GPIOA_MODER=moder;
}
/* ---- ACARS BCS, CCITT polynomial, bytes and bits in transmitted order ---- */
static uint16_t crcAdd(uint16_t crc,uint8_t byte){
for(uint8_t n=0;n<8u;n++){
bool top=(crc&0x8000u)!=0;
crc=(uint16_t)((crc<<1)|((byte>>n)&1u));
if(top) crc^=0x1021u;
}
return crc;
}
static bool odd(uint8_t v){
v^=v>>4; v^=v>>2; v^=v>>1;
return (v&1u)!=0;
}
static uint16_t frameCrc(void){
uint16_t crc=0;
for(uint8_t n=0;n<g.len;n++) crc=crcAdd(crc,g.frame[n]);
crc=crcAdd(crc,g.crcBytes[0]);
return crcAdd(crc,g.crcBytes[1]);
}
/* Correct only bytes whose odd parity failed. A correction is accepted solely
* when the complete BCS becomes zero, so diagnostics never become messages. */
static bool repair(void){
if(!g.parityErrors||g.parityErrors>2u) return false;
uint8_t p0=g.parityAt[0];
for(uint8_t a=0;a<8u;a++){
g.frame[p0]^=(uint8_t)(1u<<a);
if(g.parityErrors==1u){
if(frameCrc()==0u) return true;
}else{
uint8_t p1=g.parityAt[1];
for(uint8_t b=0;b<8u;b++){
g.frame[p1]^=(uint8_t)(1u<<b);
if(frameCrc()==0u) return true;
g.frame[p1]^=(uint8_t)(1u<<b);
}
}
g.frame[p0]^=(uint8_t)(1u<<a);
}
return false;
}
static char clean(uint8_t c){
c&=0x7Fu;
if(c=='\r'||c=='\n') return '\n';
return (c>=32u&&c<127u)?(char)c:' ';
}
static void copyField(char *out,uint8_t pos,uint8_t n){
uint8_t k=0;
while(k<n&&pos+k<g.len){ out[k]=clean(g.frame[pos+k]); k++; }
while(k<n) out[k++]=' ';
out[n]='\0';
}
static void accept(void){
if(g.len<13u) return;
message_t *m=g.history[HISTORY-1u];
for(uint8_t n=HISTORY-1u;n;n--) g.history[n]=g.history[n-1u];
g.history[0]=m;
copyField(m->address,1,7);
if(m->address[0]=='.'){
for(uint8_t n=0;n<6u;n++) m->address[n]=m->address[n+1u];
m->address[6]='\0';
}
copyField(m->label,9,2);
uint8_t block=(uint8_t)(g.frame[11]&0x7Fu);
m->down=(uint8_t)(block>='0'&&block<='9');
if(m->down&&g.len>=23u) copyField(m->flight,17,6);
else{ for(uint8_t n=0;n<6u;n++) m->flight[n]=' '; m->flight[6]='\0'; }
uint8_t out=0;
/* Downlinks prefix their text with a four-character sequence number and
* the six-character flight ID already shown in the header. Short blocks
* have no such prefix, so keep their payload from immediately after STX. */
uint8_t start=m->down&&g.len>=23u?23u:13u;
for(uint8_t n=start;n+1u<g.len&&out<MESSAGE_MAX;n++){
char c=clean(g.frame[n]);
if(c=='\n'&&out&&m->text[out-1u]=='\n') continue;
m->text[out++]=c;
}
while(out&&m->text[out-1u]==' ') out--;
m->text[out]='\0';
m->textLen=out;
m->sublabel[0]=m->mfi[0]='\0';
m->appAt=0;
if(m->label[0]=='H'&&m->label[1]=='1'){
if(m->down&&out>=4u&&m->text[0]=='#'&&m->text[3]=='B'){
m->sublabel[0]=m->text[1]; m->sublabel[1]=m->text[2];
m->sublabel[2]='\0'; m->appAt=4u;
}else if(!m->down&&out>=5u&&m->text[0]=='-'&&m->text[1]==' '&&m->text[2]=='#'){
m->sublabel[0]=m->text[3]; m->sublabel[1]=m->text[4];
m->sublabel[2]='\0'; m->appAt=5u;
}
if(m->appAt&&out>=m->appAt+4u&&m->text[m->appAt]=='/'&&m->text[m->appAt+3u]==' '){
m->mfi[0]=m->text[m->appAt+1u]; m->mfi[1]=m->text[m->appAt+2u];
m->mfi[2]='\0'; m->appAt+=4u;
}
}
m->rssi=g.A->rssi_dbm();
if(g.count<HISTORY) g.count++;
g.cur=g.top=0;
g.nOk++;
g.redraw|=REDRAW_FRAME;
}
static void resetDecoder(void){
g.state=PREKEY;
g.bits=8u;
g.prekey=0;
}
static void finishFrame(uint8_t byte){
g.crcBytes[1]=byte;
bool ok=(g.parityErrors==0u&&frameCrc()==0u);
if(!ok&&repair()){ ok=true; g.corrected++; }
if(ok) accept();
else if(g.parityErrors) g.parityFails++;
else g.crcFails++;
g.state=END;
}
/* Bytes after SYN SYN, already byte-aligned and in true polarity. */
static void decodedByte(uint8_t byte){
if(g.state==SOH1){
if(byte!=0x01u){ resetDecoder(); return; }
g.state=TEXT;
g.len=0;
g.parityErrors=0;
g.syncs++;
return;
}
if(g.state==TEXT){
if(g.len>=FRAME_MAX){ resetDecoder(); return; }
g.frame[g.len]=byte;
if(!odd(byte)){
if(g.parityErrors<2u) g.parityAt[g.parityErrors]=g.len;
g.parityErrors++;
}
g.len++;
if(byte==0x83u||byte==0x97u) g.state=CRC1;
return;
}
if(g.state==CRC1){ g.crcBytes[0]=byte; g.state=CRC2; return; }
if(g.state==CRC2){ finishFrame(byte); return; }
resetDecoder();
}
/* ---- integer MSK receiver ---- */
static int32_t absi(int32_t v){ return v<0?-v:v; }
static void pllUpdate(int32_t vo,int32_t error){
dem_t *d=g.dem;
int32_t av=absi(vo)>>8,ae=absi(error)>>8;
int32_t sign=error<0?-1:1;
int32_t p;
if(ae>(av<<1)) p=40;
else if(ae>av) p=28;
else if((ae<<1)>av) p=16;
else if((ae<<2)>av) p=6;
else p=0;
if(error==0) sign=0;
/* The RC oscillator may be 1 % off: the integrator has to find up to
* 18 Hz within the pre-key, then only follow it through the text. */
int32_t integral=p?TRACK_I:0;
if(g.state<TEXT){ integral=p*ACQ_I; p*=ACQ_GAIN; }
d->phase=(uint32_t)(d->phase+sign*p);
if(g.prekey>=8||g.prekey<=-8||g.state!=PREKEY){
d->pllI+=sign*integral;
if(d->pllI>PLL_I_LIMIT) d->pllI=PLL_I_LIMIT;
if(d->pllI<-PLL_I_LIMIT) d->pllI=-PLL_I_LIMIT;
}else d->pllI=0;
}
static void decision(void){
dem_t *d=g.dem;
int32_t i=0,q=0;
uint8_t slot=d->ringAt;
for(uint8_t n=0;n<16u;n++){
iq_t *v=&d->ring[slot];
i+=(int32_t)d->taps[n]*v->i;
q+=(int32_t)d->taps[n]*v->q;
slot=(uint8_t)((slot+1u)&15u);
}
int32_t vo=i,xq=q,error;
if(d->symbol&1u){ vo=q; xq=i; error=vo>=0?-i:i; }
else error=vo>=0?q:-q;
bool bit=(d->symbol&2u)?vo<0:vo>0;
/* Bit clock. The quadrature rail of the previous decision sits between
* the symbols before and after it. When those two differ (equal bits: the
* rail sign alternates) its sign tells an early clock from a late one. */
if(((g.shift>>14)&1u)==bit){
int32_t step=g.state<TEXT?TIMING_ACQ:TIMING_TRACK;
d->clock+=(d->xq^vo)<0?-step:step;
}
d->xq=xq;
pllUpdate(vo,error);
d->symbol++;
g.shift=(uint16_t)((g.shift>>1)|(bit?0x8000u:0u));
if(g.state==PREKEY){
/* Leaky run counter: noise decays to zero and leaves the keys and the
* screen alone, the pre-key tone ramps up in either polarity. */
int32_t run=bit?1:-1;
if((g.prekey^run)<0) run*=PREKEY_LEAK;
run+=g.prekey;
if(run>=-127&&run<=127) g.prekey=(int8_t)run;
/* Every bit alignment is searched; the bit clock has had + and * to
* settle. An inverted match only flips the bytes that follow. */
if(g.shift==SYNC_WORD||g.shift==SYNC_INVERTED){
g.inv=(uint8_t)-(g.shift&1u);
g.state=SOH1;
g.bits=8u;
}
return;
}
if(--g.bits==0u){ g.bits=8u; decodedByte((uint8_t)((g.shift>>8)^g.inv)); }
}
static void sample(uint16_t raw){
dem_t *d=g.dem;
if(raw<g.adcMin) g.adcMin=raw;
if(raw>g.adcMax) g.adcMax=raw;
d->dc+=(((int32_t)raw<<4)-d->dc)>>6;
int32_t x=(int32_t)raw-(d->dc>>4);
int32_t step=(int32_t)PHASE_NOMINAL+(d->pllI>>4);
d->clock+=step;
if(d->clock>=BIT_PHASE){ d->clock-=BIT_PHASE; decision(); }
d->phase=(uint32_t)(d->phase+step)&0xFFFFu;
uint8_t phase=(uint8_t)(d->phase>>10);
d->ring[d->ringAt].i=x*d->cosine[phase];
d->ring[d->ringAt].q=x*d->cosine[(phase+16u)&63u];
d->ringAt=(uint8_t)((d->ringAt+1u)&15u);
}
/* ---- screen ---- */
static bool pair(const char *s,char a,char b){ return s[0]==a&&s[1]==b; }
static uint8_t appType(const message_t *m){
const char *id=m->mfi[0]?m->mfi:m->label;
if(pair(id,'A','6')||pair(id,'A','A')||pair(id,'B','6')||pair(id,'B','A'))
return APP_ARINC622;
if(pair(m->label,'Q','0')) return APP_LINK;
if(pair(m->label,'S','A')) return APP_MEDIA;
if(pair(m->label,'M','A')) return APP_MIAM;
if(pair(m->label,'H','1')) return APP_TERMINAL;
return APP_UNKNOWN;
}
static uint8_t buildRows(const message_t *m,char *body,const char *ui){
char *o=body;
uint8_t type=appType(m);
*o++=m->label[0]; *o++=m->label[1]; *o++=' ';
if(g.raw||type==APP_UNKNOWN) o=put(o,m->text);
else{
o=put(o,m->down?ui+T_DOWN:ui+T_UP); *o++='\n';
if(m->sublabel[0]){
o=put(o,ui+T_SUB); *o++=m->sublabel[0]; *o++=m->sublabel[1];
if(m->mfi[0]){ o=put(o,ui+T_MFI); *o++=m->mfi[0]; *o++=m->mfi[1]; }
*o++='\n';
}
const char *name=ui+T_TERMINAL;
if(type==APP_ARINC622) name=ui+T_ARINC622;
else if(type==APP_LINK) name=ui+T_LINK;
else if(type==APP_MEDIA) name=ui+T_MEDIA;
else if(type==APP_MIAM) name=ui+T_MIAM;
o=put(o,name); *o++='\n';
o=put(o,ui+T_DATA); o=put(o,m->text+m->appAt);
}
*o='\0';
uint8_t width=(uint8_t)(BODY_CHARS+(g.view?COMPACT_EXTRA:0u));
uint8_t rows=0,col=0;
for(char *p=body;;p++){
if(*p=='\n'||*p=='\0'||++col==width){
rows++; col=0;
if(*p=='\0') break;
}
}
return rows;
}
static void drawBody(char *body){
char row[BODY_CHARS+COMPACT_EXTRA+1u];
uint8_t width=(uint8_t)(BODY_CHARS+(g.view?COMPACT_EXTRA:0u));
uint8_t visible=(uint8_t)(BODY_ROWS+(g.view?1u:0u));
uint8_t line=0,col=0,shown=0;
for(char *p=body;;p++){
char c=*p;
if(c!='\n'&&c!='\0'&&col<width) row[col++]=c;
if(c=='\n'||c=='\0'||col==width){
if(line>=g.top&&shown<visible){
row[col]='\0';
if(g.view) g.A->print_tiny(row,0,(uint8_t)(8u+shown*6u),false,true);
else g.A->print_normal(row,0,0,(uint8_t)(shown+1u));
shown++;
}
line++; col=0;
if(c=='\0') break;
}
}
}
static void separator(void){
uint8_t *b=g.A->fb[0];
for(unsigned x=0;x<128u;x+=2u){ b[384u+x]|=0x80u; b[385u+x]&=0x7Fu; }
}
static void draw(void){
const app_api_t *A=g.A;
char ui[UI_SIZE] __attribute__((aligned(4)));
char text[MESSAGE_MAX+64u];
A->asset_read(0,ui,UI_SIZE);
A->display_clear();
A->status_clear();
A->print_inverse(ui+T_TITLE,2,0,true,true,(uint8_t)(2u+T_TITLE_CHARS*4u));
A->draw_battery();
if(!g.count){
A->print_inverse(ui+T_WAIT,WAIT_CAPS_X,0,true,true,
(uint8_t)(WAIT_CAPS_X+T_WAIT_CHARS*4u));
}else{
message_t *m=g.history[g.cur];
A->print_bold(m->address,0,67,0);
A->print_bold(m->flight,69,g.count>1u?110u:127u,0);
A->print_inverse(g.raw?ui+T_RAW:ui+T_DEC,WAIT_CAPS_X,0,true,true,
(uint8_t)(WAIT_CAPS_X+3u*4u));
uint8_t rows=buildRows(m,text,ui);
uint8_t visible=(uint8_t)(BODY_ROWS+(g.view?1u:0u));
g.lim=rows>visible?(uint8_t)(rows-visible):0u;
drawBody(text);
}
unsigned tail=g.spk;
if(g.top||g.cur) tail+=2u;
if(g.top<g.lim||g.cur+1u<g.count) tail+=4u;
A->asset_read((uint16_t)(BMP_TAIL+tail*TAIL_W),A->status_line+SPK_X,TAIL_W);
if(g.count>1u){
char *o=text;
*o++=(char)('1'+g.cur); *o++='/'; *o++=(char)('0'+g.count); *o='\0';
A->print_inverse(text,CAPS_END-12u,0,false,true,CAPS_END);
}
separator();
drawFreq(A->rx_freq(),text);
if(g.count){
message_t *m=g.history[g.cur];
char *o=puti(text,m->rssi); o=put(o,ui+T_DBM);
o=put(o,ui+T_RX); o=puti(o,g.nOk);
o=put(o,ui+T_FIX); o=puti(o,g.corrected);
*o='\0';
A->print_tiny(text,0,49,false,true);
}else{
char *o=put(text,ui+T_ADC); o=puti(o,g.ppShown);
o=put(o,ui+T_SYNC); o=putStat(o,g.syncs);
o=put(o,ui+T_CRC); o=putStat(o,g.crcFails);
o=put(o,ui+T_PAR); o=putStat(o,g.parityFails); *o='\0';
A->print_tiny(text,0,49,false,true);
}
}
static void handleKeys(void){
uint8_t key=g.A->get_key();
int d=(key==APP_KEY_DOWN)-(key==APP_KEY_UP);
unsigned top=(unsigned)(g.top+d);
if(d&&top<=g.lim){
if(top!=g.top){ g.top=(uint8_t)top; g.redraw|=REDRAW_ON; }
}else if(d&&key!=g.prevKey&&(unsigned)(g.cur+d)<g.count){
g.cur=(uint8_t)(g.cur+d);
g.top=0;
g.redraw|=REDRAW_ON;
}
if(key==APP_KEY_INVALID||key==g.prevKey){ g.prevKey=key; return; }
g.prevKey=key;
g.redraw|=REDRAW_ON;
if(key==APP_KEY_EXIT) g.running=false;
else if(key==APP_KEY_1) g.spk^=1u;
else if(key==APP_KEY_STAR){ g.view^=VIEW_COMPACT; g.top=0; }
else if(key==APP_KEY_F){ g.raw^=1u; g.top=0; }
else if(key==APP_KEY_2){
g.count=g.cur=g.top=g.lim=0;
g.syncs=g.crcFails=g.parityFails=g.corrected=g.nOk=0;
}
}
static void house(void){
const app_api_t *A=g.A;
uint16_t pp=(uint16_t)(g.adcMax-g.adcMin);
if(pp>g.ppNow) g.ppNow=pp;
g.adcMin=0xFFFFu;
g.adcMax=0;
if(g.redraw&REDRAW_FRAME) A->backlight_on();
A->backlight_update();
if(++g.refresh>=REFRESH_SLOTS){
g.refresh=0;
g.ppShown=g.ppNow; g.ppNow=0;
g.redraw|=REDRAW_ON;
}
if(!g.redraw) return;
g.redraw=0;
adcRestore();
A->battery_sample();
adcSelect();
draw();
A->blit_status();
A->blit_full();
}
/* ---- continuous receiver ---- */
static void listen(void){
adcSelect();
clkStart();
resetDecoder();
uint32_t next=0;
uint16_t n=0;
g.redraw=REDRAW_ON;
g.adcMin=0xFFFFu;
while(g.running){
while((int32_t)(clkNow()-next)<0){}
next+=SAMPLE_CYC;
sample(adcRead());
if(++n<HOUSE_EVERY) continue;
n=0;
/* Screen and battery work discard enough samples to lose the short
* pre-key, so they wait while a carrier is up. The key scan costs
* about four samples: harmless on a carrier lead, fatal inside a
* block, so it only waits while the modem itself is working, and
* never longer than BUSY_MAX. A key is answered at once, even on a
* channel whose squelch stays open. */
bool modem=g.state!=PREKEY||g.prekey>=16||g.prekey<=-16;
bool busy=true;
if(!modem||g.busyFor+1u>=BUSY_MAX){
if(!g.rfHold&&(g.A->bk_read(0x0Cu)&2u)) g.rfHold=RF_HOLD_SLOTS;
handleKeys();
busy=!modem&&g.rfHold&&!g.redraw;
}
if(g.rfHold) g.rfHold--;
/* Open the speaker only after the complete ACARS header, then hold
* it across short OOK dropouts. */
if(g.state>=TEXT) g.audioHold=4u;
bool audible=g.audioHold!=0u;
if(g.audioHold) g.audioHold--;
uint8_t audio=(uint8_t)(g.spk&&audible);
if(audio!=g.audioOn){ g.A->audio_path(audio); g.audioOn=audio; }
if(busy&&++g.busyFor<BUSY_MAX) continue;
g.busyFor=0;
house();
next=clkNow();
}
adcRestore();
}
__attribute__((section(".text.entry"),used))
void app_main(const app_api_t *api){
message_t frames[HISTORY];
message_t *history[HISTORY];
uint8_t frame[FRAME_MAX];
dem_t dem;
for(uint8_t n=0;n<HISTORY;n++) history[n]=&frames[n];
memset(&dem,0,sizeof dem);
dem.dc=(int32_t)BIAS_CODE<<4;
_Static_assert(TAPS==COSINE+COSINE_LEN,
"MSK cosine and matched-filter assets must stay contiguous");
api->asset_read(COSINE,dem.cosine,(uint16_t)(COSINE_LEN+TAPS_LEN));
g.A=api;
g.history=history;
g.frame=frame;
g.dem=&dem;
g.prevKey=APP_KEY_INVALID;
g.savedSqr3=ADC_SQR3;
g.savedSmpr3=ADC_SMPR3;
const uint32_t moder=GPIOA_MODER,dac=DAC_CR,dhr=DAC_DHR12R1,rcc=RCC_APBENR1;
biasOn(moder,rcc);
api->cfg_load(&g.spk,2);
g.spk=(uint8_t)(g.spk==1u);
if(g.view!=VIEW_COMPACT) g.view=0;
api->set_af(APP_AF_AM);
api->delay_ms(50);
api->backlight_on();
g.running=true;
listen();
biasOff(dac,dhr,rcc,moder);
api->set_af(APP_AF_MUTE);
api->audio_path(false);
api->cfg_save(&g.spk,2);
}
+16
View File
@@ -0,0 +1,16 @@
/* ACARS RX runs in the same 4 KiB sector-cache overlay as APRS RX. */
APP_VMA = DEFINED(APP_VMA) ? APP_VMA : 0x20000280;
APP_LENGTH = 0x1000;
ENTRY(app_main)
MEMORY { APP (rwx) : ORIGIN = APP_VMA, LENGTH = APP_LENGTH }
SECTIONS {
.app APP_VMA : {
KEEP(*(.text.entry)) *(.text .text.*) *(.rodata .rodata.*)
. = ALIGN(4); *(.data .data.*) . = ALIGN(4);
__app_bss_start = .; *(.bss .bss.* COMMON) . = ALIGN(4); __app_bss_end = .;
} > APP
__app_end = .;
ASSERT(__app_end <= APP_VMA + APP_LENGTH,
"ACARS RX overlay exceeds 4 KiB")
/DISCARD/ : { *(.ARM.exidx*) *(.ARM.extab*) *(.eh_frame*) *(.comment) *(.note.*) }
}
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Build one ACARS RX overlay app. Invoked by ../../../compile-app.sh inside the
# uvk1-uvk5v3 Docker image, from this directory.
set -euo pipefail
APP="$(basename "$PWD")"
APP_NAME="ACARS RX"
APP_VER="0.1"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280}
OUT="${APP_NAME// /}"
CC=/opt/toolchain/bin/arm-none-eabi-gcc
OBJCOPY=/opt/toolchain/bin/arm-none-eabi-objcopy
command -v arm-none-eabi-gcc >/dev/null 2>&1 && { CC=arm-none-eabi-gcc; OBJCOPY=arm-none-eabi-objcopy; }
CFLAGS="-mcpu=cortex-m0plus -mthumb -Oz -fno-jump-tables -std=gnu11 -ffreestanding -fno-builtin -fno-common -fomit-frame-pointer -ffunction-sections -fdata-sections -Wall -Wextra"
LDFLAGS="-nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=${APP_VMA} -Wl,--gc-sections -Wl,-Map=${APP}.map -Wl,--build-id=none -Wl,--no-warn-rwx-segments"
rm -f ./*.app ./*.elf ./*.bin
step() { printf '\r 🔨 %-13s [%d/4] %-8s' "$APP_NAME" "$1" "$2"; }
trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR
step 1 assets ; APP_VER="$APP_VER" python3 ./gen_assets.py "${APP}_assets.bin" "${APP}_assets.h"
step 2 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf" \
|| { "$CC" $CFLAGS -c "${APP}_app.c" -o "${APP}.o"; false; }
step 3 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
step 4 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \
--name "$APP_NAME" --ver "$APP_VER" --api-min "$APP_API_MIN" \
--vma "${APP_VMA}" --shortcut none --assets "${APP}_assets.bin" >/dev/null
trap - ERR
BYTES=$(wc -c < "${APP}.bin")
printf '\r ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app \n' \
"$APP_NAME" "$BYTES" "$(( BYTES * 100 / 4096 ))" "$OUT"
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
# ACARS RX read-only assets: UI text, status icons and MSK filter tables.
import math
import os
import sys
sys.dont_write_bytecode = True
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, ".."))
from app_assets import Assets
TITLE = "ACARS RX"
WAIT = "WAIT"
UI = [
("T_TITLE", TITLE),
("T_WAIT", WAIT),
("T_ADC", "ADC "),
("T_SYNC", " SYN "),
("T_CRC", " CRC "),
("T_PAR", " PAR "),
("T_DBM", "dBm"),
("T_RX", " RX "),
("T_FIX", " FIX "),
("T_DEC", "DEC"),
("T_RAW", "RAW"),
("T_DOWN", "DOWN"),
("T_UP", "UP"),
("T_SUB", "SUB "),
("T_MFI", " MFI "),
("T_DATA", "DATA "),
("T_TERMINAL", "TERMINAL DATA"),
("T_ARINC622", "ARINC 622"),
("T_LINK", "LINK TEST"),
("T_MEDIA", "MEDIA ADVISORY"),
("T_MIAM", "MIAM"),
]
a = Assets("ACARSRX")
ui_size = 0
for name, text in UI:
pad = -(len(text) + 1) % 4
a.text(name, text + "\0" * pad)
ui_size += len(text) + 1 + pad
a.const("T_TITLE_CHARS", len(TITLE))
a.const("T_WAIT_CHARS", len(WAIT))
a.const("UI_SIZE", ui_size)
# Speaker plus scroll marks, indexed by speaker/up/down bits as in APRS RX.
speaker = [0x1C, 0x1C, 0x3E, 0x7F, 0x00, 0x22, 0x1C, 0x41, 0x22, 0x1C]
up = [0x04, 0x06, 0x07, 0x06, 0x04]
down = [0x10, 0x30, 0x70, 0x30, 0x10]
tail = []
for index in range(8):
tail += speaker if index & 1 else [0] * len(speaker)
tail += [0] * 3
tail += [(u if index & 2 else 0) | (d if index & 4 else 0)
for u, d in zip(up, down)]
a.u8("BMP_TAIL", tail)
a.const("TAIL_W", len(tail) // 8)
# 64-step 1800 Hz mixer table and the 16 samples of sin(pi*n/16) used by
# ACARSDEC's one-1200-Hz-period half-sine matched filter. Keep them contiguous:
# app_main loads both into dem_t with one asset read.
a.i8("COSINE", [round(127 * math.cos(2 * math.pi * n / 64)) for n in range(64)])
a.u8("TAPS", [round(127 * math.sin(math.pi * n / 16)) for n in range(16)])
if __name__ == "__main__":
a.main()
@@ -0,0 +1,8 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 100139 -278 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 417 -417 416 -417 417 -416 209 -208 417 -208 208 -209 208 -417 416 -209 208 -208 209 -416 417 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 416 -417 417 -416 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 417 -416 417 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -416 417 -208 209 -416 209 -208 417 -208 208 -417 208 -209 416 -209 208 -417 208 -208 417 -417 416 -209 208 -417 416 -417 208 -209 416 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -417 208 -208 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 416 -209 208 -208 209 -208 208 -417 417 -208 208 -417 417 -208 208 -417 417 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 208 -209 416 -417 417 -416 417 -417 208 -208 209 -208 208 -209 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -417
RAW_Data: 208 -208 209 -208 417 -208 208 -417 208 -209 416 -417 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 208 -209 416 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -208 208 -417 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -416 417 -417 416 -417 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 208 -208 417 -208 209 -416 417 -208 209 -416 417 -208 209 -208 208 -417 417 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -416 209 -208 417 -208 208 -417 417 -416 209 -208 208 -209 208 -208 417 -208 209 -208 208 -209 208 -417 416 -209 208 -417 416 -209 208 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -417 416 -417 417 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -208 208 -417 208 -209 208 -208 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 208 -208 209 -208 417 -416 209 -208 417 -208 208 -417 208 -209 416 -417 208 -209 208 -208 417 -417 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 417 -278 20556
@@ -0,0 +1,8 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 100139 -278 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 417 -417 416 -417 417 -416 209 -208 417 -208 208 -209 208 -417 416 -209 208 -208 209 -416 417 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 416 -417 417 -416 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 417 -416 417 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -416 417 -208 209 -416 209 -208 417 -208 208 -417 208 -209 416 -209 208 -417 208 -208 417 -417 416 -209 208 -417 416 -417 208 -209 416 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -417 416 -209 208 -208 209 -416 209 -208 417 -208 208 -417 417 -416 417 -208 209 -416 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -417 208 -209 208 -208 417 -417 416 -209 208 -208 209 -416 417 -208 209 -416 417 -208 209 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -417 417 -416 209 -208 417 -416 209 -208 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -417 416 -209 208 -417 416 -417 417 -416 209 -208 208 -209 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 208 -208 209 -208 417 -208 208 -209 208 -208 209 -416 417 -417 416 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -417 208 -208 417 -417 416 -209 208 -417 416 -417 417 -416 209 -208 417 -208 208 -417 417 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -416 417 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 416 -417 417 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -417 417 -416 417 -417 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 417 -278 20556
@@ -0,0 +1,8 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 100139 -278 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 417 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -417 417 -416 209 -208 208 -209 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -417 208 -209 416 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 417 -416 417 -208 209 -208 208 -209 208 -417 208 -208 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -416 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -417 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -416 417 -417 416 -417 417 -208 208 -417 417 -208 208 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -209 208 -417 208 -208 417 -208 209 -416 417 -417 208 -208 209 -208 208 -209 416 -209 208 -417 416 -417 208 -209 416 -209 208 -208 209 -208 208 -417 417 -416 209 -208 417 -416 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 417 -416 417 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -416 209 -208 417 -208 208 -417 417 -208 208 -209 208 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -417 416 -417 417 -416
RAW_Data: 417 -417 416 -417 417 -208 208 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -417 208 -208 209 -208 417 -416 417 -208 209 -416 209 -208 417 -416 417 -208 209 -208 208 -209 208 -417 416 -209 208 -208 209 -416 209 -208 417 -416 417 -417 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 416 -278 20556
@@ -0,0 +1,8 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 100139 -278 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 417 -417 416 -417 417 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -416 209 -208 417 -416 209 -208 417 -416 417 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 417 -208 208 -417 208 -209 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 417 -208 208 -417 417 -416 209 -208 417 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -417 208 -208 209 -208 208 -209 208 -208 209 -208 417 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -416 209 -208 417 -416 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 208 -209 208 -208 417 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -417 416 -209 208 -208 209 -416 209 -208 417 -208 208 -417 417 -208 208 -417 208 -209 416 -417 208 -209 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208
RAW_Data: 208 -209 416 -417 417 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -416 209 -208 208 -209 208 -208 417 -208 209 -416 417 -417 208 -208 417 -417 208 -208 209 -208 417 -416 417 -417 416 -209 208 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 208 -209 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 416 -278 20556
@@ -0,0 +1,10 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 100139 -278 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 417 -417 416 -417 417 -208 208 -417 208 -209 416 -209 208 -417 416 -417 208 -209 208 -208 209 -208 417 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -416 209 -208 208 -209 208 -208 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -417 416 -417 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -417 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 417 -208 208 -209 208 -417 416 -417 208 -209 416 -209 208 -417 208 -208 417 -208 209 -416 209 -208 417 -416 417 -417 416 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -208 208 -417 417 -416 417 -417 416 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -416 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -417 208 -208 209 -208 208 -209 416 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -208 208 -417 208 -209 208 -208 209 -208 208 -209 416 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 208 -208 209 -208 417 -208 208 -209 208 -208 209 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -417 416 -417 417 -416 209 -208 417 -208 208 -417
RAW_Data: 417 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 417 -416 209 -208 417 -208 208 -417 208 -209 208 -208 417 -417 416 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 209 -208 208 -209 416 -209 208 -417 416 -417 208 -209 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 208 -208 209 -208 417 -208 208 -209 208 -417 208 -208 209 -208 417 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -417 416 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -417 208 -209 416 -417 417 -208 208 -417 208 -209 208 -208 209 -208 417 -208 208 -417 417 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -417 208 -209 208 -208 417 -208 209 -416 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -417 208 -209 416 -209 208 -417 208 -208 417 -208 209 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 209 -208 417 -208 208 -417 208 -209 416 -417 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -416 417 -417 416 -209 208 -417 416 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 416 -417 208 -209 208 -208 417 -208 209 -416 417 -208 209 -416 417 -208 209 -416 209 -208 208 -209 416 -209 208 -417 208 -208 209 -208 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 209 -208 417 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 416 -209 208 -417 416 -417 208 -209 208 -208 209 -208 417 -208 208 -209 208 -208 209 -416 417 -208 209 -416 417 -208 209 -416 417 -417 416 -417 208 -209 208 -208 209 -208 417 -208 208 -417 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208
RAW_Data: 209 -208 208 -417 208 -209 416 -209 208 -417 416 -417 417 -208 208 -417 417 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -416 209 -208 208 -209 208 -208 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -416 209 -208 208 -209 208 -208 417 -208 209 -416 417 -208 209 -416 209 -208 417 -416 417 -417 208 -208 209 -208 417 -416 417 -208 209 -208 208 -417 417 -208 208 -417 417 -208 208 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 416 -209 208 -417 416 -417 208 -209 208 -208 417 -417 416 -209 208 -208 209 -416 417 -208 209 -416 209 -208 417 -416 417 -417 208 -208 209 -208 417 -416 417 -208 209 -208 208 -417 417 -208 208 -417 417 -208 208 -417 417 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -208 209 -416 417 -208 209 -416 417 -417 416 -417 208 -209 208 -208 209 -208 417 -208 208 -417 417 -416 209 -208 208 -209 416 -417 417 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -416 209 -208 208 -209 208 -208 417 -208 209 -416 417 -208 209 -416 209 -208 208 -209 416 -209 208 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -208 209 -416 417 -208 209 -416 417 -417 416 -417 417 -208 208 -209 208 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -417 416 -209 208 -417 416 -417 208 -209 208 -208 209 -208 417 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -416 417 -417 208 -208 209 -208 208 -209 416 -209 208 -208 209 -208 208 -417 417 -208 208 -417 417 -208 208 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 416 -417 417 -416 417 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -416 209 -208 208 -209 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -209
RAW_Data: 208 -417 416 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -209 208 -417 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 416 -278 20556
@@ -0,0 +1,8 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: RAW
RAW_Data: 100139 -278 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -417 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 208 -208 417 -417 416 -417 417 -416 209 -208 417 -208 208 -209 208 -417 416 -209 208 -208 209 -416 417 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 208 -208 417 -208 209 -416 417 -417 416 -417 417 -416 417 -208 209 -208 208 -417 208 -209 416 -417 208 -209 416 -417 417 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -417 208 -209 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -417 208 -208 417 -208 209 -416 209 -208 417 -208 208 -209 208 -208 209 -416 209 -208 417 -416 209 -208 417 -208 208 -209 208 -417 208 -208 417 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -417 417 -416 417 -208 209 -208 208 -417 208 -209 416 -209 208 -208 209 -416 417 -208 209 -416 209 -208 417 -208 208 -417 208 -209 416 -209 208 -417 208 -208 417 -417 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417 208 -208 209 -208 208 -209 416 -209 208 -417 416 -209 208 -417 208 -208 209 -208 417 -208 208 -417 208 -209 208 -208 417 -417 416 -209 208 -208 209 -416 417 -208 209 -208 208 -209 208 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -417 208 -209 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 417 -416 417 -208 209 -416 417 -417 416 -417 417 -208 208 -417 417 -208 208 -417 417 -208 208 -209 208 -417 416 -417 417 -416 417 -417 416 -209 208 -417 208 -208 417 -417 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -417 416 -209 208 -417 416 -209 208 -208 209 -416 417 -417 416 -417 417 -208 208 -209 208 -208 209 -208 208 -209 208 -417 416 -209 208 -417 416 -417 208 -209 416 -209 208 -417
RAW_Data: 208 -208 209 -208 417 -416 417 -417 416 -417 208 -209 208 -208 209 -208 417 -208 208 -417 417 -416 209 -208 417 -208 208 -417 417 -416 417 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -208 209 -416 417 -208 209 -208 208 -417 417 -208 208 -417 417 -416 417 -417 416 -417 417 -208 208 -209 208 -417 416 -209 208 -208 209 -208 208 -209 208 -417 416 -417 208 -209 208 -208 417 -208 209 -208 208 -209 208 -208 209 -416 209 -208 208 -209 416 -209 208 -417 416 -209 208 -417 416 -209 208 -417 416 -209 208 -417 416 -417 417 -416 417 -417 416 -209 208 -208 209 -416 417 -208 209 -208 208 -417 417 -208 208 -209 208 -417 208 -208 209 -208 208 -209 416 -209 208 -208 209 -208 208 -209 208 -417 208 -208 417 -208 209 -208 208 -417 208 -209 416 -209 208 -417 208 -208 417 -208 209 -208 208 -417 417 -416 209 -208 208 -209 208 -208 209 -208 208 -209 208 -208 209 -208 417 -277 20556
+266
View File
@@ -0,0 +1,266 @@
#!/usr/bin/env python3
"""Generate Flipper Zero Sub-GHz RAW files for ACARS RX bench tests.
The stock Flipper cannot transmit in the 118-137 MHz aeronautical band. These
files instead use 433.650 MHz OOK: the carrier is keyed with the sign of the
ACARS 1200/2400 Hz MSK waveform. An AM receiver recovers a square-wave version
of the audio, whose fundamental is handled by the ACARS RX matched filter.
Run:
flipper_acars.py [out_dir] [--freq 433650000]
Copy the generated .sub files to subghz/ on the Flipper, tune the radio to the
same frequency in AM, start ACARS RX, and send one file at a time.
"""
from __future__ import annotations
import argparse
import math
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from model_rx import (
ETB,
Demodulator,
crc_suffix,
crc_update,
make_frame,
odd_parity,
)
PRESET = "FuriHalSubGhzPresetOok270Async"
DEFAULT_FREQ = 433_650_000
BAUD = 2400
CARRIER = 1800.0
LEAD_US = 100_000
TAIL_US = 20_000
PER_LINE = 512
TEST_PREKEY_BYTES = 32
def bytes_to_bits(data: bytes):
for byte in data:
for bit in range(8):
yield (byte >> bit) & 1
def wire_bits(frame: bytes) -> list[int]:
wire = bytes((0xFF,) * TEST_PREKEY_BYTES) + bytes((0xAB, 0x2A)) + frame
return list(bytes_to_bits(wire))
def coefficients(bits: list[int]) -> list[float]:
"""Staggered I/Q signs expected by the ACARS RX MSK detector."""
return [
(1.0 if bit else -1.0) * (-1.0 if index & 2 else 1.0)
for index, bit in enumerate(bits)
]
def msk_value(coeff: list[float], time_us: float) -> float:
"""Continuous-envelope MSK waveform at a time relative to burst start."""
position = time_us * BAUD / 1_000_000.0
symbol = int(position)
i_value = q_value = 0.0
for pulse in (symbol - 1, symbol):
if not 0 <= pulse < len(coeff):
continue
within = position - pulse
shape = math.sin(math.pi * within / 2.0)
if pulse & 1:
q_value += coeff[pulse] * shape
else:
i_value += coeff[pulse] * shape
phase = 2.0 * math.pi * CARRIER * time_us / 1_000_000.0
return i_value * math.cos(phase) - q_value * math.sin(phase)
def sub_durations(frame: bytes) -> list[int]:
"""Convert an ACARS frame to signed OOK durations in microseconds."""
coeff = coefficients(wire_bits(frame))
burst_us = math.ceil(len(coeff) * 1_000_000 / BAUD + 2_000_000 / BAUD)
edges: list[int] = []
high = True
first_high = msk_value(coeff, 0.5) >= 0.0
if first_high != high:
edges.append(LEAD_US)
high = first_high
for tick in range(1, burst_us + 1):
new_high = msk_value(coeff, tick + 0.5) >= 0.0
if new_high != high:
edges.append(LEAD_US + tick)
high = new_high
end = LEAD_US + burst_us
if not high:
if edges and edges[-1] == end:
edges.pop()
else:
edges.append(end)
end += TAIL_US
points = [0] + edges + [end]
durations: list[int] = []
level = True
for start, stop in zip(points, points[1:]):
duration = stop - start
if duration:
durations.append(duration if level else -duration)
level = not level
return durations
def write_sub(path: str, frame: bytes, frequency: int) -> list[int]:
durations = sub_durations(frame)
lines = [
"Filetype: Flipper SubGhz RAW File",
"Version: 1",
f"Frequency: {frequency}",
f"Preset: {PRESET}",
"Protocol: RAW",
]
for index in range(0, len(durations), PER_LINE):
values = durations[index:index + PER_LINE]
lines.append("RAW_Data: " + " ".join(str(value) for value in values))
with open(path, "w", encoding="ascii") as stream:
stream.write("\n".join(lines) + "\n")
return durations
def etb_frame(**kwargs) -> bytes:
frame = make_frame(**kwargs)
protected = frame[3:-3]
protected = protected[:-1] + bytes((odd_parity(ETB),))
return frame[:3] + protected + crc_suffix(protected) + b"\x7f"
def test_frames() -> dict[str, bytes]:
basic = make_frame(text="FLIPPER ZERO ACARS TEST")
long = make_frame(
address=".LONG01",
label="Q0",
flight="AF4321",
text=(
"THIS LONG ACARS MESSAGE CHECKS DISPLAY WRAPPING AND SCROLLING "
"OVER SEVERAL ROWS ON THE RADIO SCREEN"
),
)
h1 = make_frame(
address=".H1TEST",
label="H1",
flight="AF0622",
text="#M1B/B6 LHWE1YA.ADS.TEST",
)
block = etb_frame(
address=".BLOCK1", label="B1", flight="BA0248", text="ETB BLOCK END TEST"
)
repair = bytearray(make_frame(text="ONE PARITY BIT MUST BE REPAIRED"))
repair[10] ^= 0x80
badcrc = bytearray(make_frame(text="BAD CRC MUST STAY ON WAIT SCREEN"))
badcrc[-3] ^= 0x01
return {
"basic": basic,
"long": long,
"h1_arinc": h1,
"etb": block,
"repair": bytes(repair),
"badcrc": bytes(badcrc),
}
def samples_from_durations(
durations: list[int], sample_phase: float = 0.0
) -> list[int]:
"""Reconstruct ideal AM-detector audio for the host decoder smoke test."""
boundaries = []
elapsed = 0
for duration in durations:
elapsed += abs(duration)
boundaries.append(elapsed)
samples = []
segment = 0
period_us = 1_000_000 / 19_200
count = math.ceil((elapsed - sample_phase) / period_us)
for index in range(count):
time_us = sample_phase + index * period_us
while segment + 1 < len(boundaries) and time_us >= boundaries[segment]:
segment += 1
level = 1 if durations[segment] > 0 else -1
samples.append(2048 + 700 * level)
return samples
def validate(frames: dict[str, bytes]):
for name in ("basic", "long", "h1_arinc", "etb"):
durations = sub_durations(frames[name])
for phase_step in range(8):
sample_phase = phase_step * 1_000_000 / 19_200 / 8
samples = samples_from_durations(durations, sample_phase)
if not Demodulator().run(samples):
raise AssertionError(
f"generated {name} waveform was not decoded at phase {phase_step}"
)
for name in ("repair", "badcrc"):
samples = samples_from_durations(sub_durations(frames[name]))
if Demodulator().run(samples):
raise AssertionError(f"strict host decoder unexpectedly accepted {name}")
repair = bytearray(frames["repair"][3:-3])
repair_crc = frames["repair"][-3:-1]
parity_errors = [
index for index, value in enumerate(repair)
if bin(value).count("1") % 2 == 0
]
if len(parity_errors) != 1:
raise AssertionError("repair frame must contain exactly one parity error")
corrected = False
for bit in range(8):
repair[parity_errors[0]] ^= 1 << bit
crc = 0
for value in repair + repair_crc:
crc = crc_update(crc, value)
if crc == 0:
corrected = True
break
repair[parity_errors[0]] ^= 1 << bit
if not corrected:
raise AssertionError("repair frame cannot be restored to a valid BCS")
badcrc = frames["badcrc"]
crc = 0
for value in badcrc[3:-1]:
crc = crc_update(crc, value)
if crc == 0:
raise AssertionError("badcrc frame unexpectedly has a valid BCS")
def frequency_supported(frequency: int) -> bool:
return any(low <= frequency <= high for low, high in (
(300_000_000, 348_000_000),
(387_000_000, 464_000_000),
(779_000_000, 928_000_000),
))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("out", nargs="?", default="flipper")
parser.add_argument("--freq", type=int, default=DEFAULT_FREQ)
args = parser.parse_args()
if 118_000_000 <= args.freq <= 137_000_000:
sys.exit("refusing the live aeronautical band; use a legal bench frequency")
if not frequency_supported(args.freq):
sys.exit("frequency is outside the Flipper Zero CC1101 operating bands")
frames = test_frames()
validate(frames)
os.makedirs(args.out, exist_ok=True)
for name, frame in frames.items():
path = os.path.join(args.out, f"acars_{name}.sub")
durations = write_sub(path, frame, args.freq)
elapsed_ms = sum(abs(value) for value in durations) / 1000.0
print(f"{path:<34} {elapsed_ms:6.0f} ms {len(durations):5d} edges")
+433
View File
@@ -0,0 +1,433 @@
#!/usr/bin/env python3
"""Host model for the ACARS RX overlay.
The radio path is modelled at 19.2 kHz: DC removal, coherent MSK detection,
carrier/clock tracking, and the ACARS character/CRC state machine. The
synthetic transmitter stays separate from the receiver so the tests exercise
the complete wire format.
"""
from __future__ import annotations
import math
import random
FS = 19_200
BAUD = 2_400
SPB = FS // BAUD
SYN = 0x16
SOH = 0x01
ETX = 0x03
ETB = 0x17
MAX_TEXT = 240
def crc_update(crc: int, byte: int) -> int:
"""ACARS CRC-16, polynomial 0x1021 with each byte entering LSB first."""
for bit in range(8):
top = crc & 0x8000
crc = (crc << 1) & 0xFFFF
if byte & (1 << bit):
crc |= 1
if top:
crc ^= 0x1021
return crc
def odd_parity(value: int) -> int:
value &= 0x7F
if bin(value).count("1") % 2 == 0:
value |= 0x80
return value
def crc_suffix(data: bytes) -> bytes:
crc = 0
for byte in data:
crc = crc_update(crc, byte)
for suffix in range(65_536):
low, high = suffix & 0xFF, suffix >> 8
if crc_update(crc_update(crc, low), high) == 0:
return bytes((low, high))
raise AssertionError("CRC suffix not found")
def make_frame(
address: str = ".FTEST1",
label: str = "H1",
flight: str = "AF1234",
text: str = "PARIS TEST",
) -> bytes:
address = address[:7].ljust(7)
label = label[:2].ljust(2)
flight = flight[:6].ljust(6)
body = "2" + address + "\x15" + label + "A" + "\x02" + "M01A" + flight + text
protected = bytes(odd_parity(ord(ch)) for ch in body) + bytes((odd_parity(ETX),))
return bytes((SYN, SYN, SOH)) + protected + crc_suffix(protected) + b"\x7f"
def bytes_to_bits(data: bytes):
for byte in data:
for bit in range(8):
yield (byte >> bit) & 1
def modulate(
frame: bytes,
*,
noise: float = 0.0,
amplitude: float = 900.0,
dc: float = 2048.0,
phase: float = 0.37,
lead_samples: int = 37,
seed: int = 1,
rate_error: float = 0.0,
carrier_offset: float = 0.0,
) -> list[int]:
"""Generate MSK as staggered I/Q half-sine pulses at 2400 bit/s."""
rng = random.Random(seed)
samples = [round(dc + rng.gauss(0.0, noise)) for _ in range(lead_samples)]
wire = bytes((0xFF,) * 16) + bytes((0xAB, 0x2A)) + frame
bits = list(bytes_to_bits(wire))
coefficients = [
(1.0 if bit else -1.0) * (-1.0 if index & 2 else 1.0)
for index, bit in enumerate(bits)
]
for index in range(len(bits) * SPB + 2 * SPB):
position = index * (1.0 + rate_error)
symbol = int(position / SPB)
i_value = q_value = 0.0
for pulse in (symbol - 1, symbol):
if not 0 <= pulse < len(coefficients):
continue
within = position - pulse * SPB
shape = math.sin(math.pi * within / (2 * SPB))
if pulse & 1:
q_value += coefficients[pulse] * shape
else:
i_value += coefficients[pulse] * shape
carrier_phase = phase + 2.0 * math.pi * (
1800.0 * (1.0 + rate_error) + carrier_offset
) * index / FS
value = dc + amplitude * (
i_value * math.cos(carrier_phase) - q_value * math.sin(carrier_phase)
)
samples.append(round(value + rng.gauss(0.0, noise)))
# Idle tail: the receiver decides two bits late and may sit a bit off.
samples.extend(round(dc + rng.gauss(0.0, noise)) for _ in range(SPB * 32))
return samples
def frame_crc(data) -> int:
crc = 0
for value in data:
crc = crc_update(crc, value)
return crc
def parity_errors(text) -> list[int]:
return [index for index, value in enumerate(text)
if bin(value).count("1") % 2 == 0]
def repair(text: bytearray, crc: bytes) -> bool:
"""Flip one bit in each of at most two parity-marked bytes, as repair()."""
bad = parity_errors(text)
if not 1 <= len(bad) <= 2:
return False
for first in range(8):
text[bad[0]] ^= 1 << first
if len(bad) == 1:
if frame_crc(text + crc) == 0:
return True
else:
for second in range(8):
text[bad[1]] ^= 1 << second
if frame_crc(text + crc) == 0:
return True
text[bad[1]] ^= 1 << second
text[bad[0]] ^= 1 << first
return False
class Demodulator:
"""Integer MSK receiver mirrored by acarsrx_app.c.
`frames` holds the blocks that passed parity and BCS as received (the
strict decoder); `repaired` holds those accepted only after repair().
"""
PREKEY, SOH1, TEXT, CRC1, CRC2, END = range(6)
PHASE_NOMINAL = 6144 # 1800 / 19200 of a 16-bit turn
BIT_PHASE = 49152 # 3/4 turn = one 2400 bit/s symbol
PLL_I_LIMIT = 192 * 16
ACQ_GAIN = 24 # phase gain multiplier before the text
ACQ_I = 3 # integrator step per unit of phase step, acquiring
TRACK_I = 8 # integrator step once in the text
TIMING_ACQ = 1024 # bit clock step (1/6 sample) before the text
TIMING_TRACK = 256
PREKEY_LEAK = 3
SYNC = 0x1616 # SYN SYN, last 16 bits
SYNC_INVERTED = 0xE9E9
def __init__(self):
self.dc = 2048 << 4
self.ring = [(0, 0)] * 16
self.slot = 0
self.phase = 0
self.clock = 0
self.pll_i = 0
self.symbol = 0
self.quadrature = 0
self.cosine = [round(127 * math.cos(2 * math.pi * n / 64)) for n in range(64)]
self.taps = [round(127 * math.sin(math.pi * n / 16)) for n in range(16)]
self.frames: list[bytes] = []
self.repaired: list[bytes] = []
self.shift = 0
self.invert = 0
self._reset_decoder()
def _reset_decoder(self):
self.state = self.PREKEY
self.nbits = 8
self.prekey = 0
self.text = bytearray()
self.crc = bytearray()
def busy(self) -> bool:
"""modemBusy in listen(): screen, battery and keys must wait."""
return self.state != self.PREKEY or abs(self.prekey) >= 16
def _decoded_byte(self, byte: int):
if self.state == self.SOH1:
if byte != SOH:
self._reset_decoder()
return
self.state = self.TEXT
self.text.clear()
self.crc.clear()
return
if self.state == self.TEXT:
if len(self.text) >= MAX_TEXT:
self._reset_decoder()
return
self.text.append(byte)
if byte in (odd_parity(ETX), odd_parity(ETB)):
self.state = self.CRC1
return
if self.state == self.CRC1:
self.crc[:] = bytes((byte,))
self.state = self.CRC2
return
if self.state == self.CRC2:
self.crc.append(byte)
if len(self.text) >= 13:
if frame_crc(self.text + self.crc) == 0 and not parity_errors(self.text):
self.frames.append(bytes(value & 0x7F for value in self.text[:-1]))
elif repair(self.text, bytes(self.crc)):
self.repaired.append(bytes(value & 0x7F for value in self.text[:-1]))
self.state = self.END
return
self._reset_decoder()
def _prekey_bit(self, bit: bool):
"""Leaky run counter: noise decays to zero, the pre-key tone ramps."""
step = 1 if bit else -1
if (self.prekey ^ step) < 0:
step *= self.PREKEY_LEAK
if -127 <= self.prekey + step <= 127:
self.prekey += step
if self.shift in (self.SYNC, self.SYNC_INVERTED):
self.invert = 0xFF if self.shift & 1 else 0
self.state = self.SOH1
self.nbits = 8
def _pll_update(self, value: int, error: int):
av, ae = abs(value) >> 8, abs(error) >> 8
sign = -1 if error < 0 else 1 if error > 0 else 0
if ae > av * 2:
proportional = 40
elif ae > av:
proportional = 28
elif ae * 2 > av:
proportional = 16
elif ae * 4 > av:
proportional = 6
else:
proportional = 0
integral = self.TRACK_I if proportional else 0
if self.state < self.TEXT:
integral = proportional * self.ACQ_I
proportional *= self.ACQ_GAIN
self.phase = (self.phase + sign * proportional) & 0xFFFF
if abs(self.prekey) >= 8 or self.state != self.PREKEY:
self.pll_i += sign * integral
self.pll_i = max(-self.PLL_I_LIMIT, min(self.PLL_I_LIMIT, self.pll_i))
else:
self.pll_i = 0
def _decision(self):
i_value = q_value = 0
for index, tap in enumerate(self.taps):
i_part, q_part = self.ring[(self.slot + index) & 15]
i_value += tap * i_part
q_value += tap * q_part
if self.symbol & 1:
value, other = q_value, i_value
error = -i_value if value >= 0 else i_value
else:
value, other = i_value, q_value
error = q_value if value >= 0 else -q_value
bit = value < 0 if self.symbol & 2 else value > 0
# Symbol timing. The previous decision's quadrature rail sits between
# the symbols before and after it: when those two differ (equal bits,
# the rail sign alternates) its sign tells early from late.
if ((self.shift >> 14) & 1) == int(bit):
step = self.TIMING_ACQ if self.state < self.TEXT else self.TIMING_TRACK
self.clock += -step if (self.quadrature ^ value) < 0 else step
self.quadrature = other
self._pll_update(value, error)
self.symbol = (self.symbol + 1) & 0xFF
self.shift = ((self.shift >> 1) | (int(bit) << 15)) & 0xFFFF
if self.state == self.PREKEY:
self._prekey_bit(bit)
return
self.nbits -= 1
if self.nbits == 0:
self.nbits = 8
self._decoded_byte((self.shift >> 8) ^ self.invert)
def sample(self, sample: int):
self.dc += ((sample << 4) - self.dc) >> 6
value = sample - (self.dc >> 4)
step = self.PHASE_NOMINAL + (self.pll_i >> 4)
self.clock += step
if self.clock >= self.BIT_PHASE:
self.clock -= self.BIT_PHASE
self._decision()
self.phase = (self.phase + step) & 0xFFFF
phase = self.phase >> 10
self.ring[self.slot] = (
value * self.cosine[phase],
value * self.cosine[(phase + 16) & 63],
)
self.slot = (self.slot + 1) & 15
def run(self, samples: list[int]) -> list[bytes]:
for sample in samples:
self.sample(sample)
return self.frames
def test_clean_frame():
decoded = Demodulator().run(modulate(make_frame()))
assert decoded, "clean synthetic frame was not decoded"
assert b"AF1234PARIS TEST" in decoded[0]
return 1
def test_timing_and_phase():
"""Every sample offset of the bit clock against every carrier phase."""
frame = make_frame(address=".FGKXY", label="Q0", text="HELLO FROM PARIS")
cases = 0
for offset in range(SPB):
for step in range(8):
samples = modulate(
frame,
noise=150.0,
amplitude=750.0,
phase=2.0 * math.pi * step / 8,
lead_samples=32 + offset,
seed=offset * 8 + step,
)
decoded = Demodulator().run(samples)
assert decoded, f"offset {offset}, phase step {step} was not decoded"
assert b"HELLO FROM PARIS" in decoded[0]
cases += 1
return cases
def test_sample_clock():
"""The MCU runs from its internal RC oscillator: +/-1.5 % must decode."""
long = make_frame(text="LONG " + "ABCDEFGHIJ0123456789" * 10) + b"\x7f" * 8
cases = 0
for rate in (-0.015, -0.01, -0.005, 0.005, 0.01, 0.015):
for seed in range(4):
samples = modulate(
long,
noise=150.0,
amplitude=750.0,
phase=seed * 0.9,
lead_samples=32 + 2 * seed,
seed=seed,
rate_error=rate,
)
assert Demodulator().run(samples), f"clock {rate:+.1%} seed {seed} failed"
cases += 1
return cases
def test_weak_and_after_noise():
frame = make_frame(text="WEAK SIGNAL")
rng = random.Random(5)
noise = [round(2048 + rng.gauss(0.0, 300.0)) for _ in range(FS)]
cases = 0
for seed in range(8):
weak = modulate(frame, noise=6.0, amplitude=40.0, seed=seed,
phase=seed * 0.7, lead_samples=30 + seed)
assert Demodulator().run(weak), f"weak frame {seed} was not decoded"
after = modulate(frame, noise=150.0, seed=seed, phase=seed * 0.7,
lead_samples=30 + seed)
assert Demodulator().run(noise + after), f"frame {seed} after noise failed"
cases += 2
return cases
def test_crc_rejects_corruption():
frame = bytearray(make_frame())
frame[14] ^= 0x06 # two flips in one byte keep its parity valid
demodulator = Demodulator()
assert not demodulator.run(modulate(bytes(frame)))
assert not demodulator.repaired, "a parity-clean BCS failure must not be repaired"
return 1
def test_repair():
cases = 0
for flips in ((10, 0x80), (20, 0x02), (9, 0x01, 30, 0x40)):
frame = bytearray(make_frame(text="PARITY REPAIR"))
for index in range(0, len(flips), 2):
frame[flips[index]] ^= flips[index + 1]
demodulator = Demodulator()
assert not demodulator.run(modulate(bytes(frame)))
assert demodulator.repaired, f"flips {flips} were not repaired"
assert b"PARITY REPAIR" in demodulator.repaired[0]
cases += 1
return cases
def test_idle_stays_responsive():
"""Without a signal the modem must not claim the slot (keys, screen)."""
cases = 0
for sigma in (0.6, 20.0, 300.0):
rng = random.Random(7)
demodulator = Demodulator()
busy = slots = 0
for index in range(20 * FS):
demodulator.sample(round(2048 + rng.gauss(0.0, sigma)))
if index % 960 == 959:
slots += 1
busy += demodulator.busy()
assert busy * 50 <= slots, f"idle busy {busy}/{slots} at sigma {sigma}"
assert not demodulator.frames and not demodulator.repaired
cases += 1
return cases
if __name__ == "__main__":
total = 0
for test in (test_clean_frame, test_timing_and_phase, test_sample_clock,
test_weak_and_after_noise, test_crc_rejects_corruption,
test_repair, test_idle_stays_responsive):
total += test()
print(f"ACARS RX model: {total}/{total} synthetic cases passed")
+7 -5
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; v0.11 gates the speaker with the squelch |
| 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 |
@@ -24,7 +24,9 @@ Status:
3. The speaker is off by default (v0.6): the decoder does not need it, only the
BK4829 AF output (tested on the radio: PA4 joins the audio before the
amplifier). Key 1 turns it on to listen to the channel, as FoxHunt's audio
key; since v0.8 the app keeps it as you left it.
key; since v0.8 the app keeps it as you left it. Since v0.11 the squelch
(menu SQL) gates the speaker: the frames are heard, not the noise between
them; with SQL 0 the speaker stays open. The decoder is never gated.
Since v0.4 the app decodes **continuously**, as a TNC does: no RSSI trigger,
the FCS and a UI-frame check (control 0x03, PID 0xF0) sort frames from noise.
@@ -54,7 +56,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,10 +64,10 @@ 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 |
| 1 | Speaker on/off, saved on exit (v0.8; off by default), gated by the squelch (v0.11); FoxHunt's speaker icon in the status bar while on |
| 2 | Clear all five frames and the counters |
| EXIT | Quit |
+12 -6
View File
@@ -36,7 +36,8 @@
* Keys (UV-K5 and UV-K1): UP/DOWN (held) scroll the frame 1 px per slot when
* needed; pressed again at either end: the newer / older frame · * normal
* view / compact view (saved) · 1 speaker on/off (saved, off by default: the
* decoder does not need it) · 2 clear · EXIT quit.
* decoder does not need it; while on, the squelch gates it) · 2 clear · EXIT
* quit.
* The loader re-runs RADIO_SetupRegisters on exit; the app restores the ADC,
* PA4, the DAC and its clock itself.
*/
@@ -694,8 +695,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; }
@@ -708,7 +710,7 @@ static void handleKeys(void){
g.redraw|=REDRAW_ON;
if(key==APP_KEY_EXIT) g.running=false;
else if(key==APP_KEY_2){ g.count=g.cur=0; g.nOk=0; g.top=0; } /* g.lim: 0 at the redraw that follows */
else if(key==APP_KEY_1){ g.spk^=1u; A->audio_path(g.spk); } /* speaker, as FoxHunt's audio */
else if(key==APP_KEY_1) g.spk^=1u; /* speaker: listen() gates it at the next slot */
else if(key==APP_KEY_STAR){ g.cw^=CW_COMPACT; g.top=0; } /* the view (saved on exit) */
}
@@ -759,6 +761,11 @@ static void listen(void){
for(uint8_t k=0;k<NSL;k++) slice(&sl[k],a,b);
if(++cnt<HOUSE_EVERY) continue;
cnt=0;
/* Speaker amplifier: on only while key 1 allows it and the squelch is
* open (REG_0C bit 1), so the frames are heard without the noise
* between them. Here, ahead of the busy test: house() is skipped
* during a frame. The decoder is not gated, it reads the AF output. */
A->audio_path(g.spk & (A->bk_read(0x0Cu)>>1));
bool bz=false; /* only the slot's own sample decides */
for(uint8_t k=0;k<NSL;k++) bz|=busy(&sl[k]);
if(bz && ++busyFor<BUSY_MAX) continue;
@@ -793,8 +800,7 @@ void app_main(const app_api_t *api){
/* Speaker amplifier (PA8) as saved, off by default: the decoder does not
* need it (tested on the radio, 2026-09-30): PA4, the voice-prompt DAC pin,
* joins the audio before the amplifier; only the BK4829 AF output must be
* on. Key 1 toggles the speaker to listen to the channel. */
api->audio_path(g.spk);
* on. Key 1 allows the speaker; listen() switches it with the squelch. */
api->set_af(APP_AF_FM);
api->delay_ms(50);
+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.11"
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)
+4 -4
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 frame scrolling direction; v0.7 fixes the editor navigation 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 |
@@ -71,7 +71,7 @@ When SYMB is selected, both shortened help rows remain visible and the edited
| 0-9 | Digit at the cursor, then the next one (on a position digit) |
| * | N/S (latitude line), E/W (longitude line), next SSID 0-15 (SSID), next path (PATH), or next symbol (SYMB) |
| F then * | Previous SSID, path or symbol (the `F` icon in the status bar while F is armed, as in FoxHunt; F again disarms it) |
| UP / DOWN | Move the cursor: 6 digits, N/S, 7 digits, E/W, SSID, PATH, SYMB |
| UP / DOWN | Move the cursor: DOWN (RIGHT on UV-K1) advances through 6 digits, N/S, 7 digits, E/W, SSID, PATH and SYMB; UP (LEFT on UV-K1) moves back |
| MENU | Check (degrees ≤ 90 / 180, minutes < 60) and keep: the frame is rebuilt; `Invalid position` otherwise |
| EXIT | Cancel |
@@ -120,7 +120,7 @@ edited position (south/west) vs `ax25.build`, every path with SSID 0, 15 and 9
and a selected symbol vs `ax25.build`, the config round trip (and a v0.2 config
falling back to the default SSID, path and symbol), the editor rows and bold column, the editor keys (13
digits typed in a row, cursor stops, `*` and F then `*` on each field; the
UP/DOWN keys through `nav_dir()`, with SET_NAV on and off), the position limits,
UP/DOWN editor keys with DOWN/RIGHT moving forward), the position limits,
then
AFSK (continuous phase or reset, tw 0/+4, TX path twist 0/+5 dB) decoded by the
RX model through its RAW and STD audio paths, with and without noise.
+3 -3
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; }
@@ -544,7 +544,7 @@ static void handleKeys(void){
}
else if(key==APP_KEY_EXIT) g.edit=false;
else {
int8_t d=A->nav_dir(key);
int8_t d=(key==APP_KEY_DOWN)-(key==APP_KEY_UP);
if(d<0 && g.cur) g.cur--;
if(d>0 && g.cur<CUR_SYM) g.cur++;
}
+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.7"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
+20 -21
View File
@@ -97,9 +97,9 @@ def nav_dir(key, set_nav):
return d if set_nav else -d
def editor_keys(keys, ed, hemi=0, ssid=7, path=1, sym=5, cur=0, set_nav=True):
def editor_keys(keys, ed, hemi=0, ssid=7, path=1, sym=5, cur=0):
"""aprstx_app.c handleKeys() in the editor: digits 0-9, '*', raw UP 'U' /
DOWN 'D' through nav_dir(), 'F'"""
DOWN 'D', 'F'"""
ed = list(ed)
farm = False
for k in keys:
@@ -122,7 +122,7 @@ def editor_keys(keys, ed, hemi=0, ssid=7, path=1, sym=5, cur=0, set_nav=True):
elif back: sym = sym - 1 if sym else 47
else: sym = sym + 1 if sym < 47 else 0
else:
d = nav_dir(k, set_nav)
d = (k == "D") - (k == "U")
if d < 0 and cur: cur -= 1
if d > 0 and cur < CUR_SYM: cur += 1
return ed, hemi, ssid, path, sym, cur
@@ -257,28 +257,27 @@ def main():
good = [nav_dir(k, sn) for sn in (True, False) for k in "UD*"] == [1, -1, 0, -1, 1, 0]
# 13 digits typed in a row skip N/S and stay on the last digit; the
# navigation keys reach N/S, E/W, SSID and path; '*' acts on the field under
# the cursor. Run for both SET_NAV settings: the raw key moving the cursor
# forward (nav_dir +1) is UP with SET_NAV, DOWN without.
# the cursor. DOWN / RIGHT moves forward and UP / LEFT moves backward,
# independently of SET_NAV.
typed = editor_keys("3352131511256", [0] * 13)
good &= typed == (pos, 0, 7, 1, 5, 13)
good &= editor_keys("2*", pos)[:2] == ([2] + pos[1:], 1) # '*' on a LAT digit
for sn in (True, False):
N, P = ("U", "D") if sn else ("D", "U") # next / previous field
k = lambda keys, **kw: editor_keys(keys, pos, set_nav=sn, **kw)
good &= k(N)[5] == 1 and k(P)[5] == 0 and k(N + P)[5] == 0 # the raw keys' direction
good &= k(P * 13 + N * 6 + "*")[1:] == (0 ^ 1, 7, 1, 5, CUR_NS)
good &= k(N * 14 + "*" + "5")[:2] == (pos, 2) # E/W: '*', a digit ignored
good &= k(N * 15 + "**")[2:] == (9, 1, 5, CUR_SSID)
good &= k(N * 16 + "**" + N)[3:] == (0, 5, CUR_SYM)
good &= k(N * 15 + "F*F*")[2] == 5 # F then '*': SSID back
good &= k(N * 15 + "F*" * 8, ssid=3)[2] == 11 # ... wrapping 0 -> 15
good &= k(N * 16 + "F*F*F*")[3] == 1 # path back, 1 -> 0 -> 2 -> 1
good &= k(N * 15 + "FF*")[2] == 8 # F twice: disarmed
good &= k(N * 15 + "F" + N + "*")[3:] == (2, 5, CUR_PATH) # F used by a move, '*' forward
good &= k(N * 17 + "**")[4:] == (7, CUR_SYM) # symbol forward
good &= k(N * 17 + "F*")[4:] == (4, CUR_SYM) # symbol backward
N, P = "D", "U" # next / previous field
k = lambda keys, **kw: editor_keys(keys, pos, **kw)
good &= k(N)[5] == 1 and k(P)[5] == 0 and k(N + P)[5] == 0 # the raw keys' direction
good &= k(P * 13 + N * 6 + "*")[1:] == (0 ^ 1, 7, 1, 5, CUR_NS)
good &= k(N * 14 + "*" + "5")[:2] == (pos, 2) # E/W: '*', a digit ignored
good &= k(N * 15 + "**")[2:] == (9, 1, 5, CUR_SSID)
good &= k(N * 16 + "**" + N)[3:] == (0, 5, CUR_SYM)
good &= k(N * 15 + "F*F*")[2] == 5 # F then '*': SSID back
good &= k(N * 15 + "F*" * 8, ssid=3)[2] == 11 # ... wrapping 0 -> 15
good &= k(N * 16 + "F*F*F*")[3] == 1 # path back, 1 -> 0 -> 2 -> 1
good &= k(N * 15 + "FF*")[2] == 8 # F twice: disarmed
good &= k(N * 15 + "F" + N + "*")[3:] == (2, 5, CUR_PATH) # F used by a move, '*' forward
good &= k(N * 17 + "**")[4:] == (7, CUR_SYM) # symbol forward
good &= k(N * 17 + "F*")[4:] == (4, CUR_SYM) # symbol backward
ok &= good
print("editor keys (SET_NAV on and off):", good)
print("editor keys (DOWN/RIGHT forward):", good)
checks = [([4,8,5,0,9,0,0,0,2,1,6,2,5], True), ([9,0,0,0,0,0,1,8,0,0,0,0,0], True),
([9,0,0,1,0,0,0,0,0,0,0,0,0], False), ([4,8,6,0,0,0,0,0,0,0,0,0,0], False),
([4,8,0,0,0,0,1,8,0,0,0,0,1], False), ([4,8,0,0,0,0,1,8,1,0,0,0,0], False),
+68
View File
@@ -0,0 +1,68 @@
# CW Decode overlay app
`CW Decode` decodes keyed RF carriers (A1A) from the BK4829 RSSI envelope. It
also uses the BK4829 USB baseband output as a product detector, with the
receiver internally tuned 700 Hz below the displayed carrier frequency. This
makes incoming CW audible without changing the RSSI-based decoding path.
The adaptive detection threshold starts 4 dB above the calibrated quiet-channel
level. This margin remains adjustable and is saved with the app configuration.
## IC-705 bench test
1. Select the same quiet 2 m or 70 cm frequency on both radios.
2. Put the IC-705 in CW mode and use the lowest practical RF power, or use a
dummy load / attenuated coupling for a conducted test.
3. Tune the UV-K1/K5 V3 to that frequency and launch `CW Decode`.
4. Keep the IC-705 key up while `KEEP CHANNEL QUIET` is displayed.
5. Send `VVV` first so the timing estimator settles, then send clean Morse.
The automatic start-up capture range is roughly 8–22 WPM. For a faster
operator, press `2` as needed before sending; use `F` then `2` as needed for
a slower operator.
## Display
- The up/down marks show whether more decoded text is available.
- The boxed `F` icon means that the next adjustable key runs in reverse.
- The speaker icon, shared with Foxhunt, means that receive audio is enabled.
The `F` icon temporarily replaces it while reverse adjustment is armed.
- The `AGC` icon means that automatic receiver gain is enabled.
- The upper capsule row shows detection `THR` on the left, the adaptive `WPM`
speed estimate in the centre, and `RSSI` on the right. Their frames reserve
the maximum value widths and therefore remain fixed. The dBm suffix is
omitted from the main screen and documented in the lower-right corner of
the help page. The bottom row shows the current `MORSE` symbol on the left
and the RX frequency on the right, using Beacon's normal-font format.
## Controls
- `UP` / `DOWN`: smooth pixel-by-pixel history scrolling.
- `0`: enable or mute the continuously open CW receive audio path.
- `*`: switch between normal and compact decoded-text fonts.
- `1`: raise the detection threshold; `F` then `1` lowers it.
- `2`: raise the current WPM bias; `F` then `2` lowers it.
- `3`: enable or disable AGC, then repeat quiet-channel calibration.
- `4`: repeat quiet-channel calibration.
- `5`: clear the decoded history.
- `MENU`: open the help page.
- `EXIT`: leave the app from the decoder, or return to the decoder from help.
`MENU` also returns from help.
Threshold and RSSI values are expressed in dBm.
The help page uses the same two-column capsule layout as CW Keyer. Decoding
and audio continue in the background while help is displayed, but its setting
and scrolling controls are ignored until `MENU` or `EXIT` returns to the
decoder.
A normal Morse word gap schedules a space. A much longer silence of 15 dot
units ends the sequence (about 1.2 seconds at 15 WPM), but does not create an
empty line. The pending space or line break is inserted only when the next
keyed carrier starts. Automatic following advances by one complete text row;
manual history scrolling remains pixel-smooth, and a new keyed carrier resumes
automatic following on the active line. The font, threshold margin, AGC mode
and speaker state are saved. To protect mark timing, decoded text is redrawn during the longer
inter-word silence; the `MORSE` capsule continues to show each symbol between
full redraws. Timing is relearned on every launch. Letters A-Z, digits 0-9 and
the `+`, `=` and `/` symbols are supported; malformed or unsupported patterns
are shown as `?`.
+30
View File
@@ -0,0 +1,30 @@
/* Overlay-app link script. */
APP_VMA = DEFINED(APP_VMA) ? APP_VMA : 0x20000280;
APP_LENGTH = 0x1000;
ENTRY(app_main)
MEMORY {
APP (rwx) : ORIGIN = APP_VMA, LENGTH = APP_LENGTH
}
SECTIONS {
.app APP_VMA : {
KEEP(*(.text.entry))
*(.text .text.*)
*(.rodata .rodata.*)
. = ALIGN(4);
*(.data .data.*)
. = ALIGN(4);
__app_bss_start = .;
*(.bss .bss.* COMMON)
. = ALIGN(4);
__app_bss_end = .;
} > APP
__app_end = .;
ASSERT(__app_end <= APP_VMA + APP_LENGTH,
"CW Decode overlay app overflows the 4 KiB overlay")
/DISCARD/ : { *(.ARM.exidx*) *(.ARM.extab*) *(.eh_frame*) *(.comment) *(.note.*) }
}
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Build one overlay-app blob (.app). Invoked by ../../../compile-app.sh inside
# the uvk1-uvk5v3 Docker image, run from this app's directory.
set -euo pipefail
APP="$(basename "$PWD")"
APP_NAME="CW Decode"
APP_VER="0.2"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280}
OUT="${APP_NAME// /}"
CC=/opt/toolchain/bin/arm-none-eabi-gcc
OBJCOPY=/opt/toolchain/bin/arm-none-eabi-objcopy
command -v arm-none-eabi-gcc >/dev/null 2>&1 && { CC=arm-none-eabi-gcc; OBJCOPY=arm-none-eabi-objcopy; }
CFLAGS="-mcpu=cortex-m0plus -mthumb -Os -fno-jump-tables -std=gnu11 -ffreestanding -fno-builtin -fno-common \
-fomit-frame-pointer -ffunction-sections -fdata-sections -Wall -Wextra"
LDFLAGS="-nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=${APP_VMA} \
-Wl,--gc-sections -Wl,-Map=${APP}.map -Wl,--build-id=none -Wl,--no-warn-rwx-segments"
rm -f ./*.app ./*.elf ./*.bin
step() { printf '\r 🔨 %-13s [%d/4] %-8s' "$APP_NAME" "$1" "$2"; }
trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR
step 1 assets ; python3 ./gen_assets.py "${APP}_assets.bin" "${APP}_assets.h"
step 2 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
step 3 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
step 4 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \
--name "$APP_NAME" --ver "$APP_VER" --api-min "$APP_API_MIN" \
--vma "${APP_VMA}" --shortcut none \
--assets "${APP}_assets.bin" >/dev/null
trap - ERR
BYTES=$(wc -c < "${APP}.bin")
if [ "$BYTES" -gt 4096 ]; then
printf '\r 🚨 %-13s OVERFLOWS 4 KiB (%d B) \n' "$APP_NAME" "$BYTES"; exit 1
fi
printf '\r ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app \n' \
"$APP_NAME" "$BYTES" "$(( BYTES * 100 / 4096 ))" "$OUT"
+804
View File
@@ -0,0 +1,804 @@
/* Copyright 2026 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* CW Decode - keyed-carrier Morse decoder for the Fusion firmware.
*
* The BK4829 cannot demodulate SSB/CW into an audio sidetone. This app follows
* the corrected RSSI envelope instead, so it decodes a nearby A1A transmitter
* without relying on the noisy FM audio path.
*/
#include <stdbool.h>
#include <stdint.h>
#include "../app_api.h"
#include "cwdecode_assets.h"
#define SAMPLE_MS 10u
#define KEY_MS 50u
#define DRAW_MS 250u
#define BATTERY_MS 500u
#define CALIBRATE_MS 1200u
#define DEBOUNCE_MS 10u
#define MIN_MARK_MS 20u
#define MAX_MARK_MS 1200u
#define DEFAULT_DOT_MS 80u
#define MIN_DOT_MS 30u
#define MAX_DOT_MS 300u
#define SPEED_STEP_MS 5u
#define DEFAULT_MARGIN 4u
#define MIN_MARGIN 4u
#define MAX_MARGIN 30u
#define HYSTERESIS_DB 3
#define WORD_GAP_DOTS 6u
#define LINE_GAP_DOTS 15u
#define TEXT_CAP 256u
#define BODY_BOTTOM 39u
#define CFG_MAGIC_V1 0xC9u
#define CFG_MAGIC 0xCAu
#define RX_PITCH_10HZ 70u
#define AGC_X 44u
#define SCROLL_X 63u
#define F_X 72u
#define THR_CAPS_X 2u
#define THR_CAPS_END 34u
#define WPM_CAPS_X 52u
#define WPM_CAPS_END 76u
#define RSSI_CAPS_X 91u
#define RSSI_CAPS_END 127u
#define MORSE_CAPS_END 54u
#define REG_TX_LINK 0x30u
#define REG_AM_CTRL 0x31u
#define REG_FREQ_LOW 0x38u
#define REG_FREQ_HIGH 0x39u
#define REG_AUDIO_FILTER 0x3Du
#define REG_RX_LEVEL 0x42u
#define REG_AUDIO_GAIN 0x48u
#define REG_AUDIO_1 0x54u
#define REG_AUDIO_2 0x55u
#define REG_AFC 0x73u
#define REG_RX_FILTER_1 0x2Au
#define REG_RX_FILTER_2 0x2Bu
#define REG_RX_FILTER_3 0x2Fu
#define AF_USB 5u
#define RX_LINK_OFF 0x0000u
typedef struct {
uint8_t magic, compact, margin, agc, speaker;
} config_t;
_Static_assert(sizeof(config_t) <= 16u, "CW Decode config exceeds overlay slot");
typedef struct {
char morse[MORSE_TREE_LEN];
uint32_t decimalPlaces[DECIMAL_PLACES_LEN / 4u];
} asset_tables_t;
_Static_assert(DECIMAL_PLACES == MORSE_TREE + MORSE_TREE_LEN,
"CW Decode lookup assets must remain contiguous");
_Static_assert(sizeof(asset_tables_t) == MORSE_TREE_LEN + DECIMAL_PLACES_LEN,
"CW Decode lookup cache must not contain padding");
_Static_assert(BMP_CAL_SCREEN_LEN == 4u * 128u,
"CW Decode calibration screen must cover LCD pages 1 through 4");
static struct {
const app_api_t *A;
const char *morse;
const uint32_t *decimalPlaces;
bool running, down, candidate, fArm, redraw, follow, keyRedraw, morseRedraw;
bool help;
bool agc, speaker;
bool linePending;
uint8_t prevKey, margin, gapStage, sampleIndex;
uint8_t code, depth, compact;
uint16_t dotMs, textLen, top, limit;
int16_t rssi, threshold, samples[3];
int32_t noiseQ8;
uint32_t edgeAt, candidateAt, lastDraw, lastBattery, lastKey;
char pattern[7];
char *text;
} g;
#define A (g.A)
static int32_t divPow2(int32_t value, uint8_t shift)
{
return value < 0 ? -(int32_t)((uint32_t)(-value) >> shift)
: (int32_t)((uint32_t)value >> shift);
}
static uint32_t udiv(uint32_t n, uint32_t d)
{
return (uint32_t)A->uidivmod(n, d);
}
static char *put(char *out, const char *text)
{
while (*text)
*out++ = *text++;
return out;
}
static char *putu(char *out, uint32_t value)
{
bool started = false;
for (uint8_t i = 0u; i < DECIMAL_PLACES_LEN / 4u; i++) {
const uint32_t place = g.decimalPlaces[i];
uint8_t digit = 0u;
while (value >= place) {
value -= place;
digit++;
}
if (digit || started || place == 1u) {
*out++ = (char)('0' + digit);
started = true;
}
}
return out;
}
static char *puti(char *out, int32_t value)
{
if (value < 0) {
*out++ = '-';
value = -value;
}
return putu(out, (uint32_t)value);
}
static void configureCwRx(void)
{
uint32_t frequency = A->rx_freq();
if (frequency > RX_PITCH_10HZ)
frequency -= RX_PITCH_10HZ;
A->bk_write(REG_FREQ_LOW, (uint16_t)frequency);
A->bk_write(REG_FREQ_HIGH, (uint16_t)(frequency >> 16));
const uint16_t rxLink = A->bk_read(REG_TX_LINK);
A->bk_write(REG_TX_LINK, RX_LINK_OFF);
A->bk_write(REG_TX_LINK, rxLink);
/* Match CW Keyer's proven USB product-detector path. The displayed VFO
* remains the carrier frequency while the receiver runs 700 Hz below it. */
A->bk_write(REG_AM_CTRL, A->bk_read(REG_AM_CTRL) & 0xFFFEu);
A->bk_write(REG_RX_LEVEL, 0x6B5Au);
A->bk_write(REG_RX_FILTER_1, 0x7400u);
A->bk_write(REG_RX_FILTER_2, 0x0000u);
A->bk_write(REG_RX_FILTER_3, 0x9890u);
A->bk_write(REG_AUDIO_1, 0x9009u);
A->bk_write(REG_AUDIO_2, 0x31A9u);
A->bk_write(REG_AUDIO_GAIN,
(uint16_t)((A->bk_read(REG_AUDIO_GAIN) & 0xFFF0u) | 0x000Fu));
A->bk_write(REG_AUDIO_FILTER, 0u);
A->bk_write(REG_AFC, A->bk_read(REG_AFC) | 0x0010u);
A->set_agc(g.agc);
A->set_af(AF_USB);
A->audio_path(g.speaker);
}
static void updateLimit(void)
{
const uint16_t width = g.compact ? 32u : 18u;
const uint16_t step = g.compact ? 6u : 8u;
const uint16_t visibleRows = g.compact ? 6u : 5u;
uint16_t rows = g.textLen ? 1u : 0u;
uint16_t column = 0u;
for (uint16_t i = 0u; i < g.textLen; i++) {
if (g.text[i] == '\n') {
rows++;
column = 0u;
} else {
if (column == width) {
rows++;
column = 0u;
}
column++;
}
}
g.limit = rows > visibleRows ? (uint16_t)((rows - visibleRows) * step) : 0u;
if (g.top > g.limit)
g.top = g.limit;
}
static void append(char c)
{
if (c == ' ' && (!g.textLen || g.text[g.textLen - 1u] == ' ' ||
g.text[g.textLen - 1u] == '\n'))
return;
if (c == '\n' && (!g.textLen || g.text[g.textLen - 1u] == '\n'))
return;
const bool keepFollowing = g.follow || g.top == g.limit;
if (g.textLen >= TEXT_CAP) {
const uint16_t width = g.compact ? 32u : 18u;
const uint16_t step = g.compact ? 6u : 8u;
uint16_t drop = 0u;
while (drop < width && g.text[drop] != '\n')
drop++;
if (g.text[drop] == '\n')
drop++;
for (uint16_t i = drop; i < g.textLen; i++)
g.text[i - drop] = g.text[i];
g.textLen = (uint16_t)(g.textLen - drop);
if (!keepFollowing)
g.top = g.top > step ? (uint16_t)(g.top - step) : 0u;
}
g.text[g.textLen++] = c;
g.text[g.textLen] = '\0';
g.follow = keepFollowing;
updateLimit();
if (g.follow)
g.top = g.limit;
g.redraw = true;
}
static void armLineBreak(void)
{
if (!g.textLen || g.text[g.textLen - 1u] == '\n')
return;
g.linePending = true;
}
static void clearSymbol(void)
{
g.code = 1u;
g.depth = 0u;
g.pattern[0] = '\0';
g.morseRedraw = true;
}
static void finishCharacter(void)
{
if (!g.depth)
return;
append(g.depth <= 5u && g.code < 64u && g.morse[g.code]
? g.morse[g.code] : '?');
clearSymbol();
}
static void addMark(uint32_t duration)
{
if (duration < MIN_MARK_MS)
return;
if (duration > MAX_MARK_MS) {
append('?');
clearSymbol();
return;
}
const bool dash = duration > (uint32_t)g.dotMs * 2u;
uint32_t estimate = dash ? udiv(duration, 3u) : duration;
if (estimate < MIN_DOT_MS)
estimate = MIN_DOT_MS;
if (estimate > MAX_DOT_MS)
estimate = MAX_DOT_MS;
const uint32_t low = (uint32_t)g.dotMs * 7u >> 3;
const uint32_t high = (uint32_t)g.dotMs * 9u >> 3;
if (estimate < low)
estimate = low;
if (estimate > high)
estimate = high;
g.dotMs = (uint16_t)(((uint32_t)g.dotMs * 3u + estimate + 2u) >> 2);
if (g.depth < 6u) {
g.pattern[g.depth++] = dash ? '-' : '.';
g.pattern[g.depth] = '\0';
if (g.depth <= 5u)
g.code = (uint8_t)((g.code << 1) | (dash ? 1u : 0u));
}
g.morseRedraw = true;
g.redraw = true;
}
static void serviceGap(uint32_t now)
{
if (g.down)
return;
const uint32_t gap = now - g.edgeAt;
if (g.gapStage == 0u && gap >= ((uint32_t)g.dotMs * 5u >> 1)) {
finishCharacter();
g.gapStage = 1u;
}
if (g.gapStage == 1u && gap >= (uint32_t)g.dotMs * WORD_GAP_DOTS) {
g.gapStage = 2u;
}
if (g.gapStage == 2u && gap >= (uint32_t)g.dotMs * LINE_GAP_DOTS) {
armLineBreak();
g.gapStage = 3u;
}
}
static void acceptEdge(bool down, uint32_t at)
{
if (down) {
if (!g.follow) {
g.follow = true;
g.top = g.limit;
g.redraw = true;
g.keyRedraw = true;
}
serviceGap(at);
if (g.linePending) {
g.linePending = false;
append('\n');
} else if (g.gapStage >= 2u)
append(' ');
g.down = true;
g.edgeAt = at;
} else {
const uint32_t mark = at - g.edgeAt;
g.down = false;
g.edgeAt = at;
g.gapStage = 0u;
addMark(mark);
}
g.redraw = true;
}
static void sample(uint32_t now)
{
const int16_t rawRssi = A->rssi_dbm();
g.samples[g.sampleIndex] = rawRssi;
if (++g.sampleIndex >= 3u)
g.sampleIndex = 0u;
const int16_t a = g.samples[0], b = g.samples[1], c = g.samples[2];
g.rssi = a > b ? (b > c ? b : (a > c ? c : a))
: (a > c ? a : (b > c ? c : b));
g.threshold = (int16_t)(divPow2(g.noiseQ8, 8u) + g.margin);
const int16_t decision = g.down
? (int16_t)(g.threshold - HYSTERESIS_DB) : g.threshold;
const bool raw = g.rssi >= decision;
/* A valid Morse mark never exceeds MAX_MARK_MS. Rebase a continuously
* asserted input so a raised noise floor cannot lock the decoder forever. */
if (g.down && raw && g.candidate && now - g.edgeAt > MAX_MARK_MS) {
append('?');
clearSymbol();
g.noiseQ8 = (int32_t)g.rssi * 256;
g.threshold = (int16_t)(g.rssi + g.margin);
g.down = g.candidate = false;
g.candidateAt = g.edgeAt = now;
g.gapStage = 0u;
return;
}
if (!g.down && !raw) {
const int32_t target = (int32_t)g.rssi * 256;
g.noiseQ8 += divPow2(target - g.noiseQ8, 8u);
}
if (raw == g.down) {
g.candidate = raw;
g.candidateAt = now;
} else if (raw != g.candidate) {
g.candidate = raw;
g.candidateAt = now;
} else if (now - g.candidateAt >= DEBOUNCE_MS) {
acceptEdge(raw, g.candidateAt);
g.candidateAt = now;
}
}
static void drawStatus(const char *ui)
{
A->status_clear();
A->print_inverse(ui + T_TITLE, 2u, 0u, true, true,
(uint8_t)(2u + T_TITLE_CHARS * 4u));
if (g.agc)
A->asset_read(BMP_AGC, A->status_line + AGC_X, BMP_AGC_LEN);
unsigned marks = 0u;
if (g.top)
marks |= 1u;
if (g.top < g.limit)
marks |= 2u;
A->asset_read((uint16_t)(BMP_SCROLL + marks * BMP_SCROLL_W),
A->status_line + SCROLL_X, BMP_SCROLL_W);
if (g.fArm)
A->asset_read(BMP_F, A->status_line + F_X, BMP_F_LEN);
else if (g.speaker)
A->asset_read(BMP_SPEAKER, A->status_line + F_X, BMP_SPEAKER_LEN);
A->draw_battery();
}
static void renderDecoded(void)
{
char row[33];
uint16_t pos = 0u, vrow = g.compact ? 2u : 0u;
const uint8_t width = g.compact ? 32u : 18u;
const uint8_t step = g.compact ? 6u : 8u;
const int32_t bodyTop = g.compact ? 2 : 0;
const int32_t bodyBottom = g.compact ? (int32_t)BODY_BOTTOM - 1
: (int32_t)BODY_BOTTOM;
const int32_t glyphHeight = g.compact ? 5 : 7;
const uint16_t bottomMask = g.compact ? 0x3Fu : 0x7Fu;
while (pos < g.textLen) {
uint8_t n = 0u;
while (n < width && pos < g.textLen && g.text[pos] != '\n')
row[n++] = g.text[pos++];
row[n] = '\0';
const int32_t y = (int32_t)vrow - (int32_t)g.top;
if (n && y + glyphHeight > bodyTop && y < bodyBottom) {
uint8_t *scratch = A->fb[6];
if (g.compact)
A->print_tiny(row, 0u, 48u, false, true);
else
A->print_normal(row, 0u, 0u, 6u);
for (unsigned x = 0u; x < 128u; x++) {
if (y < bodyTop) {
const uint8_t crop = (uint8_t)(bodyTop - y);
A->fb[0][x] |= (uint8_t)((scratch[x] >> crop) << bodyTop);
} else {
const unsigned page = (unsigned)y >> 3;
const unsigned shift = (unsigned)y & 7u;
uint16_t bits = (uint16_t)scratch[x] << shift;
uint8_t *out = A->fb[page] + x;
if (page == 4u)
bits &= bottomMask;
*out |= (uint8_t)bits;
if (page < 4u)
out[128] |= (uint8_t)(bits >> 8);
}
scratch[x] = 0u;
}
}
if (pos < g.textLen && g.text[pos] == '\n')
pos++;
vrow = (uint16_t)(vrow + step);
}
}
static void drawCapsules(const char *labels, bool blit)
{
char line[34], *out;
if (blit) {
for (unsigned x = 0u; x < 128u; x++) {
A->fb[5][x] = 0u;
A->fb[6][x] = 0u;
}
}
out = put(line, labels + T_THR - T_RSSI);
out = puti(out, g.threshold);
*out = '\0';
A->print_inverse(line, THR_CAPS_X, 5u, false, true, THR_CAPS_END);
out = put(line, labels);
out = puti(out, g.rssi);
*out = '\0';
A->print_inverse(line, RSSI_CAPS_X, 5u, false, true, RSSI_CAPS_END);
out = put(line, labels + T_WPM - T_RSSI);
out = putu(out, udiv(1200u, g.dotMs));
*out = '\0';
A->print_inverse(line, WPM_CAPS_X, 5u, false, true, WPM_CAPS_END);
out = put(line, labels + T_MORSE - T_RSSI);
if (g.pattern[0]) {
*out++ = ' ';
out = put(out, g.pattern);
} else if (g.gapStage == 3u) {
*out++ = ' ';
out = put(out, labels + T_WAIT - T_RSSI);
}
*out = '\0';
/* Reserve the wider idle label so neither Morse marks nor WAITING resize
* the capsule. */
A->print_inverse(line, 2u, 6u, false, true, MORSE_CAPS_END);
/* Match Beacon: RX frequency in 10 Hz units, shown as MHz with five
* decimals and right-aligned in the normal font. */
char *const end = putu(line, A->rx_freq());
char *point = end;
point[1] = '\0';
for (uint8_t i = 0u; i < 5u; i++, point--)
point[0] = point[-1];
point[0] = '.';
const unsigned len = (unsigned)(end - line) + 1u;
A->print_normal(line, (uint8_t)(126u - len * 7u), 0u, 6u);
if (blit) {
A->blit_line(5u);
A->blit_line(6u);
}
}
static void drawHelp(const char *ui)
{
char text[TEXT_MAX];
A->status_clear();
A->print_inverse(ui + T_TITLE, 2u, 0u, true, true,
(uint8_t)(2u + T_TITLE_CHARS * 4u));
A->asset_read(T_HELP, text, TEXT_MAX);
A->print_inverse(text, AGC_X, 0u, true, true,
(uint8_t)(AGC_X + 4u * 4u));
A->draw_battery();
for (uint8_t i = 0u; i < 6u; i++) {
A->asset_read((uint16_t)(T_HELP_LEFT + i * T_HELP_LEFT_STRIDE),
text, TEXT_MAX);
A->print_inverse(text, 2u, (uint8_t)(i + 1u), false, true, 46u);
A->asset_read((uint16_t)(T_HELP_RIGHT + i * T_HELP_RIGHT_STRIDE),
text, TEXT_MAX);
if (text[0])
A->print_inverse(text, 79u, (uint8_t)(i + 1u), false, true, 127u);
}
A->asset_read(T_HELP_UNIT_1, text, TEXT_MAX);
A->print_tiny(text, 56u, 41u, false, true);
A->asset_read(T_HELP_UNIT_2, text, TEXT_MAX);
A->print_tiny(text, 56u, 49u, false, true);
}
static void refreshCapsules(void)
{
char labels[UI_SIZE - T_RSSI];
A->asset_read(T_RSSI, labels, sizeof(labels));
drawCapsules(labels, true);
}
static void draw(bool calibrating)
{
char ui[UI_SIZE] __attribute__((aligned(4)));
A->asset_read(0u, ui, UI_SIZE);
A->display_clear();
if (g.help) {
drawHelp(ui);
} else {
drawStatus(ui);
if (calibrating) {
A->asset_read(BMP_CAL_SCREEN, A->fb[1], BMP_CAL_SCREEN_LEN);
} else {
if (g.textLen)
renderDecoded();
drawCapsules(ui + T_RSSI, false);
}
}
A->blit_status();
A->blit_full();
g.redraw = false;
g.morseRedraw = false;
}
static void calibrate(void)
{
g.down = false;
g.candidate = false;
g.candidateAt = A->ticks_ms();
clearSymbol();
g.gapStage = 2u;
g.rssi = A->rssi_dbm();
g.noiseQ8 = (int32_t)g.rssi * 256;
const uint32_t start = A->ticks_ms();
uint32_t last = start;
draw(true);
while (A->ticks_ms() - start < CALIBRATE_MS) {
A->delay_ms(SAMPLE_MS);
const int32_t target = (int32_t)A->rssi_dbm() * 256;
g.noiseQ8 += divPow2(target - g.noiseQ8, 3u);
const uint32_t now = A->ticks_ms();
if (now - last >= DRAW_MS) {
last = now;
A->backlight_update();
}
}
g.rssi = (int16_t)divPow2(g.noiseQ8, 8u);
g.threshold = (int16_t)(g.rssi + g.margin);
g.sampleIndex = 0u;
for (uint8_t i = 0u; i < 3u; i++)
g.samples[i] = g.rssi;
g.edgeAt = A->ticks_ms();
g.lastDraw = 0u;
g.redraw = true;
}
static void loadConfig(void)
{
config_t cfg;
A->cfg_load((uint8_t *)&cfg, sizeof(cfg));
if (cfg.magic == CFG_MAGIC || cfg.magic == CFG_MAGIC_V1) {
g.compact = (uint8_t)(cfg.compact == 1u);
g.margin = cfg.margin >= MIN_MARGIN && cfg.margin <= MAX_MARGIN
? cfg.margin : DEFAULT_MARGIN;
g.agc = cfg.agc <= 1u ? cfg.agc : true;
g.speaker = cfg.magic == CFG_MAGIC && cfg.speaker <= 1u
? cfg.speaker : true;
} else {
g.margin = DEFAULT_MARGIN;
g.agc = true;
g.speaker = true;
}
}
static void saveConfig(void)
{
const config_t cfg = {
CFG_MAGIC, g.compact, g.margin, g.agc, g.speaker
};
A->cfg_save((const uint8_t *)&cfg, sizeof(cfg));
}
static void clearText(void)
{
g.textLen = g.top = g.limit = 0u;
g.text[0] = '\0';
g.follow = true;
g.linePending = false;
clearSymbol();
g.gapStage = 2u;
}
static void handleKey(uint8_t key)
{
if (g.help) {
if (key == APP_KEY_INVALID || key == APP_KEY_SAVER ||
key == APP_KEY_WAKE || key == g.prevKey) {
g.prevKey = key;
return;
}
g.prevKey = key;
A->backlight_on();
if (key == APP_KEY_MENU || key == APP_KEY_EXIT) {
g.help = false;
g.redraw = true;
g.keyRedraw = true;
}
return;
}
/* Scrolling is spatial: raw UP/DOWN is intentional and matches the
* APRS RX/EPIRB fix for K5 UP/DOWN and K1 LEFT/RIGHT (issue #613).
* Using nav_dir() here would reintroduce the model-dependent reversal. */
const int direction = (key == APP_KEY_DOWN) - (key == APP_KEY_UP);
const uint16_t top = (uint16_t)(g.top + direction);
if (direction && top <= g.limit) {
g.top = top;
g.follow = g.top == g.limit;
g.redraw = true;
}
if (key == APP_KEY_INVALID || key == APP_KEY_SAVER || key == APP_KEY_WAKE ||
key == g.prevKey) {
g.prevKey = key;
return;
}
g.prevKey = key;
A->backlight_on();
/* A key action may redraw at the first stable carrier-up interval instead
* of waiting for the much longer normal housekeeping window. */
g.keyRedraw = true;
if (key == APP_KEY_F) {
g.fArm = !g.fArm;
g.redraw = true;
return;
}
const bool reverse = g.fArm;
g.fArm = false;
switch (key) {
case APP_KEY_EXIT:
g.running = false;
break;
case APP_KEY_0:
g.speaker = !g.speaker;
A->audio_path(g.speaker);
break;
case APP_KEY_1:
if (!reverse && g.margin < MAX_MARGIN)
g.margin++;
else if (reverse && g.margin > MIN_MARGIN)
g.margin--;
break;
case APP_KEY_2:
if (!reverse && g.dotMs > MIN_DOT_MS + SPEED_STEP_MS - 1u)
g.dotMs -= SPEED_STEP_MS;
else if (reverse && g.dotMs < MAX_DOT_MS - SPEED_STEP_MS + 1u)
g.dotMs += SPEED_STEP_MS;
break;
case APP_KEY_3:
g.agc = !g.agc;
A->set_agc(g.agc);
calibrate();
break;
case APP_KEY_4:
calibrate();
break;
case APP_KEY_5:
clearText();
break;
case APP_KEY_STAR:
g.compact ^= 1u;
updateLimit();
g.top = g.limit;
g.follow = true;
break;
case APP_KEY_MENU:
g.help = true;
break;
default:
break;
}
g.redraw = true;
}
__attribute__((section(".text.entry"), used))
void app_main(const app_api_t *api)
{
asset_tables_t tables;
/* History lives for the whole modal call but stays outside the 4 KiB
* overlay image, following the larger-buffer overlay apps. */
char text[TEXT_CAP + 1u];
A = api;
g.morse = tables.morse;
g.decimalPlaces = tables.decimalPlaces;
g.text = text;
A->asset_read(MORSE_TREE, &tables, sizeof(tables));
g.running = true;
g.follow = true;
g.help = false;
g.prevKey = APP_KEY_INVALID;
g.dotMs = DEFAULT_DOT_MS;
loadConfig();
updateLimit();
A->backlight_on();
configureCwRx();
calibrate();
while (g.running) {
const uint32_t now = A->ticks_ms();
sample(now);
serviceGap(now);
if (now - g.lastKey >= KEY_MS) {
g.lastKey = now;
handleKey(A->get_key());
}
if (!g.running)
break;
const uint32_t gap = now - g.edgeAt;
const bool stableGap = !g.down && g.candidate == g.down;
const bool quietWindow = stableGap &&
(g.gapStage == 2u || gap >= (uint32_t)g.dotMs * 4u);
const bool drawWindow = quietWindow || (stableGap && g.keyRedraw);
if (drawWindow && (g.redraw || !g.lastDraw || now - g.lastDraw >= DRAW_MS)) {
g.lastDraw = now;
draw(false);
g.keyRedraw = false;
} else if (!g.help && stableGap && g.morseRedraw) {
refreshCapsules();
g.morseRedraw = false;
}
if (quietWindow && now - g.lastBattery >= BATTERY_MS) {
g.lastBattery = now;
A->battery_sample();
}
A->backlight_update();
A->delay_ms(SAMPLE_MS);
}
saveConfig();
/* The loader's retune may keep its cached AGC setting, so restore the
* receiver's normal automatic gain explicitly before returning. */
A->set_agc(true);
A->set_af(APP_AF_MUTE);
A->audio_path(false);
}
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""CW Decode read-only UI, icon and Morse assets."""
import os
import re
import sys
sys.dont_write_bytecode = True
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, ".."))
from app_assets import Assets
TITLE = "CW DECODE"
UI = [
("T_TITLE", TITLE),
("T_RSSI", "RSSI "),
("T_THR", "THR "),
("T_WPM", "WPM "),
("T_MORSE", "MORSE"),
("T_WAIT", "WAITING"),
]
a = Assets("CWDECODE")
ui_size = 0
for name, text in UI:
padding = -(len(text) + 1) % 4
a.text(name, text + "\0" * padding)
ui_size += len(text) + 1 + padding
a.const("UI_SIZE", ui_size)
a.const("T_TITLE_CHARS", len(TITLE))
a.text("T_HELP", "HELP")
a.text("T_HELP_UNIT_1", "Threshold and RSSI")
a.text("T_HELP_UNIT_2", "are in dBm")
a.table("T_HELP_LEFT", [
"0 SPEAKER", "1 THRESHOLD", "2 WPM", "3 AGC", "4 CAL", "5 CLEAR",
])
a.table("T_HELP_RIGHT", [
"* FONT", "F+ DECREASE", "UP/DN SCROLL", "", "", "",
])
def load_small_font():
"""Decode the firmware's packed 6x7 font exactly as UI_PrintStringBuffer."""
with open(os.path.join(HERE, "..", "..", "font.c"), encoding="ascii") as source:
match = re.search(
r"const uint8_t gFontSmallPacked\[FONT_SMALL_PACKED_SIZE\]\s*=\s*"
r"\{(.*?)\n\};", source.read(), re.DOTALL)
if not match:
raise RuntimeError("gFontSmallPacked not found in App/font.c")
packed = bytes(int(value, 16) for value in re.findall(r"0x([0-9A-Fa-f]{2})",
match.group(1)))
expected = (94 * 6 * 7 + 7) // 8
if len(packed) != expected:
raise RuntimeError(f"unexpected packed small-font size: {len(packed)}")
def glyph(char):
bit = (ord(char) - ord(" ") - 1) * 6 * 7
columns = []
for _ in range(6):
byte = bit >> 3
word = packed[byte] | packed[byte + 1] << 8
columns.append((word >> (bit & 7)) & 0x7F)
bit += 7
return columns
return glyph
def calibration_screen():
"""Pre-render framebuffer pages 1..4 of the fixed calibration screen."""
frame = bytearray(7 * 128)
glyph = load_small_font()
def print_normal(text, start, end, page):
if end > start:
start += ((end - start - len(text) * 7) + 1) // 2
for index, char in enumerate(text):
if " " < char < chr(127):
offset = page * 128 + start + index * 7 + 1
frame[offset:offset + 6] = bytes(glyph(char))
text = "CALIBRATING"
width = len(text) * 7
text_x = (128 - width) // 2
end = text_x + width + 1
print_normal(text, text_x, 0, 2)
frame[2 * 128 + text_x - 1] ^= 0x7F
for x in range(text_x, end):
frame[2 * 128 + x] ^= 0xFF
frame[1 * 128 + x] ^= 0x80
frame[2 * 128 + end] ^= 0x7F
print_normal("KEEP CHANNEL QUIET", 0, 127, 4)
return frame[128:5 * 128]
morse = bytearray(64)
for index, char in {
2: "E", 3: "T", 4: "I", 5: "A", 6: "N", 7: "M",
8: "S", 9: "U", 10: "R", 11: "W", 12: "D", 13: "K", 14: "G", 15: "O",
16: "H", 17: "V", 18: "F", 20: "L", 22: "P", 23: "J",
24: "B", 25: "X", 26: "C", 27: "Y", 28: "Z", 29: "Q",
42: "+", 49: "=", 50: "/",
32: "5", 33: "4", 35: "3", 39: "2", 47: "1",
48: "6", 56: "7", 60: "8", 62: "9", 63: "0",
}.items():
morse[index] = ord(char)
a.raw("MORSE_TREE", morse)
# Decimal places are cached once by the app and avoid divisions in the
# frequently refreshed signal and frequency formatting paths.
a.u32("DECIMAL_PLACES", [10 ** exponent for exponent in range(9, -1, -1)])
up = [0x04, 0x06, 0x07, 0x06, 0x04]
down = [0x10, 0x30, 0x70, 0x30, 0x10]
a.u8("BMP_SCROLL", [
(u if state & 1 else 0) | (d if state & 2 else 0)
for state in range(4) for u, d in zip(up, down)
])
a.const("BMP_SCROLL_W", len(up))
f_icon = [0x3E, 0x7F, 0x41, 0x75, 0x75, 0x75, 0x7D, 0x7F, 0x3E]
a.u8("BMP_F", f_icon)
a.u8("BMP_SPEAKER", [0x1C, 0x1C, 0x3E, 0x7F, 0x00,
0x22, 0x1C, 0x41, 0x22, 0x1C])
# Inverse "AGC" capsule, same style as the title and the F icon.
a.u8("BMP_AGC", [0x3E, 0x7F, 0x43, 0x75, 0x43, 0x7F, 0x63, 0x5D, 0x45, 0x7F,
0x63, 0x5D, 0x5D, 0x7F, 0x3E])
a.raw("BMP_CAL_SCREEN", calibration_screen())
a.main()
+96
View File
@@ -0,0 +1,96 @@
# CW Keyer overlay app
`CW Keyer` transmits manually keyed A1A-style carrier Morse on the current TX
VFO. It is entirely contained in the 4 KiB overlay and uses only API level 2;
it has no resident firmware component.
## Controls
- `PTT`: straight-key carrier in either mode.
- `SIDE1` / `SIDE2` in `STRAIGHT`: additional straight keys.
- `SIDE1` / `SIDE2` in `PADDLE`: automatic dits and dahs with element memory.
- `0`: enable or mute the CW receive audio path.
- `1`: switch between `STRAIGHT` and `PADDLE`.
- `2`: raise the keyer speed.
- `3`: raise the receive CW tone by 50 Hz.
- `4`: raise the semi-break-in delay by 50 ms.
- `5`: swap the SIDE1/SIDE2 dit and dah assignments.
- `6`: raise the crystal correction by 0.01 ppm.
- `F`, then `1` to `6`: apply the corresponding setting in the reverse
direction. Two-state settings (`MODE` and paddle order) simply toggle.
- `UP` / `DOWN`: tune the app frequency by a fixed 100 Hz CW step. Holding a
direction repeats after 400 ms, then every 100 ms.
- `MENU`: disarm TX and open the help page.
- `EXIT`: leave the app from the main page, or return to the main page from
help. `MENU` also returns from help.
The main screen shows all six controls as compact, equal-width 43-pixel
3x5-font capsules in the bottom three lines below the frequency. The left
column is anchored to the left edge, the right column to the right edge, and
every label is left-aligned inside its capsule: `STRAIGHT` / `PADDLE`, `WPM`,
`TONE`, `BK`, `DIT-DAH` / `DAH-DIT`, and `XTAL`. Keyboard shortcuts are shown
on the help page instead of inside the capsules. While help is open, keying and
setting changes are disabled; `MENU` or `EXIT` returns to the main page.
The Foxhunt speaker icon shows that receive audio is enabled; the `F` icon
temporarily replaces it while reverse adjustment is armed.
The speed, mode, paddle assignment, calibration, receive pitch, break-in delay
and speaker state are saved when the app exits. The default correction is +2.77 ppm,
measured as +400 Hz at 144.225 MHz and +1.20 kHz at 433.225 MHz. A positive
displayed correction means the uncorrected carrier was high; the app therefore
lowers the programmed TX frequency proportionally. The PTT always remains a
straight key, so it is usable even while `PADDLE` is selected.
The frequency offset selected with `UP` / `DOWN` is local to the overlay. It is
applied equally to CW RX, TX and the displayed frequency, and resets to zero
when the app exits; the resident VFO is never modified.
Interface keys are accepted only after a stable 20 ms reading. PTT and the two
side keys remain immediate, preventing keypad-matrix transients from opening
help or changing a setting without adding latency to CW keying.
Full-screen refreshes are kept out of the keying path; TX/RX transitions update
only the status line. Battery sampling is deferred until the transmitter has
fully returned to RX so it cannot stretch a mark or an inter-element space.
A continuous mark is limited to ten seconds as stuck-key protection; the key
must be released before the app will accept another transmission.
## RF keying
On the first mark, the app calls the existing `tx_set_params` API service. That
service selects the TX VFO, starts the BK4829 transmitter and enables the board
PA before returning. The app immediately removes PA drive, mutes every
audio/sub-audio source and caches BK4829 register `0x36`, whose upper byte
drives the `VRAMP` PA-bias output. It then applies the proportional crystal
correction to synthesizer registers `0x38`/`0x39`, in their native 10 Hz units,
and restarts register `0x30` through zero so the VCO recalibrates and latches
the corrected frequency.
Register `0x50` remains in TX modulation-mute for the entire session. During a
mark, register `0x30` is `0xC3FA`, matching the driver's TX-link configuration
with the microphone ADC disabled. During a space it becomes `0xC3F2`, which
also gates the BK4829 internal PA gain while leaving the PLL locked. The
unmodulated carrier is therefore keyed with three coordinated controls: the
internal PA gain, `VRAMP`, and the board PA enable.
Every mark uses a four-step, 4 ms rise and fall of the saved PA bias. Internal
PA gain and board PA enable are asserted before the rise, then removed after
the fall. Between marks the synthesizer remains locked, but internal PA gain,
PA bias and the board PA are all off.
`MENU`, `EXIT`, and every abort path call `tx_end` to disable the PA and restore
normal RX. After the final mark, the configurable semi-break-in hang (300 ms
by default) keeps the synthesizer ready across Morse element gaps, then
automatically calls `tx_end` and restores reception without requiring `MENU`.
The overlay then selects the BK4829 USB baseband output and tunes below the
calibrated RX frequency by the configured receive pitch (700 Hz by default),
so an incoming A1A carrier is heard at that pitch. When enabled, the speaker
remains open throughout RX: gating it with the FM squelch clips the start of
dits and chops the tone at every carrier transition.
The atomic `tx_set_params` service necessarily enables RF briefly before the
overlay can remove PA drive. The app then waits for the corrected synthesizer
setting to lock and applies the normal ramp to the first deliberate mark. A
spectrum/SDR check is required before assuming the four-step envelope is
spectrally clean on every power band.
True iambic A/B squeeze keying is not possible through the current single-key
`get_key` API. The paddle mode supports one paddle at a time, automatic repeat,
and memory of the opposite paddle when the scanner reports it during an element.
No sidetone is generated: the existing tone services also feed the TX
modulation path, which would turn the signal into MCW instead of A1A.
+30
View File
@@ -0,0 +1,30 @@
/* CW Keyer overlay-app link script. */
APP_VMA = DEFINED(APP_VMA) ? APP_VMA : 0x20000280;
APP_LENGTH = 0x1000;
ENTRY(app_main)
MEMORY {
APP (rwx) : ORIGIN = APP_VMA, LENGTH = APP_LENGTH
}
SECTIONS {
.app APP_VMA : {
KEEP(*(.text.entry))
*(.text .text.*)
*(.rodata .rodata.*)
. = ALIGN(4);
*(.data .data.*)
. = ALIGN(4);
__app_bss_start = .;
*(.bss .bss.* COMMON)
. = ALIGN(4);
__app_bss_end = .;
} > APP
__app_end = .;
ASSERT(__app_end <= APP_VMA + APP_LENGTH,
"CW Keyer overlay app overflows the 4 KiB overlay")
/DISCARD/ : { *(.ARM.exidx*) *(.ARM.extab*) *(.eh_frame*) *(.comment) *(.note.*) }
}
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Build one overlay-app blob (.app). Invoked by ../../../compile-app.sh inside
# the uvk1-uvk5v3 Docker image, run from this app's directory.
set -euo pipefail
APP="$(basename "$PWD")"
APP_NAME="CW Keyer"
APP_VER="0.1"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280}
OUT="${APP_NAME// /}"
CC=/opt/toolchain/bin/arm-none-eabi-gcc
OBJCOPY=/opt/toolchain/bin/arm-none-eabi-objcopy
command -v arm-none-eabi-gcc >/dev/null 2>&1 && { CC=arm-none-eabi-gcc; OBJCOPY=arm-none-eabi-objcopy; }
CFLAGS="-mcpu=cortex-m0plus -mthumb -Os -fno-jump-tables -std=gnu11 -ffreestanding -fno-builtin -fno-common \
-fomit-frame-pointer -ffunction-sections -fdata-sections -Wall -Wextra"
LDFLAGS="-nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=${APP_VMA} \
-Wl,--gc-sections -Wl,-Map=${APP}.map -Wl,--build-id=none -Wl,--no-warn-rwx-segments"
rm -f ./*.app ./*.elf ./*.bin
step() { printf '\r 🔨 %-13s [%d/4] %-8s' "$APP_NAME" "$1" "$2"; }
trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR
step 1 assets ; python3 ./gen_assets.py "${APP}_assets.bin" "${APP}_assets.h"
step 2 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
step 3 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
step 4 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \
--name "$APP_NAME" --ver "$APP_VER" --api-min "$APP_API_MIN" \
--vma "${APP_VMA}" --shortcut none \
--assets "${APP}_assets.bin" >/dev/null
trap - ERR
BYTES=$(wc -c < "${APP}.bin")
if [ "$BYTES" -gt 4096 ]; then
printf '\r 🚨 %-13s OVERFLOWS 4 KiB (%d B) \n' "$APP_NAME" "$BYTES"; exit 1
fi
printf '\r ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app \n' \
"$APP_NAME" "$BYTES" "$(( BYTES * 100 / 4096 ))" "$OUT"
+863
View File
@@ -0,0 +1,863 @@
/* Copyright 2026 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* CW Keyer - self-contained A1A-style manual keyer overlay.
*
* PTT is always a straight key. SIDE1/SIDE2 are either straight keys or a
* single-paddle-at-a-time automatic keyer. The existing overlay ABI reports
* only one key, so true iambic squeeze detection is intentionally not claimed.
*
* The BK4829 VRAMP PA-bias byte in REG_36 is stepped across four milliseconds
* at each edge. The board PA GPIO is enabled before the rise and disabled after
* the fall. Audio, tone and sub-audio paths remain muted throughout a session.
*/
#include <stdbool.h>
#include <stdint.h>
#include "../app_api.h"
#include "cwkeyer_assets.h"
#define CFG_MAGIC_V1 0xC7u
#define CFG_MAGIC_V2 0xC8u
#define CFG_MAGIC_V3 0xC9u
#define CFG_MAGIC 0xCAu
#define WPM_DEFAULT 18u
#define WPM_MIN 5u
#define WPM_MAX 40u
#define CAL_DEFAULT 277
#define CAL_MIN -999
#define CAL_MAX 999
#define RX_PITCH_DEFAULT 700u
#define RX_PITCH_MIN 300u
#define RX_PITCH_MAX 900u
#define RX_PITCH_STEP 50u
#define BREAKIN_DEFAULT 300u
#define BREAKIN_MIN 250u
#define BREAKIN_MAX 1500u
#define BREAKIN_STEP 50u
#define TUNE_STEP_10HZ 10
#define TUNE_MAX_10HZ 5000
#define UI_DEBOUNCE_MS 20u
#define NAV_REPEAT_DELAY_MS 400u
#define NAV_REPEAT_MS 100u
#define RAMP_MS 4u
#define PLL_LOCK_MS 2u
#define MARK_MAX_MS 10000u
#define SERVICE_MS 10u
#define BATTERY_MS 500u
#define F_X 72u
_Static_assert(1200u / WPM_MAX > 2u * RAMP_MS,
"CW mark must remain longer than both RF ramps");
#define REG_TX_LINK 0x30u
#define REG_AM_CTRL 0x31u
#define REG_PA_BIAS 0x36u
#define REG_FREQ_LOW 0x38u
#define REG_FREQ_HIGH 0x39u
#define REG_AUDIO_FILTER 0x3Du
#define REG_AUDIO_GAIN 0x48u
#define REG_SUBAUDIO 0x51u
#define REG_AUDIO_1 0x54u
#define REG_AUDIO_2 0x55u
#define REG_AFC 0x73u
#define REG_RX_FILTER_1 0x2Au
#define REG_RX_FILTER_2 0x2Bu
#define REG_RX_FILTER_3 0x2Fu
#define REG_TONE 0x70u
#define REG_RX_LEVEL 0x42u
#define AF_USB 5u
#define TX_LINK_OFF 0x0000u
#define TX_LINK_NO_MIC 0xC3FAu
#define TX_LINK_NO_PA 0xC3F2u
/* These values are already returned by get_key(), but API level 2 only names
* codes through PTT. They mirror KEY_SIDE2=17 and KEY_SIDE1=18. */
#define KEY_SIDE2 17u
#define KEY_SIDE1 18u
enum { MODE_STRAIGHT, MODE_PADDLE };
typedef struct {
uint8_t magic, wpm, mode, reverse;
int16_t calCentiPpm;
uint16_t rxPitchHz, breakInMs;
uint8_t speaker, reserved;
} config_t;
_Static_assert(sizeof(config_t) <= 16u, "CW Keyer config exceeds overlay slot");
static struct {
const app_api_t *A;
bool running, txReady, keyDown, denied, fArm, redraw, statusRedraw;
bool help, uiHandled;
bool reverse, speaker;
uint8_t wpm, mode, uiCandidate, paBias, paControl;
int16_t calCentiPpm, tuneOffset10Hz;
uint16_t rxPitchHz, breakInMs;
uint16_t serviceMs, batteryMs, markMs, hangElapsedMs;
uint32_t uiCandidateAt, navRepeatAt;
} g;
#define A (g.A)
static const uint32_t places[] = {
1000000000u, 100000000u, 10000000u, 1000000u, 100000u,
10000u, 1000u, 100u, 10u, 1u
};
static char *text(char *buf, uint16_t offset)
{
A->asset_read(offset, buf, TEXT_MAX);
return buf;
}
static uint8_t slen(const char *s)
{
uint8_t n = 0u;
while (s[n])
n++;
return n;
}
static char *put(char *out, const char *s)
{
while (*s)
*out++ = *s++;
return out;
}
static char *putu(char *out, uint32_t value)
{
bool started = false;
for (uint8_t i = 0u; i < sizeof(places) / sizeof(places[0]); i++) {
uint8_t digit = 0u;
while (value >= places[i]) {
value -= places[i];
digit++;
}
if (digit || started || places[i] == 1u) {
*out++ = (char)('0' + digit);
started = true;
}
}
return out;
}
static char *putCalibration(char *out)
{
int16_t value = g.calCentiPpm;
*out++ = value < 0 ? '-' : '+';
uint16_t magnitude = (uint16_t)(value < 0 ? -value : value);
const uint64_t qr = A->uidivmod(magnitude, 100u);
out = putu(out, (uint32_t)qr);
*out++ = '.';
uint8_t fraction = (uint8_t)(qr >> 32);
uint8_t tens = 0u;
while (fraction >= 10u) {
fraction -= 10u;
tens++;
}
*out++ = (char)('0' + tens);
*out++ = (char)('0' + fraction);
return out;
}
/* Calibration is stored in hundredths of one ppm. Positive means the radio's
* measured carrier is high, so the programmed synthesizer word is reduced. */
static uint32_t calibratedFrequency(uint32_t frequency)
{
const int16_t signedCal = g.calCentiPpm;
const uint16_t cal = (uint16_t)(signedCal < 0 ? -signedCal : signedCal);
if (cal == 0u)
return frequency;
uint64_t qr = A->uidivmod(frequency, 100000u);
const uint32_t wholeMHz = (uint32_t)qr;
const uint32_t fraction = (uint32_t)(qr >> 32);
qr = A->uidivmod(wholeMHz * cal, 1000u);
uint32_t correction = (uint32_t)qr;
const uint32_t tail = (uint32_t)(qr >> 32) * 100000u + fraction * cal;
correction += (uint32_t)A->uidivmod(tail + 50000000u, 100000000u);
if (signedCal > 0)
return correction < frequency ? frequency - correction : 0u;
return frequency + correction;
}
static uint32_t tunedFrequency(uint32_t frequency)
{
const int16_t offset = g.tuneOffset10Hz;
if (offset < 0) {
const uint16_t delta = (uint16_t)-offset;
return frequency > delta ? frequency - delta : 0u;
}
const uint16_t delta = (uint16_t)offset;
return frequency <= 0xFFFFFFFFu - delta ? frequency + delta : 0xFFFFFFFFu;
}
static void configureCwRx(void)
{
uint32_t frequency = calibratedFrequency(tunedFrequency(A->rx_freq()));
const uint32_t pitch = (uint32_t)A->uidivmod(g.rxPitchHz, 10u);
if (frequency > pitch)
frequency -= pitch;
A->bk_write(REG_FREQ_LOW, (uint16_t)frequency);
A->bk_write(REG_FREQ_HIGH, (uint16_t)(frequency >> 16));
const uint16_t rxLink = A->bk_read(REG_TX_LINK);
A->bk_write(REG_TX_LINK, TX_LINK_OFF);
A->bk_write(REG_TX_LINK, rxLink);
/* Use the BK4829 USB baseband output as a CW product detector. Tuning the
* receiver below the carrier produces the configured receive tone. */
A->bk_write(REG_AM_CTRL, A->bk_read(REG_AM_CTRL) & 0xFFFEu);
A->bk_write(REG_RX_LEVEL, 0x6B5Au);
A->bk_write(REG_RX_FILTER_1, 0x7400u);
A->bk_write(REG_RX_FILTER_2, 0x0000u);
A->bk_write(REG_RX_FILTER_3, 0x9890u);
A->bk_write(REG_AUDIO_1, 0x9009u);
A->bk_write(REG_AUDIO_2, 0x31A9u);
A->bk_write(REG_AUDIO_GAIN,
(uint16_t)((A->bk_read(REG_AUDIO_GAIN) & 0xFFF0u) | 0x000Fu));
A->bk_write(REG_AUDIO_FILTER, 0u);
A->bk_write(REG_AFC, A->bk_read(REG_AFC) | 0x0010u);
A->set_agc(true);
A->set_af(AF_USB);
/* CW must not use the FM squelch gate: its opening delay clips dits and
* repeatedly chops the recovered tone at every carrier transition. */
A->audio_path(g.speaker);
}
static void returnToRx(void)
{
A->tx_end();
g.txReady = false;
g.hangElapsedMs = 0u;
configureCwRx();
g.statusRedraw = true;
}
static void formatFrequency(char *out)
{
char *end = putu(out, tunedFrequency(A->tx_freq()));
uint8_t len = (uint8_t)(end - out);
*end = '\0';
if (len <= 5u)
return;
for (uint8_t i = (uint8_t)(len + 1u); i > len - 5u; i--)
out[i] = out[i - 1u];
out[len - 5u] = '.';
}
static uint16_t dotMs(void)
{
return (uint16_t)(uint32_t)A->uidivmod(1200u, g.wpm);
}
static bool isSide(uint8_t key)
{
return key == KEY_SIDE1 || key == KEY_SIDE2;
}
static uint8_t ditKey(void)
{
return g.reverse ? KEY_SIDE2 : KEY_SIDE1;
}
static void drawConfigTag(const char *s, bool right, uint8_t line)
{
A->print_inverse(s, right ? 87u : 2u, line, false, true,
right ? 127u : 42u);
}
static void drawStatus(uint16_t title, uint8_t titleChars)
{
A->status_clear();
char t[TEXT_MAX];
A->print_inverse(text(t, title), 2u, 0u, true, true,
(uint8_t)(2u + titleChars * 4u));
A->draw_battery();
}
static void drawBigFrequency(char *s)
{
formatFrequency(s);
const uint8_t n = slen(s);
if (n < 3u) {
A->print_normal(s, (uint8_t)((128u - n * 7u) >> 1), 127u, 1u);
return;
}
const uint8_t width = (uint8_t)((n - 3u) * 13u + 17u);
const uint8_t x = (uint8_t)((128u - width) >> 1);
char tail[3] = { s[n - 2u], s[n - 1u], '\0' };
s[n - 2u] = '\0';
A->display_freq(s, x, 0u, false);
/* End must stay zero as in APRS; a larger End re-centers the small tail. */
A->print_normal(tail, (uint8_t)(x + (n - 3u) * 13u + 3u), 0u, 1u);
}
static void drawMainStatus(void)
{
char t[TEXT_MAX];
drawStatus(T_TITLE, T_TITLE_CHARS);
if (g.denied || !g.txReady) {
text(t, g.denied ? T_DENIED : T_READY);
A->print_inverse(t, 43u, 0u, true, true,
(uint8_t)(43u + slen(t) * 4u));
}
if (g.fArm)
A->asset_read(BMP_F, A->status_line + F_X, BMP_F_LEN);
else if (g.speaker)
A->asset_read(BMP_SPEAKER, A->status_line + F_X, BMP_SPEAKER_LEN);
}
static void drawMain(void)
{
char s[24], right[24], t[TEXT_MAX];
drawMainStatus();
drawBigFrequency(s);
char *out = put(s, text(t, g.mode == MODE_PADDLE ? T_PADDLE : T_STRAIGHT));
*out = '\0';
out = put(right, text(t, T_SPEED));
out = putu(out, g.wpm);
*out = '\0';
drawConfigTag(s, false, 4u);
drawConfigTag(right, true, 4u);
out = put(s, text(t, T_RX_PITCH));
out = putu(out, g.rxPitchHz);
*out = '\0';
out = put(right, text(t, T_BREAKIN));
out = putu(out, g.breakInMs);
*out = '\0';
drawConfigTag(s, false, 5u);
drawConfigTag(right, true, 5u);
out = put(s, g.reverse ? "DAH-DIT" : "DIT-DAH");
*out = '\0';
out = put(right, text(t, T_XTAL));
out = putCalibration(out);
*out = '\0';
drawConfigTag(s, false, 6u);
drawConfigTag(right, true, 6u);
}
static void drawHelp(void)
{
char t[TEXT_MAX];
drawStatus(T_TITLE, T_TITLE_CHARS);
A->print_inverse(text(t, T_HELP), 43u, 0u, true, true,
(uint8_t)(43u + slen(t) * 4u));
for (uint8_t i = 0u; i < 6u; i++) {
A->print_inverse(text(t, T_HELP_LEFT + i * T_HELP_LEFT_STRIDE),
2u, (uint8_t)(i + 1u), false, true, 42u);
text(t, T_HELP_RIGHT + i * T_HELP_RIGHT_STRIDE);
if (t[0])
A->print_inverse(t, 79u, (uint8_t)(i + 1u), false, true, 127u);
}
A->print_tiny(text(t, T_HELP_STEP), 56u, 49u, false, true);
}
static void draw(void)
{
A->display_clear();
if (g.help)
drawHelp();
else
drawMain();
A->blit_status();
A->blit_full();
g.redraw = false;
g.statusRedraw = false;
}
static void redrawMainStatus(void)
{
drawMainStatus();
A->blit_status();
g.statusRedraw = false;
}
static void writeBias(uint8_t bias)
{
A->bk_write(REG_PA_BIAS, (uint16_t)((uint16_t)bias << 8) | g.paControl);
}
static void rampUp(void)
{
const uint8_t quarter = (uint8_t)(g.paBias >> 2);
const uint8_t half = (uint8_t)(g.paBias >> 1);
A->bk_write(REG_TX_LINK, TX_LINK_NO_MIC);
A->tx_carrier(true);
writeBias(quarter); A->delay_ms(1u);
writeBias(half); A->delay_ms(1u);
writeBias((uint8_t)(g.paBias - quarter)); A->delay_ms(1u);
writeBias(g.paBias); A->delay_ms(1u);
}
static void rampDown(void)
{
const uint8_t quarter = (uint8_t)(g.paBias >> 2);
const uint8_t half = (uint8_t)(g.paBias >> 1);
writeBias((uint8_t)(g.paBias - quarter)); A->delay_ms(1u);
writeBias(half); A->delay_ms(1u);
writeBias(quarter); A->delay_ms(1u);
writeBias(0u); A->delay_ms(1u);
A->tx_carrier(false);
A->bk_write(REG_TX_LINK, TX_LINK_NO_PA);
}
static bool prepareTx(void)
{
if (A->tx_state() != 0u) {
g.denied = true;
g.redraw = true;
return false;
}
A->audio_path(false);
A->tx_set_params();
const uint16_t pa = A->bk_read(REG_PA_BIAS);
g.paBias = (uint8_t)(pa >> 8);
g.paControl = (uint8_t)pa;
/* tx_set_params is atomic and returns with RF enabled. Remove PA drive
* immediately, apply the calibrated frequency, then start every mark with
* the same controlled ramp. REG_38/39 use 10 Hz synthesizer units. */
A->tx_carrier(false);
writeBias(0u);
/* REG_50 mutes modulation, not the RF carrier. Keep it muted for the whole
* keying session, remove every modulation source, and disable the mic ADC.
* RF is keyed with internal PA gain, external PA bias and PA_ENABLE. */
A->tx_mute(true);
A->bk_write(REG_SUBAUDIO, 0u);
A->bk_write(REG_TONE, 0u);
const uint32_t frequency = calibratedFrequency(tunedFrequency(A->tx_freq()));
A->bk_write(REG_FREQ_LOW, (uint16_t)frequency);
A->bk_write(REG_FREQ_HIGH, (uint16_t)(frequency >> 16));
/* REG_38/39 are written after tx_set_params, so restart the TX link to
* recalibrate the VCO and latch the corrected synthesizer frequency. Keep
* internal PA gain disabled when the link comes back up. */
A->bk_write(REG_TX_LINK, TX_LINK_OFF);
A->bk_write(REG_TX_LINK, TX_LINK_NO_PA);
A->delay_ms(PLL_LOCK_MS);
g.txReady = true;
g.denied = false;
g.statusRedraw = true;
return true;
}
static bool keyOn(void)
{
if (g.txReady && A->tx_state() != 0u) {
returnToRx();
g.denied = true;
return false;
}
if (!g.txReady && !prepareTx())
return false;
rampUp();
g.keyDown = true;
g.markMs = 0u;
g.hangElapsedMs = 0u;
return true;
}
static void keyOff(void)
{
rampDown();
g.keyDown = false;
}
static void disarm(void)
{
if (g.keyDown)
keyOff();
if (g.txReady)
returnToRx();
g.denied = false;
g.statusRedraw = true;
}
static void serviceOneMs(void)
{
A->delay_ms(1u);
if (g.keyDown)
g.hangElapsedMs = 0u;
if (g.keyDown && ++g.markMs >= MARK_MAX_MS) {
keyOff();
if (g.txReady)
returnToRx();
g.denied = true;
g.statusRedraw = true;
}
if (g.txReady && !g.keyDown && ++g.hangElapsedMs >= g.breakInMs) {
returnToRx();
}
if (++g.serviceMs >= SERVICE_MS) {
g.serviceMs = 0u;
A->backlight_update();
g.batteryMs = (uint16_t)(g.batteryMs + SERVICE_MS);
if (g.batteryMs >= BATTERY_MS) {
g.batteryMs = 0u;
if (!g.txReady) {
A->battery_sample();
g.statusRedraw = true;
}
/* A sleeping overlay consumes the first SIDE press as a wake key.
* Keep the keyer awake so a dit or dah is never discarded. */
A->backlight_on();
}
}
}
/* Wait without touching the display. Opposite-paddle taps are remembered, and
* EXIT remains an immediate RF-safe abort even during a dah. */
static bool keyerWait(uint16_t ms, uint8_t current, uint8_t *memory)
{
while (ms-- && g.running) {
serviceOneMs();
const uint8_t key = A->get_key();
if (key == APP_KEY_EXIT) {
g.running = false;
return false;
}
if (isSide(key) && key != current)
*memory = key;
}
return g.running;
}
static bool sendElement(uint8_t key, uint8_t *memory)
{
if (!keyOn())
return false;
const uint16_t dot = dotMs();
uint16_t mark = dot;
if (key != ditKey())
mark = (uint16_t)(mark * 3u);
const uint16_t holdMs = (uint16_t)(mark - 2u * RAMP_MS);
const bool complete = keyerWait(holdMs, key, memory);
keyOff();
if (!complete)
return false;
return keyerWait(dot, key, memory);
}
static void runKeyer(uint8_t first)
{
uint8_t current = first;
while (g.running && isSide(current)) {
uint8_t memory = APP_KEY_INVALID;
if (!sendElement(current, &memory))
break;
if (isSide(memory)) {
current = memory;
continue;
}
current = A->get_key();
}
}
static bool adjustFrequency(int8_t direction)
{
if (direction > 0) {
if (g.tuneOffset10Hz > TUNE_MAX_10HZ - TUNE_STEP_10HZ)
return false;
g.tuneOffset10Hz += TUNE_STEP_10HZ;
} else if (direction < 0) {
if (g.tuneOffset10Hz < -TUNE_MAX_10HZ + TUNE_STEP_10HZ)
return false;
g.tuneOffset10Hz -= TUNE_STEP_10HZ;
} else {
return false;
}
if (g.txReady) {
A->tx_end();
g.txReady = false;
g.hangElapsedMs = 0u;
}
configureCwRx();
g.denied = false;
return true;
}
static void adjustSetting(uint8_t key, int8_t direction)
{
bool retune = false;
switch (key) {
case APP_KEY_0:
g.speaker = !g.speaker;
A->audio_path(g.speaker && !g.txReady);
break;
case APP_KEY_1:
g.mode ^= 1u;
break;
case APP_KEY_2:
if (direction > 0) {
if (g.wpm >= WPM_MAX)
return;
g.wpm++;
} else {
if (g.wpm <= WPM_MIN)
return;
g.wpm--;
}
break;
case APP_KEY_3:
if (direction > 0 && g.rxPitchHz <= RX_PITCH_MAX - RX_PITCH_STEP) {
g.rxPitchHz = (uint16_t)(g.rxPitchHz + RX_PITCH_STEP);
} else if (direction < 0 &&
g.rxPitchHz >= RX_PITCH_MIN + RX_PITCH_STEP) {
g.rxPitchHz = (uint16_t)(g.rxPitchHz - RX_PITCH_STEP);
} else {
return;
}
retune = true;
break;
case APP_KEY_4:
if (direction > 0 && g.breakInMs <= BREAKIN_MAX - BREAKIN_STEP) {
g.breakInMs = (uint16_t)(g.breakInMs + BREAKIN_STEP);
} else if (direction < 0 &&
g.breakInMs >= BREAKIN_MIN + BREAKIN_STEP) {
g.breakInMs = (uint16_t)(g.breakInMs - BREAKIN_STEP);
} else {
return;
}
break;
case APP_KEY_5:
g.reverse = !g.reverse;
break;
case APP_KEY_6:
if (direction > 0) {
if (g.calCentiPpm >= CAL_MAX)
return;
g.calCentiPpm++;
} else {
if (g.calCentiPpm <= CAL_MIN)
return;
g.calCentiPpm--;
}
retune = true;
break;
default:
return;
}
if (retune) {
if (g.txReady)
returnToRx();
else
configureCwRx();
}
g.redraw = true;
}
static void handleUiKey(uint8_t key)
{
if (g.help) {
if (key == APP_KEY_EXIT || key == APP_KEY_MENU) {
g.help = false;
g.redraw = true;
}
return;
}
int8_t direction = A->nav_dir(key);
if (direction != 0) {
if (adjustFrequency(direction))
g.redraw = true;
g.fArm = false;
g.statusRedraw = true;
return;
}
switch (key) {
case APP_KEY_EXIT:
g.running = false;
break;
case APP_KEY_MENU:
disarm();
g.fArm = false;
g.help = true;
g.redraw = true;
break;
case APP_KEY_F:
g.fArm = !g.fArm;
g.statusRedraw = true;
break;
case APP_KEY_0: case APP_KEY_1: case APP_KEY_2: case APP_KEY_3:
case APP_KEY_4: case APP_KEY_5: case APP_KEY_6:
adjustSetting(key, g.fArm ? -1 : 1);
g.fArm = false;
g.statusRedraw = true;
break;
default:
break;
}
}
static void resetUiInput(void)
{
g.uiCandidate = APP_KEY_INVALID;
g.uiHandled = false;
g.navRepeatAt = 0u;
}
static void handleUiInput(uint8_t key)
{
const uint32_t now = A->ticks_ms();
const int8_t direction = A->nav_dir(key);
if (key != g.uiCandidate) {
g.uiCandidate = key;
g.uiCandidateAt = now;
g.uiHandled = false;
g.navRepeatAt = 0u;
return;
}
if (!g.uiHandled) {
if ((uint32_t)(now - g.uiCandidateAt) < UI_DEBOUNCE_MS)
return;
A->backlight_on();
handleUiKey(key);
g.uiHandled = true;
if (direction != 0)
g.navRepeatAt = now + NAV_REPEAT_DELAY_MS;
return;
}
if (direction != 0 && (int32_t)(now - g.navRepeatAt) >= 0) {
A->backlight_on();
handleUiKey(key);
g.navRepeatAt = now + NAV_REPEAT_MS;
}
}
static void loadConfig(void)
{
config_t cfg;
A->cfg_load((uint8_t *)&cfg, sizeof(cfg));
if ((cfg.magic == CFG_MAGIC || cfg.magic == CFG_MAGIC_V3 ||
cfg.magic == CFG_MAGIC_V2 || cfg.magic == CFG_MAGIC_V1) &&
cfg.wpm >= WPM_MIN && cfg.wpm <= WPM_MAX &&
cfg.mode <= MODE_PADDLE && cfg.reverse <= 1u) {
g.wpm = cfg.wpm;
g.mode = cfg.mode;
g.reverse = cfg.reverse;
g.calCentiPpm = (cfg.magic == CFG_MAGIC || cfg.magic == CFG_MAGIC_V3 ||
cfg.magic == CFG_MAGIC_V2) &&
cfg.calCentiPpm >= CAL_MIN && cfg.calCentiPpm <= CAL_MAX
? cfg.calCentiPpm : CAL_DEFAULT;
} else {
g.wpm = WPM_DEFAULT;
g.mode = MODE_STRAIGHT;
g.reverse = false;
g.calCentiPpm = CAL_DEFAULT;
}
g.rxPitchHz = (cfg.magic == CFG_MAGIC || cfg.magic == CFG_MAGIC_V3) &&
cfg.rxPitchHz >= RX_PITCH_MIN && cfg.rxPitchHz <= RX_PITCH_MAX
? cfg.rxPitchHz : RX_PITCH_DEFAULT;
g.breakInMs = (cfg.magic == CFG_MAGIC || cfg.magic == CFG_MAGIC_V3) &&
cfg.breakInMs >= BREAKIN_MIN && cfg.breakInMs <= BREAKIN_MAX
? cfg.breakInMs : BREAKIN_DEFAULT;
g.speaker = cfg.magic == CFG_MAGIC && cfg.speaker <= 1u
? cfg.speaker : true;
}
static void saveConfig(void)
{
const config_t cfg = {
CFG_MAGIC, g.wpm, g.mode, (uint8_t)g.reverse, g.calCentiPpm,
g.rxPitchHz, g.breakInMs, (uint8_t)g.speaker, 0u
};
A->cfg_save((const uint8_t *)&cfg, sizeof(cfg));
}
__attribute__((section(".text.entry"), used))
void app_main(const app_api_t *api)
{
A = api;
g.running = true;
g.redraw = true;
g.statusRedraw = false;
g.help = false;
resetUiInput();
loadConfig();
A->backlight_on();
configureCwRx();
draw();
while (g.running) {
uint8_t key = A->get_key();
if (key == APP_KEY_SAVER) {
resetUiInput();
serviceOneMs();
continue;
}
if (key == APP_KEY_WAKE)
key = APP_KEY_INVALID;
/* A denied key-down is latched until every keying control is released;
* otherwise a held PTT would call tx_state and repaint every millisecond. */
if (g.denied) {
if (key == APP_KEY_PTT || isSide(key)) {
resetUiInput();
serviceOneMs();
continue;
}
g.denied = false;
g.statusRedraw = true;
}
const bool straight = !g.help && (key == APP_KEY_PTT ||
(g.mode == MODE_STRAIGHT && isSide(key)));
if (straight) {
resetUiInput();
if (!g.keyDown)
keyOn();
} else if (g.keyDown) {
keyOff();
resetUiInput();
} else if (!g.help && g.mode == MODE_PADDLE && isSide(key)) {
resetUiInput();
A->backlight_on();
runKeyer(key);
} else if (key != APP_KEY_INVALID) {
handleUiInput(key);
} else {
resetUiInput();
}
if (g.redraw && !g.keyDown)
draw();
else if (g.statusRedraw && !g.help && !g.keyDown)
redrawMainStatus();
serviceOneMs();
}
disarm();
saveConfig();
A->set_af(APP_AF_MUTE);
A->audio_path(false);
}
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""CW Keyer read-only UI assets."""
import os
import sys
sys.dont_write_bytecode = True
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, ".."))
from app_assets import Assets
TITLE = "CW KEYER"
a = Assets("CWKEYER")
a.text("T_TITLE", TITLE)
a.text("T_HELP", "HELP")
a.text("T_HELP_STEP", "Tuning step 100 Hz")
a.text("T_READY", "RX")
a.text("T_DENIED", "TX OFF")
a.text("T_STRAIGHT", "STRAIGHT")
a.text("T_PADDLE", "PADDLE")
a.text("T_SPEED", "WPM ")
a.text("T_RX_PITCH", "TONE ")
a.text("T_BREAKIN", "BK ")
a.text("T_XTAL", "XTAL ")
a.table("T_HELP_LEFT", [
"0 SPEAKER", "1 MODE", "2 WPM", "3 TONE", "4 BREAK-IN", "5 ORDER",
])
a.table("T_HELP_RIGHT", [
"6 XTAL", "F+ DECREASE", "UP/DN FREQ", "PTT STRAIGHT", "F1/F2 PADDLE", "",
])
a.u8("BMP_F", [0x3E, 0x7F, 0x41, 0x75, 0x75, 0x75, 0x7D, 0x7F, 0x3E])
a.u8("BMP_SPEAKER", [0x1C, 0x1C, 0x3E, 0x7F, 0x00,
0x22, 0x1C, 0x41, 0x22, 0x1C])
a.const("T_TITLE_CHARS", len(TITLE))
a.main()
+108 -19
View File
@@ -32,16 +32,16 @@ shown immediately from its first detail row. The history is lost on exit.
| Status bar | `EPIRB 406`, speaker icon while enabled, and arrows when details or another history entry exist above/below |
| Before the first frame | Blinking `WAITING...` in the body |
| Fixed line 0 | 15-hex ID in bold and, with several messages, the selected/newest count (`2/5`) at its right |
| Scrolled body | Country and protocol; latitude and longitude together on one row when they fit, otherwise on two rows in normal view; SELF-TEST and LONG/SHORT; BCH-1/BCH-2 (`BCH OK / OK`, with `--` on a short message); internal/external source and flags on their own row when present (`EXT 121`, `INT`, `COARSE`, `RAW ID`), then the frame sequence and burst RSSI on the row below (`FRAME 3 -95dBm`). `121` abbreviates the 121.5 MHz homing transmitter. Both views always keep 5 coordinate decimals. Normal view puts BCH on its own line without indentation; compact view appends it to the SELF-TEST / LONG/SHORT row |
| Scrolled body | Country and protocol; latitude and longitude together on one row when they fit, otherwise on two rows in normal view; right below, the position source and flags when present (`EXT 121`, `INT`, `COARSE`, `RAW ID` for a spare location code 0000/0001, `CANCEL` for an ELT(DT) cancellation message), so they show without scrolling; SELF-TEST and LONG/SHORT; then the frame sequence and burst RSSI (`FR 3 -95dBm`). `121` abbreviates the 121.5 MHz homing transmitter. Both views always keep 5 coordinate decimals |
| Dotted separator | APRS-style line at y=40, above the two fixed information rows |
| Fixed tuning row | Current RX frequency prefixed by `RX` and its offset from the VFO, updated immediately by keys 4/5/6; current RSSI and noise floor in dBm are shown as `-40 / -100dBm` at the right |
| Bottom row | Complete frames (`FRAME`), failed captures (`ERROR`), last error: `NOSYNC` (no frame sync found) or `CUT` (sync found, message incomplete) |
| Bottom row | Complete frames (`FR`), failed captures (`ER`), last error: `NS` (no frame sync found) or `CUT` (sync found, message incomplete); at the right, BCH-1/BCH-2 of the selected message (`BCH OK/OK`, `BCH ER/ER`, or `--` on a short message) |
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 |
@@ -129,10 +129,28 @@ then 112 (short) or 144 (long) bits, one burst about every 50 s.
| 107-132 | Fine position offsets and flags (PDF-2, long messages) |
| 133-144 | BCH-2, BCH(26,14) over bits 107-144 |
Standard location protocols (codes 0010-0111, 1100, 1110): bits 41-64
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.
Every position format of C/S T.001 Issue 4 Rev. 11 (October 2023, Annex A3.3) has
the same shape: N/S flag, 7 bits of latitude degrees, sub-degree bits, E/W flag,
8 bits of longitude degrees, sub-degree bits; then, in PDF-2, an optional offset
per axis (sign, 1 = plus; minutes; seconds in 4 s steps, `1111` = no offset).
Since v2.2 the parser is one routine driven by this table (`FMT` in `dec406.c`):
| Format (T.001) | Codes | Position | Steps | Offsets | Source / 121.5 |
|---|---|---|---|---|---|
| Standard location (A3.3.5) | 0010-0111, 1100, 1110 | 65-85 | 15' | 113-132, 5-bit minutes, if 107-110 = `1101` | 111 / 112 |
| National location (A3.3.6) | 1000, 1010, 1011, 1111 | 59-85 | 2' | 113-126, 2-bit minutes, if 107-110 = `1101` (bit 110: position data flag) | 111 / 112 |
| RLS location (A3.3.7) | 1101 | 67-85 | 30' | 115-132, 4-bit minutes | 107 / 108 |
| ELT(DT) location (A3.3.8) | 1001 | 67-85 | 30' | 115-132, 4-bit minutes, unless 113-114 = `00` (rotating field, e.g. operator 3LD) | - |
| User-location (A3.3.4) | user 010, 110, 001, 011, 111, long | PDF-2 108-132 | 4' | - | 107 / - |
The 15-hex ID is bits 26-85 (section 3.2), with a PDF-1 position replaced by its
default value (A3.2: flags 0, degrees all ones, sub-degree bits all ones except in
national location). A latitude above 90 degrees means no position: the default
value, or the fixed pattern of the ELT(DT) cancellation message (A3.3.8.5, shown
as `CANCEL`). User protocols carry no position except user-location, whose
position is only used with a valid BCH-2. Not displayed: ELT(DT) activation,
altitude and location freshness, the RLS return-link bits, the 3LD letters and the
user-protocol identities (MMSI, call sign, registration).
## Decoder (`dec406.c`)
@@ -150,7 +168,8 @@ freestanding (no libc). About 150 bytes of state.
receive chain may invert). Up to 2 mismatches allowed.
5. **Bits**: soft decision per bit, first half minus second half.
6. **Parsing**: BCH-1 and BCH-2 syndromes, country, protocol, 15-hex ID, position
for standard location protocols (coarse + fine offsets).
(coarse + fine offsets) for every location format (table above). The parse
leaves the PDF-1 position bits at their default value: parse a message once.
Size for Cortex-M0+ (firmware toolchain, `-Os`): 1,468 bytes of code, after the
size work described above.
@@ -168,7 +187,30 @@ test/run_tests.sh
raised-cosine transitions, carrier offset, noise in a 25 kHz channel, FM
discriminator, audio low-pass, AC coupling, 9.6 kHz ADC with clock error,
12-bit around the measured 518 bias, full-scale noise when no carrier.
- `host_dec406.c` runs the decoder over the samples and prints each message.
- `host_dec406.c` runs the decoder over the samples and prints each message;
`host_dec406 -x HEX` parses a frame directly, without the audio chain.
### Every T.001 coding option (`test/t001frames.py`)
`t001frames.py` builds one frame per coding option of T.001 Rev. 11 Annex A (61
frames): user protocols (maritime MMSI and call sign, radio call sign, aviation,
the six serial types, test, national, orbitography, emergency codes, self-test),
user-location, standard location (all identities, S/W, default offsets, default
position, self-test), national location (with and without offsets), RLS (TAC,
MMSI, test, return-link bits) and ELT(DT) (24-bit address, operator, TAC, test,
3LD rotating field, default position, GNSS self-test, cancellation). Each frame
carries the 15-hex ID and position the specification gives; the BCH code is
checked against both worked examples of Annex B. Test positions: 49.07624 N,
0.73018 E and 33 52'08" S, 70 39'28" W.
```
test/t001check.py --fast # parse the bits directly (seconds)
test/t001check.py # through gen406.py audio + the demodulator (minutes)
test/t001flipper.py # test/flipper/t001/*.sub + README index
test/limesdr/t001lime.py # the same frames as LimeSDR IQ files (see below)
```
v2.2: 61/61 match, both ways.
Results (2026-09-27): 29/29 pass. The first 17 cases, covering CNR 12-15 dB, ±5 kHz offset,
inverted chain, ±3000 ppm clock error, 50-250 µs rise time, 3 kHz audio
@@ -199,13 +241,61 @@ 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
on 406.0-406.1 MHz.
## LimeSDR test transmitter (`test/limesdr/`)
The Flipper sends 2-FSK; a LimeSDR (Mini) sends the real beacon modulation,
±1.1 rad biphase-L with 150 µs transitions, so the receiver's discriminator
outputs pulses as with a real EPIRB and the app's integrator is exercised as in
the field. Needs numpy and SoapySDR with the LimeSuite driver.
```
cd test/limesdr
./t001lime.py # t001/psk and t001/fsk: 61 bursts each, cs16, 1 MS/s
./txall.py --dry-run # the sequence and what the app should show
./txall.py # every psk frame on 433.650 MHz, 3 s apart
./txall.py --only 'eltdt_*' --delay 5 # one family
./limetx.py t001/psk/nl_epirb.cs16 --format cs16 --rate 1e6 --freq 433.65e6 --repeat 5 --gap 2.5
```
- `t001lime.py` writes the 61 `t001frames.py` frames as IQ files (`psk/`: real
modulation, `fsk/`: the Flipper signal), carrier at 0 Hz, plus `t001/README.md`,
the index of expected IDs and positions that `txall.py` prints before each
burst. The `.cs16` files (181 MB per mode) are not in git: regenerate them.
- `limetx.py` transmits one IQ file (LO 250 kHz below the signal, shifted back
digitally so the LO leakage stays off the channel); `txall.py` opens the radio
once and sends a folder, one burst per file. Default TX gain 30 dB.
- Checked on the host: the 61 psk files, through an FM discriminator and
dec406, give the expected ID and position. Set the radio to 433.650 MHz FM
wide and launch EPIRB 406 before transmitting.
`limetx.py` refuses 405.9-406.2 MHz: **never transmit on 406.0-406.1 MHz**, the
Cospas-Sarsat satellites relay anything there as a real distress alert.
### Validation of v2.3 (2026-10-08)
All the tests that validate v2.3 were made over the air with the **psk** IQ
files (real 406 modulation), transmitted by a LimeSDR with `txall.py` on
433.650 MHz, the radio in FM wide running EPIRB 406 v2.3: the 61 frames of
`t001/`, one burst each, covering every coding option of C/S T.001 Rev. 11
Annex A. Result: `FRAME 61 ERR 0`, every burst detected, synchronized and
received in full, with no failed capture.
![EPIRB 406 v2.3 after the 61 LimeSDR bursts](test/limesdr/validation_v2.3.png)
Last burst of the series (`ul_test`, user-location test protocol), as expected
in `t001/README.md`: ID `9C7C123456789AB`, `227 User test`, `49.06666N
0.73333E`, `INT COARSE` (internal source, 4-minute PDF-2 position), `LONG`,
`BCH OK/OK`; bottom row `FRAME 61 ERR 0`.
## Bench results
**2026-09-27, UV-K1, generator on 433.650 MHz, DIR mode, first on-air decode.**
@@ -309,13 +399,12 @@ a real bias reproduced on the host at the measured level.
## Open points
- **Default position pattern** in the 15-hex ID (`0 1111111 11 0 11111111 11`)
follows the reference decoder; to confirm against T.001.
- **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.
- **Layouts** checked against T.001 Issue 4 Rev. 11 (v2.2): default position
pattern (A3.2), every location format (A3.3.4-A3.3.8), BCH codes (both Annex B
examples). Over the air with the real 406 modulation (LimeSDR, v2.3): all 61
coding options, 61/61 received. Real-beacon confirmation so far: standard
location (bench generator) and ELT(DT) (issue #616, PlutoSDR frame); national,
RLS and user-location only with generated frames.
- 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 +414,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 source: **v2.4**; size to be measured (v2.3: 4,084 of 4,096 bytes).
+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.4"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
+114 -56
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. */
@@ -146,84 +141,147 @@ bool dec406_push(dec406_t *d, uint16_t sample)
/* ---- parsing ---- */
static uint8_t bit(const dec406_t *d, uint8_t n) /* n = 25..144 */
/* Message bits are numbered as in C/S T.001: 25..144, bit 25 = MSB of b[0]. */
static unsigned bit(const uint8_t *b, unsigned n)
{
uint8_t i = (uint8_t)(n - 25u);
return (uint8_t)((d->bits[i >> 3] >> (7u - (i & 7u))) & 1u);
unsigned i = n - 25u;
return (b[i >> 3] >> (7u - (i & 7u))) & 1u;
}
static uint32_t field(const dec406_t *d, uint8_t first, uint8_t len)
static uint32_t field(const uint8_t *b, unsigned first, unsigned len)
{
uint32_t v = 0;
for (uint8_t i = 0; i < len; i++) v = (v << 1) | bit(d, (uint8_t)(first + i));
while (len--) v = (v << 1) | bit(b, first++);
return v;
}
/* Write v (len bits) at first..first+len-1. */
static void setf(uint8_t *b, unsigned first, unsigned len, uint32_t v)
{
while (len--) {
unsigned i = first + len - 25u, m = 0x80u >> (i & 7u);
if (v & 1u) b[i >> 3] |= m; else b[i >> 3] &= (uint8_t)~m;
v >>= 1;
}
}
/* Remainder of the codeword spanning bits first..last: 0 when valid. */
static uint32_t syndrome(const dec406_t *d, uint8_t first, uint8_t last, uint32_t gen, uint8_t r)
static uint32_t syndrome(const uint8_t *b, unsigned first, unsigned last, uint32_t gen, unsigned r)
{
uint32_t reg = 0;
for (uint8_t n = first; n <= last; n++) {
reg = (reg << 1) | bit(d, n);
for (unsigned n = first; n <= last; n++) {
reg = (reg << 1) | bit(b, n);
if (reg >> r) reg ^= gen;
}
return reg;
}
static bool isStdLoc(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;
}
/* Position formats (T.001 Annex A3.3). Every one is: N/S flag, 7 bits of
* latitude degrees, sub-degree bits, E/W flag, 8 bits of longitude degrees,
* sub-degree bits; then, in PDF-2, an optional offset per axis: sign (1 = +),
* minutes, seconds in 4 s steps (1111 = no offset). */
typedef struct {
uint8_t base; /* first bit of the position */
uint8_t sub; /* sub-degree bits */
uint8_t unit; /* minutes per sub-degree step */
uint8_t off; /* first bit of the offsets, 0 = none */
uint8_t offMin; /* offset minute bits */
uint8_t src; /* position source bit (homing = next), 0 = none */
} pos_fmt_t;
void dec406_parse(const dec406_t *d, dec406_info_t *o)
{
enum { F_STD, F_NAT, F_RLS, F_ELTDT, F_USER, F_NONE };
o->longMsg = bit(d, 25);
static const pos_fmt_t FMT[] = {
[F_STD] = { 65, 2, 15, 113, 5, 111 }, /* A3.3.5 standard location */
[F_NAT] = { 59, 5, 2, 113, 2, 111 }, /* A3.3.6 national location */
[F_RLS] = { 67, 1, 30, 115, 4, 107 }, /* A3.3.7 RLS location */
[F_ELTDT] = { 67, 1, 30, 115, 4, 0 }, /* A3.3.8 ELT(DT) location */
[F_USER] = { 108, 4, 4, 0, 0, 107 }, /* A3.3.4 user-location */
};
/* Location protocol code (bits 37-40) -> format (Table A2-B). */
static const uint8_t LOC_FMT[16] = {
F_NONE, F_NONE, F_STD, F_STD, F_STD, F_STD, F_STD, F_STD,
F_NAT, F_ELTDT, F_NAT, F_NAT, F_STD, F_RLS, F_STD, F_NAT,
};
void dec406_parse(dec406_t *d, dec406_info_t *o)
{
uint8_t *b = d->bits;
o->longMsg = bit(b, 25);
o->selftest = d->selftest;
o->bch1 = syndrome(d, 25, 106, BCH1_GEN, 21) == 0;
o->bch2 = o->longMsg ? syndrome(d, 107, 144, BCH2_GEN, 12) == 0 : 1u;
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;
o->idData = field(d, 41, 24);
o->hasPos = o->hasFine = o->internalPos = o->homing = 0;
o->latS = o->lonS = 0;
o->bch1 = syndrome(b, 25, 106, BCH1_GEN, 21) == 0;
o->bch2 = o->longMsg ? syndrome(b, 107, 144, BCH2_GEN, 12) == 0 : 1u;
o->userProto = bit(b, 26);
o->country = (uint16_t)field(b, 27, 10);
unsigned code = field(b, 37, 4);
o->proto = (uint8_t)(o->userProto ? 16u + (code >> 1) : code); /* name index */
#ifndef DEC406_LEAN /* host test only (not on the radio) */
o->idData = field(b, 41, 24);
#endif
o->hasPos = o->hasFine = o->internalPos = o->homing = 0; /* latS, lonS: only with hasPos */
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);
nib = (uint8_t)((nib << 1) | b);
/* User-location: long user protocols other than orbitography (000),
* national (100) and spare (101), position in PDF-2 (A3.3.4). */
unsigned f = o->userProto ? (o->longMsg && ((0xCEu >> (code >> 1)) & 1u) ? F_USER : F_NONE)
: LOC_FMT[code];
#ifndef DEC406_LEAN
o->stdLoc = f == F_STD;
#endif
o->idRaw = !o->userProto && f == F_NONE; /* spare location code: layout unknown */
int32_t *v = &o->latS; /* latS, then lonS */
unsigned neg = 0;
const pos_fmt_t *p = &FMT[f];
if (f != F_NONE) {
/* Read the position and leave its default value in the message (A3.2:
* flags 0, degrees all ones, sub-degree all ones in standard, RLS and
* ELT(DT) location, zeros in national and user-location). */
unsigned n = p->base, w = 8u + p->sub;
for (unsigned k = 0; k < 2u; k++, n += w++) {
neg |= bit(b, n) << k;
v[k] = (int32_t)(field(b, n + 1u, 7u + k) * 3600u + field(b, n + 8u + k, p->sub) * p->unit * 60u);
unsigned len = 7u + k + p->sub; /* after the flag */
setf(b, n, len + 1u, p->unit > 4u ? (1u << len) - 1u : ((1u << (7u + k)) - 1u) << p->sub);
}
}
for (unsigned k = 0; k < 15; k++) {
unsigned nib = field(b, 26u + 4u * k, 4);
o->id[k] = (char)(nib < 10u ? '0' + nib : 'A' - 10 + nib); /* no hex table */
}
o->id[15] = '\0';
if (!o->stdLoc) return;
/* PDF-2 is only trusted with a valid BCH-2 */
unsigned pdf2 = o->longMsg && o->bch2;
/* No position: latitude beyond 90 deg, i.e. the default (flag N, 127 deg) or
* the ELT(DT) cancellation message (fixed bits 67-85: flag S, 125 deg). */
if (f == F_NONE || (f == F_USER && !pdf2)) return;
if (v[0] > 90 * 3600) { if (f == F_ELTDT && neg & 1u) o->idRaw = 2; 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) {
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;
o->hasFine = 1;
}
/* Offsets: standard and national need bits 107-110 = 1101 (fixed bits and,
* in national, the position data flag); ELT(DT) bits 113-114 = 00 flag a
* rotating field (e.g. operator 3LD) instead of offsets. */
if (p->off && pdf2 &&
(p->off == 113u ? field(b, 107, 4) == 0xDu : f != F_ELTDT || field(b, 113, 2))) {
unsigned n = p->off, m = p->offMin;
o->hasFine = 1;
for (unsigned k = 0; k < 2u; k++, n += 5u + m) {
uint32_t se = field(b, n + 1u + m, 4);
int32_t dd = (int32_t)(field(b, n + 1u, m) * 60u + se * 4u);
if (se == 15u) { o->hasFine = 0; continue; } /* default: no offset */
v[k] += bit(b, n) ? dd : -dd;
}
o->latS = bit(d, 65) ? -lat : lat;
o->lonS = bit(d, 75) ? -lon : lon;
o->hasPos = 1;
}
if (o->longMsg && o->bch2) { o->internalPos = bit(d, 111); o->homing = bit(d, 112); }
for (unsigned k = 0; k < 2u; k++) if (neg >> k & 1u) v[k] = -v[k];
o->hasPos = 1;
/* bit 1 set: the source flag is present; bit 0: 1 = internal device */
if (p->src && pdf2) {
o->internalPos = (uint8_t)(2u | bit(b, p->src));
if (f != F_USER) o->homing = bit(b, p->src + 1u);
}
}
const char *dec406_proto_name(const dec406_info_t *in)
@@ -235,7 +293,7 @@ const char *dec406_proto_name(const dec406_info_t *in)
"EPIRB ser\0PLB serial\0Nat ELT\0ELT-DT\0Nat EPIRB\0Nat PLB\0"
"Ship sec\0RLS\0Std test\0Nat test\0"
"Orbito\0ELT avia\0Maritime\0Serial\0National\0Spare\0Callsign\0User test";
uint8_t n = in->userProto ? (uint8_t)(16u + (in->proto & 7u)) : (uint8_t)(in->proto & 15u);
uint8_t n = in->proto;
const char *p = NAMES;
while (n--) { while (*p) p++; p++; }
return p;
+9 -7
View File
@@ -55,15 +55,16 @@ typedef struct {
uint8_t selftest; /* self-test frame sync */
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 proto; /* bits 37-40 (location), 16 + bits 37-39 (user) */
uint8_t stdLoc; /* standard location protocol (host only) */
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 internalPos; /* bit 1: flag present, bit 0: internal 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 (spare location code),
2 = ELT(DT) cancellation message (A3.3.8.5) */
uint16_t country; /* bits 27-36 */
uint32_t idData; /* std location: bits 41-64 */
uint32_t idData; /* std location: bits 41-64 (host only) */
int32_t latS, lonS; /* position in arc seconds, N and E positive */
char id[16]; /* 15-hex beacon ID, NUL terminated */
} dec406_info_t;
@@ -79,8 +80,9 @@ void dec406_rearm(dec406_t *d);
* has been received (state DEC406_DONE); call dec406_parse, then dec406_rearm. */
bool dec406_push(dec406_t *d, uint16_t sample);
/* BCH checks and field extraction. */
void dec406_parse(const dec406_t *d, dec406_info_t *out);
/* BCH checks and field extraction. The PDF-1 position bits of d are left at
* their default value (they make the 15-hex ID): parse a message only once. */
void dec406_parse(dec406_t *d, dec406_info_t *out);
/* Short protocol name for display. */
const char *dec406_proto_name(const dec406_info_t *in);
+32 -31
View File
@@ -40,6 +40,7 @@
#include <stddef.h>
#include "../app_api.h"
#include "epirb406_assets.h" /* generated by gen_assets.py */
#define DEC406_LEAN /* no idData / stdLoc: not displayed */
#include "dec406.c"
/* ---- MCU registers (PY32F071, core and SysTick at 48 MHz, 10 ms period) ---- */
@@ -127,14 +128,14 @@ static struct {
static char *put(char *o,const char *s){ while(*s)*o++=*s++; return o; }
/* Formatting by repeated subtraction: no division, so no __udivsi3 in the
* 4 KiB overlay. Values printed stay below 100000. */
static uint8_t sub(uint32_t *v,uint32_t d){ uint8_t q=0; while(*v>=d){ *v-=d; q++; } return q; }
static unsigned sub(uint32_t *v,uint32_t d){ unsigned q=0; while(*v>=d){ *v-=d; q++; } return q; }
static char *puti(char *o,int32_t v){
static const uint16_t P10[]={10000u,1000u,100u,10u,1u};
uint32_t u;
if(v<0){*o++='-';u=(uint32_t)(-v);} else u=(uint32_t)v;
bool lead=false;
for(uint8_t i=0;i<5;i++){
uint8_t c=sub(&u,P10[i]);
for(unsigned i=0;i<5;i++){
unsigned c=sub(&u,P10[i]);
if(c||lead||i==4u){ *o++=(char)('0'+c); lead=true; }
}
return o;
@@ -143,18 +144,18 @@ static char *puti(char *o,int32_t v){
static char *putPos(char *o,int32_t s,char pos,char neg){
uint32_t a=(uint32_t)(s<0?-s:s);
o=puti(o,sub(&a,3600u)); *o++='.';
for(uint8_t k=0;k<5u;k++){ a*=10u; *o++=(char)('0'+sub(&a,3600u)); }
for(unsigned k=0;k<5u;k++){ a*=10u; *o++=(char)('0'+sub(&a,3600u)); }
*o++= s<0?neg:pos;
return o;
}
/* Flags row: every text starts with a space, skipped by the caller. */
static char *putFlags(char *o,const dec406_info_t *in,const char *s){
bool source=in->longMsg&&in->stdLoc&&in->bch2;
bool coarse=in->hasPos&&!in->hasFine;
if(!(source||coarse||in->idRaw)) return o;
bool have=false;
if(source){ o=put(o,(in->internalPos?s+T_INT:s+T_EXT)+1); have=true; if(in->homing) o=put(o,s+T_HOMING); }
if(coarse){ o=put(o,s+T_COARSE+(have?0:1)); have=true; }
if(in->idRaw) o=put(o,s+T_RAWID+(have?0:1));
if(in->internalPos){ /* bit 1: source flag present */
o=put(o,(in->internalPos&1u)?s+T_INT:s+T_EXT);
if(in->homing) o=put(o,s+T_HOMING);
}
if(in->hasPos&&!in->hasFine) o=put(o,s+T_COARSE);
if(in->idRaw) o=put(o,in->idRaw>1u?s+T_CANCEL:s+T_RAWID);
return o;
}
static void tiny(char *buf,uint8_t x,uint8_t y,char *end){ *end='\0'; g.A->print_tiny(buf,x,y,false,true); }
@@ -170,10 +171,8 @@ static void tune(void){
}
/* "433.645" from 10 Hz units, rounded to the kHz, without division */
static char *putFreq(char *o,uint32_t f){
uint16_t mhz=0;
f+=50u;
while(f>=100000u){ f-=100000u; mhz++; }
o=puti(o,mhz); *o++='.';
o=puti(o,(int32_t)sub(&f,100000u)); *o++='.';
*o++=(char)('0'+sub(&f,10000u)); *o++=(char)('0'+sub(&f,1000u)); *o++=(char)('0'+sub(&f,100u));
return o;
}
@@ -232,7 +231,7 @@ static uint8_t capture(const saved_hw_t *saved){
if(!done){ g.nFail++; g.lastErr=d.state==DEC406_DATA?2u:1u; return APP_KEY_INVALID; }
g.nSync++; g.lastErr=0;
frame_t *f=g.history[HISTORY-1u];
for(uint8_t k=HISTORY-1u;k;k--) g.history[k]=g.history[k-1u];
for(unsigned k=HISTORY-1u;k;k--) g.history[k]=g.history[k-1u];
g.history[0]=f;
dec406_parse(&d,&f->info);
f->rssi=g.rssi; /* RSSI at the trigger (not updated while sampling) */
@@ -294,8 +293,7 @@ static void draw(void){
/* Normal view has five or six rows, plus one when coordinates wrap.
* Compact view has four or five rows depending on metadata. */
o=puti(str,in->country); *o++=' ';
uint8_t n=in->userProto ? (uint8_t)(16u+(in->proto&7u)) : (uint8_t)(in->proto&15u);
o+=g.A->asset_read((uint16_t)(T_PROTO+n*T_PROTO_STRIDE),o,T_PROTO_STRIDE);
o+=g.A->asset_read((uint16_t)(T_PROTO+in->proto*T_PROTO_STRIDE),o,T_PROTO_STRIDE);
emit(str,o);
if(in->hasPos){
@@ -309,24 +307,26 @@ static void draw(void){
} else o=put(str,s+T_NOPOS);
emit(str,o);
/* Flags right below the position, so they show without scrolling. */
o=putFlags(str,in,s);
if(o!=str) emit(str+1,o);
o=str;
if(in->selftest) o=put(o,s+T_SELFTEST);
o=put(o,in->longMsg?s+T_LONG:s+T_SHORT);
if(!g.cfg.view){ emit(str,o); o=str; }
else *o++=' ';
o=put(o,s+T_BCH); o=put(o,in->bch1?s+T_OK:s+T_ERR); o=put(o,s+T_SEP);
o=put(o,in->longMsg?(in->bch2?s+T_OK:s+T_ERR):s+T_NA);
emit(str,o);
o=putFlags(str,in,s);
if(o!=str) emit(str,o);
o=put(str,s+T_FRAME); o=puti(o,frame->seq); *o++=' '; o=puti(o,frame->rssi); o=put(o,s+T_DBM);
emit(str,o);
/* Keep the last row whole above the two fixed information rows. */
g.lim=g.vrow>40u?g.vrow-40u:0u;
/* BCH of the selected message, right-aligned on the bottom row. */
o=put(str,s+T_BCH); o=put(o,in->bch1?s+T_OK:s+T_ERR); *o++='/';
o=put(o,in->longMsg?(in->bch2?s+T_OK:s+T_ERR):s+T_NA);
tiny(str,(uint8_t)(128u-(unsigned)(o-str)*4u),49,o);
/* Fixed ID over the scrolled rows; the tiny history capsule fits in
* the remaining columns at its right. */
for(unsigned x=0;x<128u;x++) g.A->fb[0][x]=0;
@@ -348,7 +348,7 @@ static void draw(void){
/* APRS-style dotted separator at y=40, above the two fixed information rows. */
for(unsigned x=0;x<128u;x+=2u) g.A->fb[5][x]|=0x01u;
o=str; *o++='R'; *o++='X'; *o++=' ';
o=put(str,s+T_RX);
o=putFreq(o,g.vfoFreq+(uint32_t)((int32_t)g.offSteps*STEP_10HZ));
if(g.offSteps){ *o++=' '; if(g.offSteps>0) *o++='+'; o=puti(o,(int32_t)g.offSteps*5); o=put(o,s+T_KHZ); }
tiny(str,0,43,o);
@@ -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; }
@@ -390,7 +390,7 @@ void app_main(const app_api_t *api){
frame_t frames[HISTORY]; /* on the stack: the 4 KiB overlay also holds .bss */
frame_t *history[HISTORY];
saved_hw_t saved;
for(uint8_t k=0;k<HISTORY;k++) history[k]=&frames[k];
for(unsigned k=0;k<HISTORY;k++) history[k]=&frames[k];
g.A=api;
g.history=history;
g.prevKey=APP_KEY_INVALID; /* the rest of g starts at 0 (overlay zeroed by the loader) */
@@ -410,21 +410,22 @@ void app_main(const app_api_t *api){
g.A->delay_ms(50);
int32_t f=0;
for(uint8_t i=0;i<16;i++){ f+=g.A->rssi_dbm(); g.A->delay_ms(5); }
for(unsigned i=0;i<16;i++){ f+=g.A->rssi_dbm(); g.A->delay_ms(5); }
g.floorQ=f*4;
g.running=true;
while(g.running){
uint8_t key=APP_KEY_INVALID;
for(uint8_t i=0;i<TICK_MS;i++){
for(unsigned i=0;i<TICK_MS;i++){
g.rssi=g.A->rssi_dbm();
int32_t fl=g.floorQ/64; /* noise floor, dBm */
if(g.coolUntil){
uint32_t now=g.A->ticks_ms();
if(g.rssi < g.floorQ/64+REARM_DB || (int32_t)(now-g.coolUntil)>=0)
if(g.rssi < fl+REARM_DB || (int32_t)(now-g.coolUntil)>=0)
g.coolUntil=0;
else { g.A->delay_ms(1); continue; }
}
if(g.rssi>=g.floorQ/64+TRIG_DB){
if(g.rssi>=fl+TRIG_DB){
key=capture(&saved);
g.coolUntil=g.A->ticks_ms()+COOL_MS;
if(!g.coolUntil) g.coolUntil=1u;
+6 -4
View File
@@ -36,19 +36,21 @@ UI = [
("T_SHORT", "SHORT"),
("T_BCH", "BCH "),
("T_OK", "OK"),
("T_ERR", "ERR"),
("T_ERR", "ER"),
("T_NA", "--"),
("T_SEP", " / "),
("T_FRAME", "FRAME "),
("T_FRAME", "FR "),
("T_DBM", "dBm"),
("T_INT", " INT"),
("T_EXT", " EXT"),
("T_HOMING", " 121"),
("T_COARSE", " COARSE"),
("T_RAWID", " RAW ID"),
("T_CANCEL", " CANCEL"),
("T_KHZ", " kHz"),
("T_ERROR", " ERROR "),
("T_NOSYNC", " NOSYNC"),
("T_RX", "RX "),
("T_ERROR", " ER "),
("T_NOSYNC", " NS"),
("T_CUT", " CUT"),
]
@@ -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 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 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -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 2500 -2500 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
@@ -0,0 +1,79 @@
# C/S T.001 test frames (Flipper Zero)
One `.sub` file per coding option of C/S T.001 Issue 4 Rev. 11 (October 2023),
Annex A, generated by `../../t001flipper.py` from `../../t001frames.py`
(433.650 MHz, 2-FSK, one burst per file). Copy the folder to `subghz/` on the
Flipper, set the radio to 433.650 MHz FM wide, launch EPIRB 406, then Send one
file per burst, at least 2 s apart.
**Never transmit these files on 406.0-406.1 MHz (distress band).**
Positions: a test point (49.07624 N, 0.73018 E) and a south-west point
(33 52'08" S, 70 39'28" W). "coarse" = PDF-2 offsets absent or not applicable.
`../../t001check.py` runs the same frames through the host decoder, and
`../../limesdr/t001lime.py` writes them as LimeSDR IQ files with the real
406 modulation.
| File | Coding option | 15-hex ID | Position |
|---|---|---|---|
| `annexb_example.sub` | Annex B example (short serial user, USA 366) | `ADCD00800440401` | none |
| `u_maritime_mmsi.sub` | Maritime user, MMSI 123456 (A2.2) | `9C69D65028154D1` | none |
| `u_maritime_cs.sub` | Maritime user, call sign FABC (A2.2) | `9C6A4936E33B8D1` | none |
| `u_radio_callsign.sub` | Radio call sign user FAB1234 (A2.3) | `9C7B6E33748D0D1` | none |
| `u_aviation.sub` | Aviation user F-GABC (A2.4) | `9C664936AF8CEE1` | none |
| `u_serial_elt.sub` | Serial user, ELT serial, TAC 105 (A2.5.1) | `9C6C40C0E4001A5` | none |
| `u_serial_epirb_ff.sub` | Serial user, float-free EPIRB (A2.5.1) | `9C6D4350C4003C9` | none |
| `u_serial_epirb_nff.sub` | Serial user, non float-free EPIRB (A2.5.1) | `9C6E400C24003C9` | none |
| `u_serial_plb.sub` | Serial user, PLB, national serial (A2.5.1) | `9C6F0042482A001` | none |
| `u_serial_24bit.sub` | Serial user, aircraft 24-bit address 3A1B2C (A2.5.2) | `9C6DCE86CB014F9` | none |
| `u_serial_opdes.sub` | Serial user, operator AFR serial 42 (A2.5.3) | `9C6CF8DAA02A4F9` | none |
| `u_test_short.sub` | Test user, short (A2.6) | `9C7C123456789AB` | none |
| `u_national_short.sub` | National user, short (A2.8) | `9C702468ACE0246` | none |
| `u_national_long.sub` | National user, long (A2.8) | `9C702468ACE0246` | none |
| `u_orbitography.sub` | Orbitography (A2.7) | `9C60F0F0F0F0F0F` | none |
| `u_maritime_emerg.sub` | Maritime user, emergency code: sinking (A2.9.1) | `9C69D65028154D1` | none |
| `u_aviation_emerg.sub` | Aviation user, emergency: fire + medical (A2.9.2) | `9C664936AF8CEE1` | none |
| `u_selftest.sub` | Maritime user, self-test frame | `9C69D65028154D1` | none |
| `ul_maritime.sub` | User-location, maritime MMSI (A3.3.4) | `9C69D65028154D1` | 49.06666, 0.73333 (coarse) |
| `ul_radio_callsign.sub` | User-location, radio call sign (A3.3.4) | `9C7B6E33748D0D1` | 49.06666, 0.73333 (coarse) |
| `ul_aviation.sub` | User-location, aviation F-GABC (A3.3.4) | `9C664936AF8CEE1` | 49.06666, 0.73333 (coarse) |
| `ul_serial_plb.sub` | User-location, serial PLB (A3.3.4) | `9C6F404248003C9` | 49.06666, 0.73333 (coarse) |
| `ul_test.sub` | User-location, test (protocol 111) (A3.3.4) | `9C7C123456789AB` | 49.06666, 0.73333 (coarse) |
| `ul_maritime_sw.sub` | User-location, maritime, S/W position (A3.3.4) | `9C69D65028154D1` | -33.86666, -70.66666 (coarse) |
| `ul_maritime_nopos.sub` | User-location, maritime, default position (A3.3.4) | `9C69D65028154D1` | none |
| `sl_mmsi.sub` | Standard location, EPIRB MMSI 123456 #1 (A3.3.5) | `1C643C4802FFBFF` | 49.07555, 0.73000 |
| `sl_24bit.sub` | Standard location, ELT 24-bit address 3A1B2C (A3.3.5) | `1C66743658FFBFF` | 49.07555, 0.73000 |
| `sl_elt_serial.sub` | Standard location, ELT serial TAC 105 SN 1234 (A3.3.5) | `1C683489A4FFBFF` | 49.07555, 0.73000 |
| `sl_elt_opdes.sub` | Standard location, ELT operator AFR SN 42 (A3.3.5) | `1C6B8B2854FFBFF` | 49.07555, 0.73000 |
| `sl_epirb_serial.sub` | Standard location, EPIRB serial TAC 242 SN 99 (A3.3.5) | `1C6C7900C6FFBFF` | 49.07555, 0.73000 |
| `sl_plb_serial.sub` | Standard location, PLB serial TAC 242 SN 7 (A3.3.5) | `1C6E79000EFFBFF` | 49.07555, 0.73000 |
| `sl_ship_security.sub` | Standard location, ship security MMSI 123456 (A3.3.5) | `1C783C4800FFBFF` | 49.07555, 0.73000 |
| `sl_test.sub` | Standard location, test protocol (A3.3.5) | `1C7C2468ACFFBFF` | 49.07555, 0.73000 |
| `sl_mmsi_sw.sub` | Standard location, MMSI, S/W, internal GNSS (A3.3.5) | `1C643C4802FFBFF` | -33.86888, -70.65777 |
| `sl_mmsi_coarse.sub` | Standard location, MMSI, default offsets (A3.3.5) | `1C643C4802FFBFF` | 49.00000, 0.75000 (coarse) |
| `sl_mmsi_nopos.sub` | Standard location, MMSI, default position (A3.2) | `1C643C4802FFBFF` | none |
| `sl_mmsi_selftest.sub` | Standard location, MMSI, self-test (A3.2) | `1C643C4802FFBFF` | none |
| `nl_elt.sub` | National location, ELT (A3.3.6) | `1C7152D2BF81FE0` | 49.07555, 0.73000 |
| `nl_epirb.sub` | National location, EPIRB (A3.3.6) | `1C7552D2BF81FE0` | 49.07555, 0.73000 |
| `nl_plb.sub` | National location, PLB (A3.3.6) | `1C7752D2BF81FE0` | 49.07555, 0.73000 |
| `nl_test.sub` | National location, test protocol (A3.3.6) | `1C7F52D2BF81FE0` | 49.07555, 0.73000 |
| `nl_epirb_sw.sub` | National location, EPIRB, S/W (A3.3.6) | `1C7552D2BF81FE0` | -33.86888, -70.65777 |
| `nl_epirb_natuse.sub` | National location, EPIRB, bits 113-126 national use (A3.3.6) | `1C7552D2BF81FE0` | 49.06666, 0.73333 (coarse) |
| `nl_epirb_nopos.sub` | National location, EPIRB, default position (A3.2) | `1C7552D2BF81FE0` | none |
| `rls_epirb.sub` | RLS location, EPIRB TAC 1042 SN 321 (A3.3.7) | `1C7A8540A0BFDFF` | 49.07555, 0.73000 |
| `rls_elt.sub` | RLS location, ELT TAC 2105 SN 55 (A3.3.7) | `1C7A0D201BBFDFF` | 49.07555, 0.73000 |
| `rls_plb.sub` | RLS location, PLB TAC 3242 SN 7 (A3.3.7) | `1C7B1E4003BFDFF` | 49.07555, 0.73000 |
| `rls_mmsi.sub` | RLS location, MMSI 123456, first EPIRB (A3.3.7) | `1C7A78F1203FDFF` | 49.07555, 0.73000 |
| `rls_test.sub` | RLS location test protocol (A3.3.7) | `1C7B9E4000BFDFF` | 49.07555, 0.73000 |
| `rls_epirb_sw_ack.sub` | RLS location, EPIRB, S/W, RLM type-1+2 received (A3.3.7) | `1C7A8540A0BFDFF` | -33.86888, -70.65777 |
| `rls_epirb_nopos.sub` | RLS location, EPIRB, default position (A3.2) | `1C7A8540A0BFDFF` | none |
| `eltdt_24bit.sub` | ELT(DT), 24-bit address 3A1B2C, auto, <=400 m, current (A3.3.8) | `1C721D0D963FDFF` | 49.07555, 0.73000 |
| `eltdt_opdes.sub` | ELT(DT), operator AFR serial 42, manual, 10 km+, <=60 s (A3.3.8) | `1C72E2CA153FDFF` | 49.07555, 0.73000 |
| `eltdt_tac.sub` | ELT(DT), TAC 318 serial 1234, external, alt n/a, >60 s (A3.3.8) | `1C7327C2693FDFF` | 49.07555, 0.73000 |
| `eltdt_test.sub` | ELT(DT) location test protocol (bits 43-66 all 0) (A3.3.8) | `1C720000003FDFF` | 49.07555, 0.73000 |
| `eltdt_24bit_sw.sub` | ELT(DT), 24-bit address, S/W (A3.3.8) | `1C721D0D963FDFF` | -33.86888, -70.65777 |
| `eltdt_3ld_afr.sub` | ELT(DT), rotating field: operator 3LD AFR (A3.3.8.3) | `1C721D0D963FDFF` | 49.00000, 0.50000 (coarse) |
| `eltdt_3ld_zga.sub` | ELT(DT), rotating field: no 3LD (default ZGA) | `1C721D0D963FDFF` | 49.00000, 0.50000 (coarse) |
| `eltdt_nopos.sub` | ELT(DT), default position (A3.2) | `1C721D0D963FDFF` | none |
| `eltdt_selftest.sub` | ELT(DT), GNSS self-test with position | `1C721D0D963FDFF` | 49.07555, 0.73000 |
| `eltdt_cancel.sub` | ELT(DT) cancellation message (A3.3.8.5) | `1C721D0D963FDFF` | none |
@@ -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 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 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 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 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 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -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 2500 -2500 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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 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 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 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 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 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 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -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 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 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 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 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 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 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -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 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 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -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 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 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -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 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 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 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 1250 -1250 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 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 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -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 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 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -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 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 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -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 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 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -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 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 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -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 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 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 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 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 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 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 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 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -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 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 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 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 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -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 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 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 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 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -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 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 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 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 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 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 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 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 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 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 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 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 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 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 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 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 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 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 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 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 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 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 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 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 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 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 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 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 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 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 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -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 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 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 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 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 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 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 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 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -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 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 -1250 1250 -1250 1250 -2500 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 -2500 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 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 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 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 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 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 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 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 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 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 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 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 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 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -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 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -2500 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 2500 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -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 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 1250 -1250 2500 -1250 1250 -2500 2500 -2500 1250 -1250 1250 -1250 2500 -1250 1250 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 2500 -1250 1250 -2500 1250 -1250 2500 -2500 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 1250 -1250 2500 -1250 1250 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 2500 -2500 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 1250 -1250 2500 -2500 2500 -1250 1250 -2500 2500 -2500 2500 -1250 1250 -1250 1250 -2500 2500 -1250 1250 -1250 1250 -1250 1250 -2500 21250
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff. Show more