Add APRS RX and APRS TX overlay Apps

This commit is contained in:
Armel FAUVEAU committed 2026-09-30 05:13:27 +02:00
1 parent 5e8469966b
commit 92f793bb2b
23 files changed
+2741 -1

No files matched your search

+156
View File
@@ -0,0 +1,156 @@
# APRS RX: on-radio APRS receiver (work in progress)
Goal: an overlay app that receives APRS (AX.25 UI frames, Bell 202 AFSK 1200
bauds) on the UV-K1 / UV-K5 v3 and shows the last frame, then an APRS TX app.
Status:
| Step | State |
|---|---|
| Hardware demodulation by the BK4829 FSK block | **Dead end** (2026-09-25, FT3D on 144.800: the chip has no Bell 202 demodulator) |
| 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, the frequency bottom right as Beacon, drops `pp` and the UP/DOWN/5 tuning offset to fit 4 KiB (builds: 3976 B, 120 B free; radio test pending) |
| 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 |
## Using the app
1. Set the VFO to the APRS frequency, **FM**: 144.800 MHz, or 433.650 MHz for the
Flipper test files.
2. Launch **APRS RX**.
3. Lower the volume: the RX audio must stay on (it is what reaches PA4), so the
speaker plays the channel.
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.
Up to v0.3, captures started 10 dB above a tracked noise floor; on a busy
144.800 that floor crept up (−132 → −104 dBm), so weak stations were never
sampled. Several frames in one transmission are now received too.
Keys and the screen cost sample time (the key scan alone is ~400 µs), so they
are served every 50 ms only while no slicer is inside a preamble (two
back-to-back flags) or a frame whose first bytes look like a callsign (busy
~4 % of the time on noise, in the model), and after 3 s of busy at the latest.
The screen is redrawn after a new frame, a key, or every 5 s.
| Screen | Content |
|---|---|
| Status bar | `APRS RX` title, then a `WAIT` capsule until the first frame |
| Line 0 | Source call and frame number, e.g. `F4HWN-7 #3` |
| Tiny row y=8 | `>DEST,DIGI*,...` (as many path entries as fit on one row) |
| Tiny rows y=13/19/25 | Info field, 3 × 32 characters (non-ASCII shown as `.`); for Mic-E (most Yaesu/Kenwood beacons, v0.3): position `48 50.89N 002 16.25E`, then speed km/h, course, symbol and message type (`Off Duty`, `En Route`...), then altitude and comment without the device markers; for an uncompressed position (`!` `=` `/` `@`, v0.5): position, then the timestamp if any, the symbol (table + code) and the comment over two rows. Compressed positions stay raw text |
| Lines 4-5 | Under a dotted separator (y=30), the frequency drawn as the main screen draws it (big digits up to the kHz, the last two in the small font), centred; same code as APRS TX |
| Bottom row y=49 | `ok 12 -89dBm sl 5/12/7`: frames received, RSSI at the end of the last frame, frames per slicer (space-favouring / neutral / mark-favouring; a frame found by several slicers counts in each, but once in `ok`) |
Keys (UV-K5 and UV-K1):
| Key | Action |
|---|---|
| MENU | Clear the last frame and the counters |
| EXIT | Quit |
The receive path is the firmware's own (STD: 300 Hz high-pass, de-emphasis,
3 kHz low-pass). Up to v0.3, key 1 switched to RAW (filters and AFC off, as
EPIRB 406); every on-air decode was made in STD and the model shows no gain
for RAW, so v0.4 dropped it to fit the 4 KiB overlay.
The last good frame is kept on `app_main`'s stack and the demodulator state on
`capture`'s: the 4 KiB overlay also holds `.bss`. The screen texts and the two
cos tables are assets.
## Demodulator (`test/model_rx.py`, class `Demod`; the C is a transcription)
Per ADC sample (9.6 kHz, 8 samples per bit):
1. DC removal (1-pole tracker, 64 samples).
2. Band-pass biquad centred on √(1200·2200) = 1625 Hz, Q 0.9 (Q14): equal gain
on both tones, removes the out-of-band noise the box correlators let through.
3. Four sliding correlators over one bit: I/Q at 1200 Hz (8-entry cos table) and
2200 Hz (48 entries = 11 cycles). The sine is the cos table read 3/4 period
ahead (+6, +12). Products `>> 8`, so the decision below never overflows int32.
4. Magnitudes `max + 3/8 min`, per-tone peak trackers (attack 1/32, decay
1/1024): each tone normalised by its own peak, so the twist (pre-emphasis or
not, de-emphasis or not) does not bias the decision much.
5. **3 slicers** (since v0.4), decision `sign(wa·Mm·Ps − wb·Ms·Pm)` with
wa:wb = 2:3, 1:1, 3:2, each with its own DPLL, NRZI and HDLC below (Direwolf's
multi-slicer idea): the AGC does not cancel a strong twist in noise. A frame
from any slicer is accepted; a copy from another slicer within 1 s is dropped.
6. DPLL: 65536 per bit, a bit at each wrap; each transition pulls the phase 1/4 of
the way toward mid-bit + half a sample (the transition is only seen at the
next sample: without that half-sample, a +1 % clock failed long frames).
7. NRZI, HDLC (flag, destuffing, abort after 7 ones), CRC-16/X.25 per byte,
frame accepted on the residue 0xF0B8 **and** as a UI frame: noise brings ~0.7
candidates per second to the FCS check, so without the UI check a false frame
would pass about once a day.
### Slicers (`test/variants.py`, 4 seeds, 24 hard cases: noise 3000-4000 Hz, ±1 % clock, twist −9 to +9 dB at noise 2500 Hz, RAW and STD)
| Variant | Frames /96 |
|---|---|
| 1 slicer (v0.1-v0.3) | 57 |
| 3 slicers, weights ×2 | 65 |
| **3 slicers, weights ×1.5 (v0.4)** | **71** |
| 5 slicers (×1.5 and ×2) | 71 |
The gain is on strong twist: sine AFSK at +6 / +9 dB in RAW goes from 0/4 to 4/4.
Noise only (30 s, 2 seeds, 50 and 400 LSB rms): no frame accepted.
### Model results (`test/model_rx.py`, 3 seeds per case, 1 slicer; 3 slicers: all ≥ these)
Channel: discriminator output + white noise (rms over 48 kHz) → radio audio path
(RAW: 5 kHz low-pass; STD: 300 Hz high-pass, 750 µs de-emphasis, 3 kHz low-pass)
→ 12-bit ADC at 9.6 kHz, 0.065 LSB/Hz (EPIRB 406 measurement), optional clock
error. 150 ms of no-carrier noise before and after each burst.
| Case | RAW | STD |
|---|---|---|
| Flipper, clean: `pos`, `long` (233 bytes) | 3/3, 3/3 | 3/3, 3/3 |
| Flipper `badfcs` (must be rejected) | 0 frames | 0 frames |
| Flipper, noise 1500 / 3000 / 4500 Hz | 3/3, 2/3, 0/3 | 3/3, 2/3, 0/3 |
| Flipper, ±4 kHz carrier offset | 3/3 | 3/3 |
| Flipper `long`, ±1 % sample clock | 3/3 | 3/3 (−1 %), 3/3 (+1 %) |
| Sine AFSK (a real station), twist −6 / 0 / +6 dB, noise 1500 | 3/3, 3/3, 2/3 | 2/3, 3/3, 3/3 |
Noise 4500 Hz is Eb/N0 ≈ 9.6 dB: the theoretical frame success of non-coherent
FSK on a 520-bit frame is then ~6 %, so the failures there are expected.
(At ±0.5 % clock, 6/6 in every variant tried.)
## Flipper Zero test transmitter (`test/flipper_aprs.py`)
The Flipper's CC1101 cannot put an audio tone on FM, only switch between two
frequencies (preset `2FSKDev238Async`, ±2.38 kHz). Each AFSK tone is therefore
sent as a square wave: the carrier toggles at twice the tone frequency,
phase-continuous across bits, with edge times rounded from exact values (no
drift). The receiver's discriminator outputs that square wave and its audio
low-pass leaves mostly the fundamental. The Flipper cannot reach 2 m anyway; the
script refuses 144-146 MHz.
```
test/flipper_aprs.py test/flipper [--call F4HWN] [--freq 433650000]
```
writes `aprs_pos.sub`, `_digi`, `_msg`, `_status`, `_long` (233 bytes, 2 s) and
`_badfcs` (one info bit flipped: must not be shown). 100 ms of carrier, 40 flags
(267 ms), the frame, 3 flags, 10 ms of carrier. Copy them to `subghz/` on the
Flipper, set the radio to 433.650 MHz FM, launch the app, then Send one file at
a time, about 1 s apart. (Up to v0.4, UP/DOWN tuned ±5 kHz for a Flipper whose
crystal puts the carrier off the channel; never needed on the bench, dropped in
v0.5 to fit 4 KiB.)
`test/ax25.py` builds the frames (shared by the generator and the model);
`test/mice.py` checks the Mic-E display decoder against a spec encoder, and
the standard position display against real frames;
`test/variants.py` compares demodulator variants on the hard cases (dev tool).
## Bench checklist
- Level reaching the ADC (`pp`, shown up to v0.4, dropped in v0.5 to make
room): estimated ~300 LSB peak-to-peak for the Flipper (±2.38 kHz at
0.065 LSB/Hz), **measured 734-778** with the Flipper and **916** with a real
station (F1PRY-14): ~20 % of full scale, plenty of headroom, and the per-tone
AGC makes the absolute level irrelevant.
- The per-slicer counters show which decision threshold the stations need: if
one outer slicer does most of the work, the twist of the path is off-centre.
+43
View File
@@ -0,0 +1,43 @@
/* Overlay-app link script (POC).
*
* The blob is linked to run at the PY25Q16 sector-cache overlay VMA (shared with
* the multiboot RAM stub — never both active at once). Code + rodata + data +
* bss must all fit in the 4 KiB overlay. objcopy -O binary emits text+rodata+
* data only; the loader zeroes the overlay before copying, so bss starts clean.
*
* VMA must match ADDR(.mb_workspace) in Core/py32f071xb.ld. If the firmware RAM
* layout shifts, update APP_VMA and the loader's compile-time assert catches a
* mismatch. */
/* APP_VMA is the target firmware's __mb_workspace_start (read from its .map). It
* varies with the RAM layout, so pass it from the build: -Wl,--defsym,APP_VMA=0x...
* The default is only a fallback for a standalone size check. */
APP_VMA = DEFINED(APP_VMA) ? APP_VMA : 0x20000280;
APP_LENGTH = 0x1000; /* 4 KiB sector-cache overlay */
ENTRY(app_main)
MEMORY {
APP (rwx) : ORIGIN = APP_VMA, LENGTH = APP_LENGTH
}
SECTIONS {
.app APP_VMA : {
KEEP(*(.text.entry)) /* app_main pinned to offset 0 */
*(.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,
"APRS RX overlay app overflows the 4 KiB overlay")
/DISCARD/ : { *(.ARM.exidx*) *(.ARM.extab*) *(.eh_frame*) *(.comment) *(.note.*) }
}
+625
View File
@@ -0,0 +1,625 @@
/* 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.
*/
/*
* APRS RX - receives APRS (AX.25 UI frames, Bell 202 AFSK 1200 bauds) in
* software and shows the last frame: source, destination and path, the info
* field. Tune the VFO (FM; 144.800 MHz, or 433.650 MHz for the Flipper Zero
* bench files of test/flipper_aprs.py), then launch.
*
* The BK4829 has no Bell 202 demodulator (see README.md), so the RX audio is
* sampled as EPIRB 406 does: it reaches PA4 (the voice DAC pin), held at
* mid-scale by the MCU DAC (unbuffered), and is read on ADC channel 4 at
* 9.6 kHz, timed from SysTick, continuously, as a TNC does: no RSSI gate, the
* FCS and the UI check sort frames from noise. Each sample goes through the
* demodulator modelled and tested in test/model_rx.py: band-pass, 1200/2200 Hz
* sliding correlators over one bit, per-tone AGC, then 3 slicers (mark/space
* weights 2:3, 1:1, 3:2), each with its DPLL, NRZI, HDLC and FCS. Keep the two
* in step.
* Keys and the screen cost sample time (the key scan alone is ~400 us), so they
* are served every 50 ms only while no slicer is inside a preamble or a
* plausible frame; the redraw follows a new frame, a key, or every 5 s.
*
* Keys (UV-K5 and UV-K1): MENU clear · EXIT quit. The speaker plays the
* channel: lower the volume.
* The loader re-runs RADIO_SetupRegisters on exit; the app restores the ADC,
* PA4, the DAC and its clock itself.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "../app_api.h"
#include "aprsrx_assets.h" /* generated by gen_assets.py */
/* ---- MCU registers (PY32F071, core and SysTick at 48 MHz, 10 ms period) ---- */
#define SYST_LOAD (*(volatile uint32_t *)0xE000E014u)
#define SYST_VAL (*(volatile uint32_t *)0xE000E018u)
#define ADC_SR (*(volatile uint32_t *)0x40012400u)
#define ADC_CR2 (*(volatile uint32_t *)0x40012408u)
#define ADC_SMPR3 (*(volatile uint32_t *)0x40012414u)
#define ADC_SQR3 (*(volatile uint32_t *)0x40012438u)
#define ADC_DR (*(volatile uint32_t *)0x40012450u)
#define GPIOA_MODER (*(volatile uint32_t *)0x50000000u)
#define DAC_CR (*(volatile uint32_t *)0x40007400u)
#define DAC_SWTRIGR (*(volatile uint32_t *)0x40007404u)
#define DAC_DHR12R1 (*(volatile uint32_t *)0x40007408u)
#define RCC_APBENR1 (*(volatile uint32_t *)0x4002103Cu)
#define RCC_DACEN (1u << 29)
/* DAC channel 1 on, output buffer off, software trigger (TSEL1 = 111) */
#define DAC_CR_BIAS ((1u << 0) | (1u << 1) | (1u << 2) | (7u << 3))
#define BIAS_CODE 2048u
#define ADC_SR_EOC (1u << 1)
#define ADC_CR2_START ((1u << 22) | (1u << 20)) /* SWSTART | EXTTRIG */
#define SMP8_POS 24u
#define SMP4_POS 12u
#define ADC_CH_PA4 4u
#define FS 9600u
#define CYC_PER_SAMPLE (48000000u / FS) /* 5000 */
#define HOUSE_EVERY 480u /* samples between key/screen slots (50 ms) */
#define BUSY_MAX 60u /* serve them anyway after 60 busy slots (3 s) */
#define REFRESH_MS 5000u /* periodic redraw (battery) */
#define DUP_MS 1000u /* the same frame from another slicer */
/* ---- BK4829 ---- */
/* ---- demodulator (test/model_rx.py, class Demod) ---- */
#define BP_B0 5356 /* band-pass 1625 Hz, Q 0.9, Q14 (b2 = -b0, b1 = 0) */
#define BP_A1 (-10714)
#define BP_A2 5673
#define AGC_ATT 5 /* peak tracker: attack 1/32 ... */
#define AGC_DEC 10 /* ... decay 1/1024 per sample */
#define PK_MIN 16
#define PLL_STEP 8192 /* 65536 per bit / 8 samples per bit */
#define PLL_CTR 36864 /* transitions pulled to mid-bit + half a sample */
#define PLL_SHIFT 2 /* 1/4 of the error per transition */
#define NSL 3 /* slicers */
#define PRE_BITS 24u /* busy for 3 bytes after two back-to-back flags */
/* ---- AX.25 ---- */
#define FRAME_MAX 330u /* 10 addresses + control + PID + 256 info + FCS */
#define FRAME_MIN 18u /* 2 addresses + control + PID + FCS */
#define FCS_GOOD 0xF0B8u
#define COUNT_MAX 999u /* counters stop here: the stats row stays within 32 tiny chars */
/* the front end shared by the slicers */
typedef struct {
int32_t dc; /* DC tracker, Q4 */
int32_t x1, x2, y1, y2; /* band-pass state */
int32_t im, qm, is, qs; /* sliding correlator sums */
int32_t pm, ps; /* per-tone magnitude peaks (AGC) */
int16_t ring[8][4]; /* the products of the last bit */
uint8_t r, km, ks; /* ring slot, table phases */
int8_t cm[8], cs[48]; /* cos tables (assets) */
} dem_t;
/* one slicer: decision weights, DPLL, NRZI, HDLC */
typedef struct {
int32_t dprev, phase; /* last decision, DPLL phase (65536/bit) */
uint16_t n, crc; /* bytes, running FCS */
uint8_t wa, wb, last; /* mark/space weights, last level */
uint8_t sr, ones, byte, nb; /* HDLC: shift reg, 1 run, byte, bit count */
uint8_t inframe, hdr; /* between flags, first bytes look like a call */
uint8_t bits, pre; /* bits since the last flag, back-to-back flags */
uint8_t buf[FRAME_MAX];
} sl_t;
/* The app state in one struct: a function loads one base address instead of one
* literal-pool word per global. Words, then bytes, then halfwords, so every field
* stays within the Thumb load/store immediate ranges (ldrb <= 31, ldrh <= 62,
* ldr <= 124). The loader zeroes the overlay (.bss) at every launch. */
static struct {
const app_api_t *A;
uint8_t *frm; /* last good frame (app_main's stack) */
uint32_t vfoFreq; /* VFO RX frequency at launch, 10 Hz units */
uint32_t tPrev, tCyc; /* SysTick cycle counter */
uint32_t tFrame, tDraw; /* ticks_ms of the last frame, last redraw */
uint32_t savedSqr3, savedSmpr3, savedModer, savedDac, savedRcc, savedDhr;
uint8_t seq, prevKey, redraw;
bool running;
int16_t rssi; /* RSSI at the end of the last frame */
uint16_t flen, nOk; /* frame length, frames */
uint16_t good[NSL]; /* frames per slicer (duplicates included) */
} g;
static char str[34];
/* ---- tiny freestanding helpers: GCC may turn the zeroing / copy loops into calls ---- */
void *memset(void *d, int c, size_t n) {
uint8_t *p = d; while (n--) *p++ = (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--) *p++ = *q++; return d;
}
/* ---- formatting ---- */
static char *put(char *o,const char *s){ while(*s)*o++=*s++; return o; }
/* Formatting by repeated subtraction: no division. 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 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(uint8_t i=0;i<5;i++){
uint8_t c=sub(&u,P10[i]);
if(c||lead||i==4u){ *o++=(char)('0'+c); lead=true; }
}
return o;
}
/* A tiny row holds 32 characters (4 px each): print_tiny does not clip, so
* every row built below stays within that. */
static void tiny(uint8_t y,char *end){ *end='\0'; g.A->print_tiny(str,0,y,false,true); }
#define WAIT_CAPS_X 40u /* "WAIT" capsule in the status bar, after the title */
/* Tiny rows under the source line: the path (capitals and digits: no
* descenders), then 3 info rows 6 px apart (the 3x5 font is 5 px, 6 with
* descenders), ending at y = 29 above the separator (y = 30). */
#define Y_PATH 8u
#define Y_INFO 13u
#define ROW_STEP 6u
/* The frequency (10 Hz units) drawn as the main screen draws it (ui/main.c,
* ENABLE_BIG_FREQ): the big digits up to the kHz on lines 4-5, the last two
* digits in the small font on line 5 right after them (from 1 GHz, the whole
* string in the big font), the group centred. Big digits are 13 px (drawn from
* +2), the point 3 px: the group shows (n big digits x 13 + 14) px. No division.
* Same code as APRS TX. */
static void drawFreq(uint32_t f){
const app_api_t *A=g.A;
uint16_t m=0;
while(f>=100000u){ f-=100000u; m++; }
char *o=puti(str,m);
*o++='.';
for(uint8_t i=0;i<5u;i++) *o++=(char)('0'+sub(&f,P10[i]));
*o='\0';
uint8_t n=(uint8_t)(o-str), big=(uint8_t)(n-3u); /* digits before "00" */
if(m>=1000u){ A->print_string(str,(uint8_t)((128u-n*8u)>>1),0,4,8); return; }
uint8_t x=(uint8_t)(((128u-(big*13u+14u))>>1)-2u);
A->print_normal(str+n-2u,(uint8_t)(x+big*13u+3u),0,5); /* "00" */
str[n-2u]='\0';
A->display_freq(str,x,4,false); /* "144.800" */
}
/* ---- SysTick cycle counter (call at least every 10 ms; wraps every 89 s,
* so compare with a signed difference) ---- */
static void clkStart(void){ g.tPrev=SYST_VAL; g.tCyc=0; }
static uint32_t clkCyc(void){
uint32_t v=SYST_VAL;
g.tCyc += (v<=g.tPrev) ? g.tPrev-v : g.tPrev+(SYST_LOAD+1u-v);
g.tPrev=v;
return g.tCyc;
}
/* ---- PA4 bias: the pin has no DC reference of its own (AC-coupled audio); the
* MCU DAC on PA4, output buffer off, at mid-scale, holds it at ~VDD/2.
* Set for the whole run, restored on exit (see EPIRB 406). ---- */
static void biasOn(void){
GPIOA_MODER=g.savedModer|(3u<<8); /* PA4 analog */
RCC_APBENR1=g.savedRcc|RCC_DACEN;
DAC_CR=DAC_CR_BIAS;
DAC_DHR12R1=BIAS_CODE;
DAC_SWTRIGR=1u; /* DHR -> DOR */
}
static void biasOff(void){ DAC_CR=g.savedDac; DAC_DHR12R1=g.savedDhr; RCC_APBENR1=g.savedRcc; GPIOA_MODER=g.savedModer; }
/* ---- ADC on PA4 (channel 4); channel 8 (battery) restored afterwards ---- */
static void adcSelPA4(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_CR2_START;
for(uint16_t n=0; !(ADC_SR&ADC_SR_EOC) && n<2000u; n++){}
return (uint16_t)(ADC_DR&0x0FFFu);
}
static bool callChar(uint8_t c){ return (c>='A'&&c<='Z')||(c>='0'&&c<='9')||c==' '; }
/* An APRS frame is a UI frame: the address field ends (bit 0 of an SSID byte)
* and is followed by control 0x03 and PID 0xF0. Rejects the noise burst that
* passes the 16-bit FCS about once a day in continuous decoding. */
static bool isUI(const uint8_t *f,uint16_t n){
uint16_t end=(uint16_t)(n-2u), e=13;
while(!(f[e]&1u) && e+7u<end) e+=7;
return (f[e]&1u) && e+2u<end && f[e+1]==0x03u && f[e+2]==0xF0u;
}
/* ---- HDLC: one bit after NRZI. True when a UI frame with a good FCS just
* ended (m->buf, m->n bytes, until the next byte). ---- */
static bool hdlcBit(sl_t *m,uint8_t b){
if(m->bits<255u) m->bits++;
m->sr=(uint8_t)((m->sr>>1)|(b<<7));
if(m->sr==0x7Eu){
bool ok = m->inframe && m->n>=FRAME_MIN && m->crc==FCS_GOOD && isUI(m->buf,m->n);
m->pre = m->bits==8u ? (uint8_t)(m->pre<255u?m->pre+1u:255u) : 0;
m->bits=0;
if(ok) return true; /* n kept for the caller, reset below */
m->inframe=1; m->hdr=1; m->n=0; m->crc=0xFFFFu;
m->ones=m->byte=m->nb=0;
return false;
}
if(b){
if(++m->ones>6u){ m->ones=7; m->inframe=0; return false; } /* abort / noise */
} else {
if(m->ones==5u){ m->ones=0; return false; } /* stuffed zero */
m->ones=0;
}
if(!m->inframe) return false;
m->byte|=(uint8_t)(b<<m->nb);
if(++m->nb==8u){
if(m->n<FRAME_MAX){
uint16_t c=m->crc^m->byte; /* CRC-16/X.25, reflected 0x1021 */
for(uint8_t i=0;i<8u;i++) c=(c&1u)?(uint16_t)((c>>1)^0x8408u):(uint16_t)(c>>1);
m->crc=c;
if(m->n<6u && ((m->byte&1u) || !callChar((uint8_t)(m->byte>>1)))) m->hdr=0;
m->buf[m->n++]=m->byte;
} else m->inframe=0;
m->byte=m->nb=0;
}
return false;
}
/* A slicer is busy inside a preamble (two back-to-back flags, then 3 bytes) or
* a frame whose first bytes look like a callsign: noise is busy ~4 % of the time. */
static bool busy(const sl_t *m){
return (m->pre && m->bits<PRE_BITS) || (m->inframe && m->n && m->hdr);
}
/* |(i, q)| ~ max + 3/8 min */
static int32_t mag(int32_t i,int32_t q){
if(i<0) i=-i;
if(q<0) q=-q;
if(i<q){ int32_t t=i; i=q; q=t; }
return i+(q>>2)+(q>>3);
}
/* ---- front end, one ADC sample: *a = Mm*Ps, *b = Ms*Pm (each tone over its
* own peak, without division; both stay below ~2^21) ---- */
static void front(dem_t *m,int32_t adc,int32_t *a,int32_t *b){
m->dc+=((adc<<4)-m->dc)>>6;
int32_t x=adc-(m->dc>>4);
int32_t y=(BP_B0*(x-m->x2)-BP_A1*m->y1-BP_A2*m->y2)>>14;
m->x2=m->x1; m->x1=x; m->y2=m->y1; m->y1=y;
/* sliding correlators over one bit; the sine is the cos table 3/4 period ahead */
int16_t *o=m->ring[m->r];
uint8_t k=(uint8_t)(m->ks+12u); if(k>=48u) k-=48u;
int32_t p;
p=(y*m->cm[m->km])>>8; m->im+=p-o[0]; o[0]=(int16_t)p;
p=(y*m->cm[(m->km+6u)&7u])>>8; m->qm+=p-o[1]; o[1]=(int16_t)p;
p=(y*m->cs[m->ks])>>8; m->is+=p-o[2]; o[2]=(int16_t)p;
p=(y*m->cs[k])>>8; m->qs+=p-o[3]; o[3]=(int16_t)p;
m->r=(uint8_t)((m->r+1u)&7u);
m->km=(uint8_t)((m->km+1u)&7u);
if(++m->ks>=48u) m->ks=0;
int32_t mm=mag(m->im,m->qm), ms=mag(m->is,m->qs);
m->pm+= mm>m->pm ? (mm-m->pm)>>AGC_ATT : -(m->pm>>AGC_DEC);
m->ps+= ms>m->ps ? (ms-m->ps)>>AGC_ATT : -(m->ps>>AGC_DEC);
if(m->pm<PK_MIN) m->pm=PK_MIN;
if(m->ps<PK_MIN) m->ps=PK_MIN;
*a=mm*m->ps;
*b=ms*m->pm;
}
/* ---- slicer: decision sign(wa*a - wb*b), DPLL, NRZI, HDLC ---- */
static bool slice(sl_t *m,int32_t a,int32_t b){
int32_t d=a*m->wa-b*m->wb;
/* DPLL: a bit at each phase wrap (mid-bit), transitions pulled to mid-phase */
m->phase+=PLL_STEP;
if((d>0)!=(m->dprev>0)) m->phase+=(PLL_CTR-m->phase)>>PLL_SHIFT;
m->dprev=d;
if(m->phase>=65536){
m->phase-=65536;
uint8_t lv=d>0;
bool r=hdlcBit(m,lv==m->last); /* NRZI: no change = 1 */
m->last=lv;
return r;
}
return false;
}
/* ---- a good frame from slicer k: shown unless another slicer just gave it ---- */
static void accept(sl_t *m,uint8_t k){
const app_api_t *A=g.A;
uint16_t n=m->n;
uint32_t t=A->ticks_ms();
if(g.good[k]<COUNT_MAX) g.good[k]++;
if(!(n==g.flen && g.frm[n-1]==m->buf[n-1] && g.frm[n-2]==m->buf[n-2]
&& t-g.tFrame<DUP_MS)){
memcpy(g.frm,m->buf,n);
g.flen=n;
if(g.nOk<COUNT_MAX) g.nOk++;
g.seq++;
g.rssi=A->rssi_dbm(); /* the carrier is still up: closing flags */
g.redraw=1;
}
g.tFrame=t;
m->inframe=1; m->hdr=1; m->n=0; m->crc=0xFFFFu; /* this flag opens the next frame */
m->ones=m->byte=m->nb=0;
}
/* ---- AX.25 address field -> "F4HWN-7" ---- */
static char *putCall(char *o,const uint8_t *a){
for(uint8_t i=0;i<6u;i++){ char c=(char)(a[i]>>1); if(c!=' ') *o++=c; }
uint8_t ssid=(uint8_t)((a[6]>>1)&15u);
if(ssid){ *o++='-'; o=puti(o,ssid); }
return o;
}
/* The tiny font covers 0x20-0x7F: anything else shows as '.'. */
static char safe(uint8_t ch){ return (ch<0x20u||ch>0x7Eu)?'.':(char)ch; }
/* Info bytes f[i..end) on one tiny row from o (up to 32 characters); returns
* the next byte. */
static uint16_t textRow(char *o,const uint8_t *f,uint16_t i,uint16_t end,uint8_t y){
for(; o<str+32 && i<end; i++) *o++=safe(f[i]);
tiny(y,o);
return i;
}
/* ---- Mic-E (APRS 1.0.1 chapter 10; test/mice.py is the reference) ----
* The latitude, N/S, the longitude +100 offset, E/W and the message bits are in
* the destination address; the longitude, speed, course and symbol in the info
* field. Byte arithmetic wraps as uint8, so a bad field prints junk, never loops. */
static char *put2(char *o,uint32_t v){ *o++=(char)('0'+sub(&v,10u)); *o++=(char)('0'+v); return o; }
static char *put3(char *o,uint32_t v){ *o++=(char)('0'+sub(&v,100u)); return put2(o,v); }
static uint8_t micDigit(uint8_t c){
if(c>='0'&&c<='9') return (uint8_t)(c-'0');
if(c>='A'&&c<='J') return (uint8_t)(c-'A');
if(c>='P'&&c<='Y') return (uint8_t)(c-'P');
return 0; /* K, L, Z: position ambiguity */
}
static bool micBit(uint8_t c){ return c>='P' || (c>='A'&&c<='K'); }
static bool micType(uint8_t t){ return t=='`'||t=='\''||t==0x1Cu||t==0x1Du; }
static void drawMicE(const uint8_t *f,uint16_t i,uint16_t end,const char *s){
const app_api_t *A=g.A;
uint8_t c[6];
for(uint8_t k=0;k<6u;k++) c[k]=(uint8_t)(f[k]>>1);
/* "48 50.89N 002 16.25E" */
char *o=str;
for(uint8_t k=0;k<6u;k++){
*o++=(char)('0'+micDigit(c[k]));
if(k==1u) *o++=' ';
if(k==3u) *o++='.';
}
*o++=micBit(c[3])?'N':'S';
uint32_t deg=(uint8_t)(f[i+1]-28u);
if(micBit(c[4])) deg+=100u;
if(deg>=180u && deg<=189u) deg-=80u;
else if(deg>=190u && deg<=199u) deg-=190u;
uint32_t mn=(uint8_t)(f[i+2]-28u);
if(mn>=60u) mn-=60u;
*o++=' '; o=put3(o,deg); *o++=' '; o=put2(o,mn); *o++='.'; o=put2(o,(uint8_t)(f[i+3]-28u));
*o++=micBit(c[5])?'W':'E';
tiny(Y_INFO,o);
/* "12km/h 245 [/ En Route": speed in knots x 1.8525 (1897/1024) */
uint32_t dc=(uint8_t)(f[i+5]-28u);
uint32_t sp=(uint32_t)(uint8_t)(f[i+4]-28u)*10u+sub(&dc,10u);
uint32_t crs=dc*100u+(uint8_t)(f[i+6]-28u);
if(sp>=800u) sp-=800u;
if(crs>=400u) crs-=400u;
o=puti(str,(int32_t)((sp*1897u)>>10)); o=put(o,s+T_KMH);
o=puti(o,(int32_t)crs); *o++=' ';
*o++=safe(f[i+7]); *o++=safe(f[i+8]); *o++=' ';
uint8_t idx=(uint8_t)(micBit(c[0])*4u+micBit(c[1])*2u+micBit(c[2]));
bool custom=false;
for(uint8_t k=0;k<3u;k++) if(c[k]>='A'&&c[k]<='K') custom=true;
if(custom && idx){ o=put(o,s+T_CUSTOM); *o++=(char)('0'+7u-idx); }
else o+=A->asset_read((uint16_t)(T_MSG+idx*T_MSG_STRIDE),o,T_MSG_STRIDE); /* NUL-padded */
tiny(Y_INFO+ROW_STEP,o);
/* comment: trailing CR/LF, the device markers (Yaesu/other: '`' or '\'' +
* text + 2 chars; Kenwood: '>' or ']' + text [+ '=' or '^']) and a leading
* "xxx}" altitude (base 91, metres + 10000) */
uint16_t j=(uint16_t)(i+9u);
while(end>j && (f[end-1]=='\r'||f[end-1]=='\n')) end--;
if(j<end){
uint8_t m=f[j];
if(m=='`'||m=='\''){ j++; if(end-j>=2) end-=2u; }
else if(m=='>'||m==']'){ j++; if(end>j && (f[end-1]=='='||f[end-1]=='^')) end--; }
}
o=str;
if(end-j>=4 && f[j+3]=='}'){
int32_t alt=(((int32_t)f[j]-33)*91+((int32_t)f[j+1]-33))*91+((int32_t)f[j+2]-33)-10000;
o=puti(o,alt); *o++='m'; *o++=' ';
j+=4u;
}
textRow(o,f,j,end,Y_INFO+2u*ROW_STEP);
}
/* ---- uncompressed position ('!' '=' or, after a 7-character timestamp, '/'
* '@'; test/mice.py draw_pos is the reference): "49 21.07N 001 50.50E", then
* the timestamp, the symbol and the comment. False for anything else (compressed
* positions included), which is then shown as raw text. ---- */
static bool drawPos(const uint8_t *f,uint16_t i,uint16_t end){
uint8_t t=f[i];
uint16_t p=(uint16_t)(i+1u);
if(t=='/'||t=='@') p+=7u;
else if(t!='!'&&t!='=') return false;
if(end<p+19u || f[p+4]!='.' || f[p+14]!='.') return false;
const uint8_t *q=f+p; /* DDMM.hhN T DDDMM.hhE C */
char *o=str;
for(uint8_t k=0;k<18u;k++){
if(k==8u) continue; /* symbol table: next row */
if(k==2u||k==9u||k==12u) *o++=' ';
*o++=safe(q[k]);
}
tiny(Y_INFO,o);
o=str;
if(p>i+1u){ for(uint16_t k=(uint16_t)(i+1u);k<p;k++) *o++=safe(f[k]); *o++=' '; }
*o++=safe(q[8]); *o++=safe(q[18]); *o++=' ';
textRow(str,f,textRow(o,f,(uint16_t)(p+19u),end,Y_INFO+ROW_STEP),end,Y_INFO+2u*ROW_STEP);
return true;
}
/* ---- display ---- */
static void draw(void){
const app_api_t *A=g.A;
/* The UI texts live in the assets: one read per frame into this word-aligned
* stack block, so each text costs a 2-byte sp-relative add instead of a
* literal and its pool word (gen_assets.py keeps every offset aligned). */
char s[UI_SIZE] __attribute__((aligned(4)));
char *o;
A->asset_read(0,s,UI_SIZE);
A->display_clear();
A->status_clear();
A->print_inverse(s+T_TITLE,2,0,true,true,(uint8_t)(2u+T_TITLE_CHARS*4u));
A->draw_battery();
if(!g.flen) /* no frame yet: a capsule, as APRS TX's TRANSMIT */
A->print_inverse(s+T_WAIT,WAIT_CAPS_X,0,true,true,(uint8_t)(WAIT_CAPS_X+T_WAIT_CHARS*4u));
else {
const uint8_t *f=g.frm;
uint16_t end=(uint16_t)(g.flen-2u); /* the FCS is not shown */
o=putCall(str,f+7); *o++=' '; *o++='#'; o=puti(o,g.seq); *o='\0';
A->print_normal(str,0,0,0); /* source */
/* ">DEST,DIGI*,..." while it fits one tiny row, then the rest of the
* address field is skipped up to its end bit */
o=str; *o++='>'; o=putCall(o,f);
uint16_t e=13; /* SSID byte of the current address */
while(!(f[e]&1u) && e+7u<end){
e+=7;
if(o<=str+20){ *o++=','; o=putCall(o,f+e-6); if(f[e]&0x80u) *o++='*'; }
}
tiny(Y_PATH,o);
/* info field (after control and PID): Mic-E and uncompressed positions
* decoded, anything else as text on 3 rows of 32 characters */
uint16_t i=(uint16_t)(e+3u);
if(end>=i+9u && micType(f[i])) drawMicE(f,i,end,s);
else if(!drawPos(f,i,end))
for(uint8_t row=0;row<3u;row++) i=textRow(str,f,i,end,(uint8_t)(Y_INFO+row*ROW_STEP));
}
/* dotted separator above the frequency: y = 30, bit 6 of line 3 (the big
* digits start at y = 33) */
for(uint8_t x=0;x<128u;x+=2u) A->fb[3][x]|=0x40u;
drawFreq(g.vfoFreq);
/* bottom line: "ok 12 -89dBm sl 5/12/7": frames, RSSI of the last one,
* frames per slicer (<= 31 characters) */
o=put(str,s+T_OK); o=puti(o,g.nOk); *o++=' '; *o++=' ';
o=puti(o,g.rssi); o=put(o,s+T_DBM);
for(uint8_t k=0;k<NSL;k++){ if(k) *o++='/'; o=puti(o,g.good[k]); }
tiny(49,o);
}
/* ---- input ---- */
static void handleKeys(void){
const app_api_t *A=g.A;
uint8_t key=A->get_key();
if(key==APP_KEY_INVALID||key==g.prevKey){ g.prevKey=key; return; }
g.prevKey=key;
g.redraw=1;
if(key==APP_KEY_EXIT) g.running=false;
else if(key==APP_KEY_MENU){
g.flen=0; g.nOk=0; g.seq=0; g.rssi=0;
for(uint8_t k=0;k<NSL;k++) g.good[k]=0;
}
}
/* ---- keys and screen, between frames ---- */
static void house(void){
const app_api_t *A=g.A;
handleKeys();
if(!g.running) return;
A->backlight_on(); /* keep the screen lit while listening */
uint32_t t=A->ticks_ms();
if(t-g.tDraw>=REFRESH_MS) g.redraw=1;
if(!g.redraw) return;
g.redraw=0;
g.tDraw=t;
adcRestore(); /* the battery is on ADC channel 8 */
A->battery_sample();
adcSelPA4();
draw();
A->blit_status();
A->blit_full();
}
/* ---- the receiver: PA4 at 9.6 kHz, continuously, until EXIT ---- */
static void listen(void){
const app_api_t *A=g.A;
dem_t d;
sl_t sl[NSL];
memset(&d,0,sizeof d);
memset(sl,0,sizeof sl);
A->asset_read(COS1200,d.cm,COS1200_LEN);
A->asset_read(COS2200,d.cs,COS2200_LEN);
d.dc=(int32_t)BIAS_CODE<<4;
d.pm=d.ps=64;
sl[0].wa=2; sl[0].wb=3; /* favours space */
sl[1].wa=1; sl[1].wb=1;
sl[2].wa=3; sl[2].wb=2; /* favours mark */
for(uint8_t k=0;k<NSL;k++) sl[k].crc=0xFFFFu;
adcSelPA4();
clkStart();
uint32_t next=CYC_PER_SAMPLE;
uint16_t cnt=0;
uint8_t busyFor=0;
g.redraw=1;
while(g.running){
while((int32_t)(clkCyc()-next)<0){}
next+=CYC_PER_SAMPLE;
int32_t a,b;
front(&d,adcRead(),&a,&b);
bool bz=false;
for(uint8_t k=0;k<NSL;k++){
if(slice(&sl[k],a,b)) accept(&sl[k],k);
bz|=busy(&sl[k]);
}
if(++cnt<HOUSE_EVERY) continue;
cnt=0;
if(bz && ++busyFor<BUSY_MAX) continue;
busyFor=0;
house();
next=clkCyc()+CYC_PER_SAMPLE; /* skip the samples the slot took */
}
adcRestore();
}
__attribute__((section(".text.entry"),used))
void app_main(const app_api_t *api){
uint8_t frm[FRAME_MAX]; /* on the stack: the 4 KiB overlay also holds .bss */
g.A=api;
g.frm=frm;
g.prevKey=APP_KEY_INVALID; /* the rest of g starts at 0 (overlay zeroed by the loader) */
g.savedSqr3=ADC_SQR3; g.savedSmpr3=ADC_SMPR3; g.savedModer=GPIOA_MODER; g.savedDac=DAC_CR;
g.savedRcc=RCC_APBENR1; g.savedDhr=DAC_DHR12R1;
biasOn();
api->backlight_on();
g.vfoFreq=api->rx_freq();
api->audio_path(true);
api->set_af(APP_AF_FM);
api->delay_ms(50);
g.running=true;
listen();
biasOff();
api->set_af(APP_AF_MUTE);
api->audio_path(false);
}
+43
View File
@@ -0,0 +1,43 @@
#!/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. Everything is
# derived from the directory name; only APP_NAME - the human label baked into
# the 64-byte blob header - is per-app. The .app file is named after it (spaces
# stripped), so "Broadcast FM" -> BroadcastFM.app.
set -euo pipefail
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
APP_NAME="APRS RX" # <-- the only per-app line
APP_VER="0.5"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
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 # drop stale artifacts so discovery is unambiguous
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"
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"
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
# APRS RX read-only assets: the screen texts and the correlator tables.
#
# The UI texts come first, each padded to a multiple of 4 bytes: draw() reads
# them in one asset_read into a word-aligned stack block, so every text costs a
# 2-byte sp-relative add instead of a literal-pool load and a pool word.
# The Mic-E message names are a fixed-stride table read straight into the row.
# The tables are 127 * cos(2 pi f n / 9600): one period of 1200 Hz (8 samples)
# and of 2200 Hz (48 samples = 11 cycles). The sine is the same table read 3/4
# of a period ahead (+6 and +12), as in test/model_rx.py.
#
# ./gen_assets.py aprsrx_assets.bin aprsrx_assets.h
import math, os, 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
FS = 9600
TITLE = "APRS RX" # the version is in the .app header
WAIT_CAPS = "WAIT" # status-bar capsule until a frame
UI = [
("T_TITLE", TITLE),
("T_WAIT", WAIT_CAPS),
("T_DBM", "dBm sl "),
("T_OK", "ok "),
("T_KMH", "km/h "),
("T_CUSTOM", "Custom-"),
]
# Mic-E message types, indexed by the destination's message bits A B C
MIC_MSG = ["Emergency", "Priority", "Special", "Committed",
"Returning", "In Service", "En Route", "Off Duty"]
def cos_table(f):
per = FS // math.gcd(FS, f)
return [int(round(127 * math.cos(2 * math.pi * f * n / FS))) for n in range(per)]
a = Assets("APRSRX")
ui_size = 0
for name, s in UI:
pad = -(len(s) + 1) % 4 # keep every offset word-aligned
a.text(name, s + "\0" * pad)
ui_size += len(s) + 1 + pad
a.const("UI_SIZE", ui_size) # the UI block read by draw()
a.const("T_TITLE_CHARS", len(TITLE))
a.const("T_WAIT_CHARS", len(WAIT_CAPS))
a.table("T_MSG", MIC_MSG) # Mic-E standard messages
a.i8("COS1200", cos_table(1200)) # 8 entries
a.i8("COS2200", cos_table(2200)) # 48 entries
a.main()
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""AX.25 UI frames for APRS, and their Bell 202 bit stream.
build(src, dst, path, info) -> frame bytes (addresses + 0x03 0xF0 + info + FCS)
hdlc_bits(frame) -> bits on the air before NRZI: flags, the frame
LSB first with bit stuffing, flags
decode(frame) -> "SRC>DST,PATH:info" (None if the FCS is wrong)
ax25.py [--call F4HWN] prints the test frames and their length
"""
import argparse
TXDELAY_FLAGS = 40 # ~267 ms of 0x7E before the frame (1 flag = 6.67 ms)
TAIL_FLAGS = 3
def crc16(data):
"""CRC-16/X.25 (the AX.25 FCS): reflected 0x1021, init/xorout 0xFFFF."""
crc = 0xFFFF
for b in data:
crc ^= b
for _ in range(8):
crc = (crc >> 1) ^ 0x8408 if crc & 1 else crc >> 1
return crc ^ 0xFFFF
def addr(call, last=False, repeated=False):
"""One 7-byte address field: 6 chars shifted left, then the SSID byte."""
call, _, ssid = call.upper().rstrip("*").partition("-")
out = bytes((ord(c) << 1) for c in call.ljust(6)[:6])
sb = 0x60 | ((int(ssid) if ssid else 0) << 1)
if repeated:
sb |= 0x80 # H bit: this digipeater has relayed it
if last:
sb |= 0x01 # end of the address field
return out + bytes([sb])
def build(src, dst="APZK5", path=(), info=""):
hops = list(path)
f = addr(dst) + addr(src, last=not hops)
for i, h in enumerate(hops):
f += addr(h, last=i == len(hops) - 1, repeated=h.endswith("*"))
f += b"\x03\xF0" + info.encode("latin-1")
fcs = crc16(f)
return f + bytes([fcs & 0xFF, fcs >> 8])
def hdlc_bits(frame, lead=TXDELAY_FLAGS, tail=TAIL_FLAGS):
flag = [(0x7E >> i) & 1 for i in range(8)]
bits = flag * lead
ones = 0
for b in frame:
for i in range(8):
bit = (b >> i) & 1
bits.append(bit)
ones = ones + 1 if bit else 0
if ones == 5:
bits.append(0) # stuffed zero
ones = 0
return bits + flag * tail
def decode(frame):
"""Readable form of a received frame, or None if too short / bad FCS."""
if len(frame) < 18 or crc16(frame[:-2]) != frame[-2] | (frame[-1] << 8):
return None
calls, i = [], 0
while i + 7 <= len(frame) - 2:
c = "".join(chr(x >> 1) for x in frame[i:i + 6]).rstrip()
ssid = (frame[i + 6] >> 1) & 15
if ssid:
c += "-%d" % ssid
if len(calls) >= 2 and frame[i + 6] & 0x80:
c += "*"
calls.append(c)
i += 7
if frame[i - 1] & 1:
break
info = frame[i + 2:-2].decode("latin-1")
return "%s>%s%s:%s" % (calls[1], calls[0],
"".join("," + c for c in calls[2:]), info)
def test_frames(call="F4HWN"):
"""name -> frame bytes. 'badfcs' has one flipped info bit: must be rejected."""
pos = "!4850.90N/00216.25E-UV-K5 APRS RX test"
fr = {
"pos": build(call, path=["WIDE1-1"], info=pos),
"digi": build(call, path=["F1ZZZ-10*", "WIDE2-1"], info=pos),
"msg": build(call, path=["WIDE1-1"], info=":%-9s:Hello from the Flipper{01" % call),
"status": build(call, info=">Flipper Zero AFSK square-wave test"),
"long": build(call, path=["WIDE1-1", "WIDE2-2"],
info=">" + "".join(chr(0x41 + i % 26) for i in range(200))),
}
bad = bytearray(fr["pos"])
bad[20] ^= 0x04
fr["badfcs"] = bytes(bad)
return fr
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--call", default="F4HWN")
a = ap.parse_args()
for name, f in test_frames(a.call).items():
print("%-7s %3d bytes %s" % (name, len(f), decode(f)))
@@ -0,0 +1,11 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 100227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353
RAW_Data: 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417
RAW_Data: 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -417 416 -354 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -291 417 -416 417 -417 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 416 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 416 -417 417 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 417 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -290 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417
RAW_Data: 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -417 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -291 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -416 417 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 10278
@@ -0,0 +1,11 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 100227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353
RAW_Data: 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227
RAW_Data: 354 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -417 416 -417 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -416 417 -417 416 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 417 -416 417 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 417 -416
RAW_Data: 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290
RAW_Data: 227 -228 227 -417 416 -417 417 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -417 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 417 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 10000
@@ -0,0 +1,18 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 100227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353
RAW_Data: 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417
RAW_Data: 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 417 -417 353 -227 228 -227 353 -417 417 -416 417 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 417 -417 416 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -417 417 -416 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416
RAW_Data: 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 417 -416 417 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -417 416 -417 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353
RAW_Data: 417 -291 227 -227 227 -417 417 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 417 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -417 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417
RAW_Data: 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 417 -417 416 -417 417 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -417 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -228
RAW_Data: 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 417 -416 417 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227
RAW_Data: 227 -291 417 -416 417 -417 416 -417 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -417 417 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 417 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227
RAW_Data: 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -417 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 417 -417 416 -417 417 -416 354 -227 227 -228
RAW_Data: 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -417 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416
RAW_Data: 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 417 -416 417 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -417 416 -417 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -417 417 -416
RAW_Data: 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 417 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -10139
+10
View File
@@ -0,0 +1,10 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 100227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353
RAW_Data: 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417
RAW_Data: 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 417 -417 416 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -417 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 417 -416 417 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -291 227 -227 227 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 417 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -416 417 -417
RAW_Data: 416 -417 417 -416 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -227 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -416 417 -227 228 -227 290 -417 417 -416 417 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -417 416 -417 417 -353 227 -228 227 -353 417 -291 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 417 -417 416 -417 417 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -10277
+11
View File
@@ -0,0 +1,11 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 100227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353
RAW_Data: 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 291 -417
RAW_Data: 353 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 417 -417 416 -417 417 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 416 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 417 -416 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228
RAW_Data: 227 -353 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -417 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 353 -227 228 -227 227 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -416 417 -227 228 -227 227 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -416 417 -417 416 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 291 -417 416 -417 353 -228 227 -227 354 -416 417 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -416 417 -417 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 354 -416 417 -417 416 -417 417 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -417 416 -417 417 -416 417 -353 228 -227 227 -354 416 -417 417 -416 417 -417 416 -417 417 -416 417 -417 416 -291 227 -227 228 -416 417 -417 416 -417 417 -416 417 -417
RAW_Data: 416 -417 417 -416 417 -227 228 -227 290 -417 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 353 -228 227 -227 10076
@@ -0,0 +1,10 @@
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433650000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: RAW
RAW_Data: 100227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227
RAW_Data: 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353
RAW_Data: 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -291 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -417 417 -353 227 -228 227 -353 417 -417 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 353 -228 227 -227 228 -227 227 -227 228 -227 227 -291 416 -354 227 -227 228 -353 417 -290 227 -228 227 -417 416 -228 227 -227 291 -416 417 -417 353 -227 228 -227 353 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 227 -227 228 -227 353 -417 291 -227 227 -227 417 -417 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -354 416 -291 227 -227 228 -416 417 -417 416 -417 417 -416 417 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 227 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -354 416 -417 417 -290 227 -228 227 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 228 -227
RAW_Data: 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 291 -417 416 -417 417 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 417 -416 417 -417 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -417 417 -290 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 353 -417 416 -417 417 -416 417 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 353 -417 416 -417 417 -416 291 -227 227 -228 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 417 -417 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -228 227 -227 291 -416 354 -227 227 -228 353 -417 290 -227 228 -227 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -417 417 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -291 417 -353 227 -228 227 -227 227 -228 227 -417 416 -417 417 -416 417 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -290 227 -228 227 -227 228 -227 290 -417 353 -228 227 -227 354 -416 291 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -353 417 -291 227 -227 227 -228 227 -227 227 -228 227 -227 354 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -417 416 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -228 227 -227 291 -416 417 -417 416 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -290 417 -353 228 -227 227 -228 227 -227 227 -228 227 -227 291 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -227 228 -227 290 -417 417 -416 354 -227 227 -228 227 -227 227 -228 416 -417 227 -228 227 -227 227 -228 227 -227 227 -228 227 -417 416 -417 417 -227 227 -227
RAW_Data: 228 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -291 416 -417 417 -416 417 -417 416 -354 227 -227 228 -227 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 416 -417 291 -227 227 -227 417 -417 227 -227 227 -291 417 -416 417 -417 416 -354 227 -227 228 -353 417 -416 417 -417 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 227 -227 227 -228 416 -417 417 -416 228 -227 227 -227 228 -227 227 -354 416 -291 227 -227 228 -227 227 -227 228 -227 227 -228 353 -417 290 -227 228 -227 417 -416 228 -227 227 -291 416 -354 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 290 -417 353 -228 227 -227 228 -227 227 -227 417 -417 416 -417 417 -416 417 -417 227 -227 227 -228 227 -227 228 -353 417 -416 417 -291 227 -227 227 -417 417 -416 417 -417 416 -228 227 -227 227 -228 227 -227 228 -227 227 -227 417 -417 227 -227 227 -228 227 -227 228 -227 227 -227 228 -227 227 -227 291 -417 353 -227 228 -227 353 -417 417 -416 291 -227 227 -228 227 -227 227 -228 227 -227 228 -227 227 -227 228 -416 417 -227 228 -227 290 -417 417 -416 417 -417 353 -227 228 -227 353 -417 417 -416 417 -417 290 -227 228 -227 417 -416 417 -417 227 -227 227 -291 417 -416 417 -353 228 -227 227 -228 227 -227 227 -417 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 417 -227 227 -227 291 -417 416 -417 417 -416 417 -417 416 -417 417 -416 417 -417 353 -227 228 -227 353 -417 417 -416 417 -417 416 -417 417 -416 417 -417 416 -417 291 -227 227 -227 10000
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Flipper Zero test transmitter for the APRS RX app: write Sub-GHz RAW files
(.sub) that play an APRS frame (AX.25, Bell 202 AFSK 1200 bauds) on 433.650 MHz
(433 MHz SRD band, the same bench channel as EPIRB 406).
The Flipper's CC1101 cannot frequency-modulate with an audio tone, only switch
between two frequencies (preset 2FSKDev238Async, +/-2.38 kHz). So each AFSK
tone is sent as a square wave: the carrier toggles at twice the tone frequency
(1200 Hz mark, 2200 Hz space), phase-continuous across bit boundaries, and the
receiver's discriminator outputs that square wave; its audio low-pass leaves
mostly the fundamental. The RAW durations are the square wave's half-periods,
rounded from exact edge times so the timing error never accumulates.
flipper_aprs.py <out_dir> [--call F4HWN] [--freq 433650000]
-> aprs_pos.sub, _digi, _msg, _status, _long, _badfcs (see ax25.test_frames)
One frame per file: press Send once, wait ~1 s before the next one.
"""
import argparse, os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ax25 import test_frames, hdlc_bits, decode
PRESET = "FuriHalSubGhzPreset2FSKDev238Async"
BAUD = 1200
MARK, SPACE = 1200.0, 2200.0
LEAD_US = 100000 # unmodulated carrier before the flags (squelch / RSSI trigger)
TAIL_US = 10000 # carrier kept after the last flag
PER_LINE = 512 # values per RAW_Data line, as the Flipper writes them
def nrzi_tones(bits):
"""Bell 202 NRZI: a 0 changes the tone, a 1 keeps it. Starts on mark."""
f, out = MARK, []
for b in bits:
if not b:
f = SPACE if f == MARK else MARK
out.append(f)
return out
def edges(tones, lead_us=LEAD_US, tail_us=TAIL_US):
"""Exact edge times (us) of the square wave; level is high at t=0.
Returns (edge_times, end_time)."""
t0 = float(lead_us) # lead carrier holds the high level
ph = 0.0 # tone phase in cycles (high while frac < 0.5)
tb = 1e6 / BAUD
ed = []
for i, f in enumerate(tones):
start, end = t0 + i * tb, t0 + (i + 1) * tb
t = start
while True:
nxt = (int(ph * 2) + 1) / 2.0 # next half-cycle boundary
dt = (nxt - ph) / f * 1e6
if t + dt > end:
ph += (end - t) * f / 1e6
break
t += dt
ph = nxt
ed.append(t)
return ed, t0 + len(tones) * tb + tail_us
def raw_durations(ed, end):
"""Signed RAW durations: positive = high tone, negative = low tone."""
pts = [0] + [round(t) for t in ed] + [round(end)]
out, lv = [], 1
for a, b in zip(pts, pts[1:]):
out.append((b - a) if lv else -(b - a))
lv ^= 1
return out
def sub_durations(frame):
ed, end = edges(nrzi_tones(hdlc_bits(frame)))
return raw_durations(ed, end)
def write_sub(path, frame, freq):
d = sub_durations(frame)
lines = ["Filetype: Flipper SubGhz RAW File", "Version: 1",
"Frequency: %d" % freq, "Preset: %s" % PRESET, "Protocol: RAW"]
for i in range(0, len(d), PER_LINE):
lines.append("RAW_Data: " + " ".join(str(x) for x in d[i:i + PER_LINE]))
with open(path, "w") as f:
f.write("\n".join(lines) + "\n")
return d
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("out", nargs="?", default=".")
ap.add_argument("--call", default="F4HWN")
ap.add_argument("--freq", type=int, default=433650000)
a = ap.parse_args()
if 144000000 <= a.freq <= 146000000:
sys.exit("refusing 2 m: the Flipper cannot reach it anyway, and 144.800 is live APRS")
os.makedirs(a.out, exist_ok=True)
for name, fr in test_frames(a.call).items():
p = os.path.join(a.out, "aprs_%s.sub" % name)
d = write_sub(p, fr, a.freq)
print("%-28s %5.0f ms %5d edges %s" % (p, sum(abs(x) for x in d) / 1000,
len(d), decode(fr) or "(bad FCS)"))
+250
View File
@@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""Mic-E: an encoder written from the APRS 1.0.1 spec (chapter 10), and the
display decoder of aprsrx_app.c transliterated byte for byte (uint8 wrap, no
division), checked against each other and against a real FT3D frame.
Also the standard (uncompressed) position display, against real frames.
mice.py runs the tests and prints the screen rows
"""
import os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
MSG = ["Emergency", "Priority", "Special", "Committed",
"Returning", "In Service", "En Route", "Off Duty"] # index = bits A B C
# ------------------------------------------------------------ encoder (spec) --
def encode(call, lat, lon, speed_kn=0, course=0, sym="[/", msg=7, custom=False,
comment="", path=(), marker="`", suffix="_0", alt_m=None):
"""lat/lon in signed decimal degrees -> AX.25 frame bytes."""
north, east = lat >= 0, lon >= 0
lat, lon = abs(lat), abs(lon)
ld = int(lat); lm = (lat - ld) * 60
lmi = int(lm); lh = int(round((lm - lmi) * 100))
if lh == 100: lmi, lh = lmi + 1, 0
digits = "%02d%02d%02d" % (ld, lmi, lh)
od = int(lon); om = (lon - od) * 60
omi = int(om); oh = int(round((om - omi) * 100))
if oh == 100: omi, oh = omi + 1, 0
offset = od < 10 or od >= 100
bits = [(msg >> 2) & 1, (msg >> 1) & 1, msg & 1]
dest = ""
for k, ch in enumerate(digits):
d = int(ch)
if k < 3:
one = bits[k]
dest += chr((ord("A") if custom else ord("P")) + d) if one else chr(ord("0") + d)
elif k == 3:
dest += chr(ord("P") + d) if north else chr(ord("0") + d)
elif k == 4:
dest += chr(ord("P") + d) if offset else chr(ord("0") + d)
else:
dest += chr(ord("P") + d) if not east else chr(ord("0") + d)
if od <= 9: b1 = od + 118
elif od <= 99: b1 = od + 28
elif od <= 109: b1 = od + 8
else: b1 = od - 72
b2 = omi + 88 if omi <= 9 else omi + 28
b3 = oh + 28
b4 = speed_kn // 10 + 28
b5 = (speed_kn % 10) * 10 + course // 100 + 28
b6 = course % 100 + 28
info = bytes([0x60, b1, b2, b3, b4, b5, b6]) + sym.encode()
body = comment
if alt_m is not None:
v = alt_m + 10000
body = chr(v // 8281 + 33) + chr(v // 91 % 91 + 33) + chr(v % 91 + 33) + "}" + body
if marker:
body = marker + body + suffix
return _frame(call, dest, path, info + body.encode("latin-1"))
def _frame(call, dest, path, info):
from ax25 import addr, crc16
hops = list(path)
f = addr(dest) + addr(call, last=not hops)
for i, h in enumerate(hops):
f += addr(h, last=i == len(hops) - 1)
f += b"\x03\xF0" + info
c = crc16(f)
return f + bytes([c & 0xFF, c >> 8])
# ------------------------------------------- decoder (mirror of aprsrx_app.c) --
def sub(v, d):
q = 0
while v >= d:
v -= d; q += 1
return q, v
def put2(v):
q, v = sub(v, 10)
return chr(48 + q) + chr(48 + v)
def put3(v):
q, v = sub(v, 100)
return chr(48 + q) + put2(v)
def mic_digit(c):
if 0x30 <= c <= 0x39: return c - 0x30
if 0x41 <= c <= 0x4A: return c - 0x41
if 0x50 <= c <= 0x59: return c - 0x50
return 0 # K, L, Z: position ambiguity
def mic_bit(c):
return c >= 0x50 or (0x41 <= c <= 0x4B)
def text_row(o, f, i, end):
while len(o) < 32 and i < end:
ch = f[i]
o += "." if ch < 0x20 or ch > 0x7E else chr(ch)
i += 1
return o, i
def is_mice(f, i, end):
return end - i >= 9 and f[i] in (0x60, 0x27, 0x1C, 0x1D)
def draw_mice(f, i, end):
c = [f[k] >> 1 for k in range(6)]
# row 1: position
o = ""
for k in range(6):
o += chr(48 + mic_digit(c[k]))
if k == 1: o += " "
if k == 3: o += "."
o += "N" if mic_bit(c[3]) else "S"
deg = (f[i + 1] - 28) & 0xFF
if mic_bit(c[4]): deg += 100
if 180 <= deg <= 189: deg -= 80
elif 190 <= deg <= 199: deg -= 190
mn = (f[i + 2] - 28) & 0xFF
if mn >= 60: mn -= 60
o += " " + put3(deg) + " " + put2(mn) + "." + put2((f[i + 3] - 28) & 0xFF)
o += "W" if mic_bit(c[5]) else "E"
r1 = o
# row 2: speed, course, symbol, message
dc = (f[i + 5] - 28) & 0xFF
q, dc = sub(dc, 10)
sp = ((f[i + 4] - 28) & 0xFF) * 10 + q
crs = dc * 100 + ((f[i + 6] - 28) & 0xFF)
if sp >= 800: sp -= 800
if crs >= 400: crs -= 400
kmh = (sp * 1897) >> 10
o = "%dkm/h %d " % (kmh, crs) + chr(f[i + 7]) + chr(f[i + 8]) + " "
idx = mic_bit(c[0]) * 4 + mic_bit(c[1]) * 2 + mic_bit(c[2])
custom = any(0x41 <= x <= 0x4B for x in c[:3])
o += ("Custom-" + chr(48 + 7 - idx)) if custom and idx else MSG[idx]
r2 = o
# row 3: comment without the device markers, altitude first
j = i + 9
while end > j and f[end - 1] in (0x0D, 0x0A): end -= 1
if j < end:
m = f[j]
if m in (0x60, 0x27):
j += 1
if end - j >= 2: end -= 2
elif m in (0x3E, 0x5D):
j += 1
if end > j and f[end - 1] in (0x3D, 0x5E): end -= 1
o = ""
if end - j >= 4 and f[j + 3] == 0x7D:
alt = (((f[j] - 33) * 91 + (f[j + 1] - 33)) * 91 + (f[j + 2] - 33)) - 10000
o = "%dm " % alt
j += 4
r3, _ = text_row(o, f, j, end)
return r1, r2, r3
def safe(ch):
return "." if ch < 0x20 or ch > 0x7E else chr(ch)
def draw_pos(f, i, end):
"""Uncompressed position ('!' '=' or, after a 7-char timestamp, '/' '@'):
None when the info field is anything else (shown as raw text)."""
t = f[i]
p = i + 1
if t in (0x2F, 0x40): p += 7
elif t not in (0x21, 0x3D): return None
if end < p + 19 or f[p + 4] != 0x2E or f[p + 14] != 0x2E: return None
q = f[p:]
o = ""
for k in range(18):
if k == 8: continue
if k in (2, 9, 12): o += " "
o += safe(q[k])
r1 = o
o = ""
if p > i + 1:
o += "".join(safe(f[k]) for k in range(i + 1, p)) + " "
o += safe(q[8]) + safe(q[18]) + " "
r2, j = text_row(o, f, p + 19, end)
r3, _ = text_row("", f, j, end)
return r1, r2, r3
def info_start(f):
end = len(f) - 2
e = 13
while not (f[e] & 1) and e + 7 < end:
e += 7
return e + 3, end
# ------------------------------------------------------------------- tests --
def main():
ok = True
# the FT3D frame received on 2026-09-30 (screen: >TXUPX9, `x,5l .[/`_0.)
ft3d = _frame("F4HWN", "TXUPX9", (), b"`x,5l \x1c[/`_0\r")
i, end = info_start(ft3d)
rows = draw_mice(ft3d, i, end)
print("FT3D ", rows)
ok &= rows == ("48 50.89N 002 16.25E", "0km/h 0 [/ Off Duty", "")
cases = [
# lat, lon, speed kn, course, msg, custom, alt, comment -> expected rows
(48.848306, 2.270844, 0, 0, 7, False, None, "", "48 50.90N 002 16.25E"),
(-33.8688, 151.2093, 25, 271, 6, False, 58, "Hello", "33 52.13S 151 12.56E"),
(40.7128, -74.0060, 5, 90, 0, False, None, "", "40 42.77N 074 00.36W"),
(51.5, -0.1276, 799, 359, 3, True, -12, "x", "51 30.00N 000 07.66W"),
(10.0, 105.5, 12, 0, 5, False, 1234, "", "10 00.00N 105 30.00E"),
(1.0, -179.99, 100, 180, 1, False, None, "", "01 00.00N 179 59.40W"),
]
for lat, lon, sp, crs, msg, cu, alt, com, want in cases:
f = encode("F4HWN-7", lat, lon, sp, crs, "[/", msg, cu, com, alt_m=alt)
i, end = info_start(f)
r1, r2, r3 = draw_mice(f, i, end)
kmh = (sp * 1897) >> 10
m = ("Custom-%d" % (7 - msg)) if cu and msg else MSG[msg]
w2 = "%dkm/h %d [/ %s" % (kmh, crs, m)
w3 = ("%dm " % alt if alt is not None else "") + com
good = (r1, r2, r3) == (want, w2, w3) and all(len(r) <= 32 for r in (r1, r2, r3))
ok &= good
print("OK " if good else "FAIL", (r1, r2, r3), "" if good else (want, w2, w3))
# standard positions (aprsrx_app.c drawPos)
pos_cases = [
("F1PRY-14", "@300158z4921.07N/00150.50EN APRS OISE V=12.5V",
("49 21.07N 001 50.50E", "300158z /N APRS OISE V=12.5V", "")),
("F4HWN", "!4850.90N/00216.25E-UV-K5 APRS RX test",
("48 50.90N 002 16.25E", "/- UV-K5 APRS RX test", "")),
("F4HWN", "=4850.90S\\12345.67W>" + "x" * 60,
("48 50.90S 123 45.67W", "\\> " + "x" * 29, "x" * 31)),
("F4HWN", "!/5L!!<*e7>7P[", None), # compressed: raw text
("F4HWN", "!4850.90N/00216.25", None), # too short
("F4HWN", ">status text", None),
]
for call, info, want in pos_cases:
f = _frame(call, "APZK5", (), info.encode("latin-1"))
i, end = info_start(f)
got = draw_pos(f, i, end)
good = got == want and (got is None or all(len(r) <= 32 for r in got))
ok &= good
print("OK " if good else "FAIL", got, "" if good else want)
print("ALL OK" if ok else "FAILURES")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())
+328
View File
@@ -0,0 +1,328 @@
#!/usr/bin/env python3
"""Pure-Python model of the APRS receive chain, and the integer demodulator the
radio app will run (written to be transliterated to C: ints, shifts, no division).
Chain: source (Flipper square-wave FSK from the .sub durations, or a real
station's sine AFSK with a given twist) -> discriminator output in Hz + offset +
white noise -> radio audio path (RAW: 5 kHz low-pass as in EPIRB 406; STD: 300 Hz
high-pass, 750 us de-emphasis, 3 kHz low-pass) -> 12-bit ADC on PA4 around 2048 at
9.6 kHz (optional clock error) -> Demod -> frames.
Demod (per ADC sample):
DC removal -> band-pass -> 4 sliding correlators over one bit (8 samples):
I/Q at 1200 and 2200 Hz -> magnitudes (max + 3/8 min) -> per-tone peak AGC
-> 3 slicers, decision sign(wa*Mm*Ps - wb*Ms*Pm) with wa:wb = 2:3, 1:1, 3:2,
each with its own DPLL (16-bit phase per bit, nudged 1/4 toward mid-bit on
each transition), NRZI, HDLC (flag, destuff, abort), FCS and UI check.
model_rx.py runs the test sweep, prints a table
"""
import math, os, random, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ax25 import test_frames, hdlc_bits, crc16, decode
from flipper_aprs import nrzi_tones, edges
FS_SIM = 96000
FS_ADC = 9600
BIAS = 2048
LSB_PER_HZ = 0.065 # measured on the K1 (EPIRB 406 lab): ~2.3 kHz -> ~150 LSB
FLIPPER_DEV = 2380.0
# ---------------------------------------------------------------- channel ----
def flipper_wave(frame):
"""Discriminator output (Hz) of the Flipper square-wave FSK, box-averaged
per simulation sample from the exact (rounded to us, as in the .sub) edges."""
ed, end = edges(nrzi_tones(hdlc_bits(frame)))
ed = [float(round(t)) for t in ed]
n = int(end * 1e-6 * FS_SIM)
T = 1e6 / FS_SIM
out, lv, k = [], 1, 0
for i in range(n):
t0, t1 = i * T, (i + 1) * T
acc, t = 0.0, t0
while k < len(ed) and ed[k] < t1:
acc += (ed[k] - t) * (1 if lv else -1)
t = ed[k]
lv ^= 1
k += 1
acc += (t1 - t) * (1 if lv else -1)
out.append(FLIPPER_DEV * acc / T)
return out
def sine_wave(frame, dev=3000.0, twist_db=0.0):
"""A real station: phase-continuous sine AFSK, space tone twist_db louder."""
tones = nrzi_tones(hdlc_bits(frame))
spb = FS_SIM / 1200.0
out, ph = [], 0.0
lead = [0.0] * int(0.1 * FS_SIM)
a_space = dev * 10 ** (twist_db / 20)
for i in range(int(len(tones) * spb)):
f = tones[int(i / spb)]
ph += f / FS_SIM
out.append((dev if f == 1200.0 else a_space) * math.sin(2 * math.pi * ph))
return lead + out
def biquad_lp(x, fc):
w = math.tan(math.pi * fc / FS_SIM)
q = 1 / math.sqrt(2)
n = 1 / (1 + w / q + w * w)
b0 = w * w * n; b1 = 2 * b0; a1 = 2 * (w * w - 1) * n; a2 = (1 - w / q + w * w) * n
y, x1, x2, y1, y2 = [], 0.0, 0.0, 0.0, 0.0
for v in x:
o = b0 * v + b1 * x1 + b0 * x2 - a1 * y1 - a2 * y2
x2, x1, y2, y1 = x1, v, y1, o
y.append(o)
return y
def rc_hp(x, fc):
a = math.exp(-2 * math.pi * fc / FS_SIM)
y, acc, prev = [], 0.0, 0.0
for v in x:
acc = a * (acc + v - prev)
prev = v
y.append(acc)
return y
def deemph(x, tau_us=750.0, ref_hz=1200.0):
"""1-pole de-emphasis, unity gain at ref_hz."""
k = 1 - math.exp(-1 / (FS_SIM * tau_us * 1e-6))
g = math.sqrt(1 + (2 * math.pi * ref_hz * tau_us * 1e-6) ** 2)
y, acc = [], 0.0
for v in x:
acc += k * (v - acc)
y.append(acc * g)
return y
def channel(sig, mode="raw", noise_hz=0.0, off_hz=0.0, ppm=0.0, seed=1,
gap_ms=150):
rnd = random.Random(seed)
gap = int(gap_ms * 1e-3 * FS_SIM)
# no carrier around the burst: the discriminator outputs full-scale noise
x = [rnd.gauss(0, 6000) for _ in range(gap)]
x += [v + off_hz + rnd.gauss(0, noise_hz) for v in sig]
x += [rnd.gauss(0, 6000) for _ in range(gap)]
if mode == "raw":
x = rc_hp(biquad_lp(x, 5000), 30)
else:
x = biquad_lp(deemph(rc_hp(x, 300)), 3000)
step = FS_SIM / FS_ADC * (1 + ppm * 1e-6)
out, p = [], 0.0
while p < len(x) - 1:
i = int(p); f = p - i
v = x[i] * (1 - f) + x[i + 1] * f
out.append(min(4095, max(0, int(round(BIAS + LSB_PER_HZ * v + rnd.gauss(0, 1))))))
p += step
return out
# ------------------------------------------------------ integer demodulator --
def _tab(f, fs):
per = fs // math.gcd(fs, int(f))
c = [int(round(127 * math.cos(2 * math.pi * f * n / fs))) for n in range(per)]
s = [int(round(127 * math.sin(2 * math.pi * f * n / fs))) for n in range(per)]
return c, s
def is_ui(f):
"""An APRS frame is a UI frame: the address field ends (bit 0 of an SSID
byte) and is followed by control 0x03 and PID 0xF0. Rejects the rare noise
burst that passes the 16-bit FCS in continuous decoding."""
end = len(f) - 2
e = 13
while not (f[e] & 1) and e + 7 < end:
e += 7
return bool(f[e] & 1) and e + 2 < end and f[e + 1] == 0x03 and f[e + 2] == 0xF0
class Slicer:
"""One decision threshold + its own DPLL and HDLC. The decision is
sign(wa*Mm*Ps - wb*Ms*Pm): wa/wb != 1 favours one tone, which recovers
frames whose twist the per-tone AGC does not fully cancel (Direwolf's
multi-slicer idea)."""
def __init__(self, dm, wa, wb):
self.dm, self.wa, self.wb = dm, wa, wb
self.dprev = 0; self.phase = 0; self.last = 0
self.sr = 0; self.ones = 0; self.byte = 0; self.nb = 0
self.buf = bytearray(); self.inframe = False
self.good = 0
def step(self, a, b):
dm = self.dm
d = a * self.wa - b * self.wb
# DPLL: sample at phase wrap (mid-bit), transitions pulled to mid-phase
self.phase += dm.step
if (d > 0) != (self.dprev > 0):
self.phase += (dm.ctr - self.phase) >> dm.pll
self.dprev = d
if self.phase >= 65536:
self.phase -= 65536
lv = 1 if d > 0 else 0
self.bit(1 if lv == self.last else 0)
self.last = lv
def bit(self, b):
self.sr = (self.sr >> 1) | (b << 7)
if self.sr == 0x7E:
if self.inframe and len(self.buf) >= 18 and \
crc16(self.buf[:-2]) == self.buf[-2] | (self.buf[-1] << 8) and \
is_ui(self.buf):
self.good += 1
self.dm.frame(bytes(self.buf))
self.buf = bytearray(); self.inframe = True
self.ones = self.byte = self.nb = 0
return
if b:
self.ones += 1
if self.ones > 6: # abort / noise: wait for a flag
self.inframe = False
return
else:
if self.ones == 5: # stuffed zero
self.ones = 0
return
self.ones = 0
if not self.inframe:
return
self.byte |= b << self.nb
self.nb += 1
if self.nb == 8:
if len(self.buf) < 330:
self.buf.append(self.byte)
else:
self.inframe = False
self.byte = self.nb = 0
class Demod:
def __init__(self, fs=FS_ADC, agc=True, bp=True, att=5, dec=10, pll=2, ctr=36864,
slicers=((2, 3), (1, 1), (3, 2))):
self.ctr, self.bp, self.att, self.dec, self.pll = ctr, bp, att, dec, pll
# 2nd-order band-pass (RBJ, constant 0 dB peak) centred on
# sqrt(1200*2200) = 1625 Hz, Q = 0.9: coefficients in Q14
w0 = 2 * math.pi * 1625 / fs
al = math.sin(w0) / (2 * 0.9)
a0 = 1 + al
self.b0 = int(round(al / a0 * 16384))
self.a1 = int(round(-2 * math.cos(w0) / a0 * 16384))
self.a2 = int(round((1 - al) / a0 * 16384))
self.x1 = self.x2 = self.y1 = self.y2 = 0
self.N = fs // 1200 # correlation window = one bit
self.cm, _ = _tab(1200, fs) # 8 entries at 9.6 kHz
self.cs, _ = _tab(2200, fs) # 48 entries at 9.6 kHz
self.im = self.qm = self.is_ = self.qs = 0
self.ring = [[0, 0, 0, 0] for _ in range(self.N)]
self.r = 0; self.km = 0; self.ks = 0
self.dc = BIAS << 4
self.pm = self.ps = 64
self.agc = agc
self.step = (65536 * 1200 + fs // 2) // fs
self.sl = [Slicer(self, wa, wb) for wa, wb in slicers]
self.frames = []
self.maxab = 0
def frame(self, f):
if f not in self.frames: # the same frame from several slicers
self.frames.append(f)
@staticmethod
def mag(i, q):
a, b = abs(i), abs(q)
if a < b: a, b = b, a
return a + (b >> 2) + (b >> 3)
def sample(self, adc):
self.dc += ((adc << 4) - self.dc) >> 6
x = adc - (self.dc >> 4)
if self.bp:
y = (self.b0 * (x - self.x2) - self.a1 * self.y1 - self.a2 * self.y2) >> 14
self.x2, self.x1, self.y2, self.y1 = self.x1, x, self.y1, y
x = y
# sin = cos read 3/4 period ahead: +6 of 8 (1200 Hz), +12 of 48 (2200 Hz:
# 11 cycles per 48 samples, 12 samples = 2.75 cycles)
cm, cs = self.cm, self.cs
p = ((x * cm[self.km]) >> 8, (x * cm[(self.km + 6) & 7]) >> 8,
(x * cs[self.ks]) >> 8, (x * cs[(self.ks + 12) % 48]) >> 8)
o = self.ring[self.r]
self.im += p[0] - o[0]; self.qm += p[1] - o[1]
self.is_ += p[2] - o[2]; self.qs += p[3] - o[3]
self.ring[self.r] = p
self.r = (self.r + 1) % self.N
self.km = (self.km + 1) % len(self.cm)
self.ks = (self.ks + 1) % len(self.cs)
mm, ms = self.mag(self.im, self.qm), self.mag(self.is_, self.qs)
if self.agc:
self.pm += (mm - self.pm) >> self.att if mm > self.pm else -(self.pm >> self.dec)
self.ps += (ms - self.ps) >> self.att if ms > self.ps else -(self.ps >> self.dec)
if self.pm < 16: self.pm = 16
if self.ps < 16: self.ps = 16
a, b = mm * self.ps, ms * self.pm
else:
a, b = mm, ms
if a > self.maxab: self.maxab = a
if b > self.maxab: self.maxab = b
for s in self.sl:
s.step(a, b)
def run(adc, **kw):
d = Demod(**kw)
for s in adc:
d.sample(s)
return d.frames
# ------------------------------------------------------------------ sweep ----
def main():
fr = test_frames()
waves = {}
def wave(name, src, twist=0.0):
key = (name, src, twist)
if key not in waves:
waves[key] = flipper_wave(fr[name]) if src == "flipper" \
else sine_wave(fr[name], twist_db=twist)
return waves[key]
cases = []
for mode in ("raw", "std"):
for name in ("pos", "long", "badfcs"):
cases.append(("flipper", 0.0, mode, name, 0, 0, 0))
for noise in (1500, 3000, 4500, 6000):
cases.append(("flipper", 0.0, mode, "pos", noise, 0, 0))
for off in (-4000, 4000):
cases.append(("flipper", 0.0, mode, "pos", 1500, off, 0))
for ppm in (-10000, 10000):
cases.append(("flipper", 0.0, mode, "long", 1500, 0, ppm))
for tw in (-6.0, 0.0, 6.0):
cases.append(("sine", tw, mode, "pos", 1500, 0, 0))
print("%-7s %5s %-4s %-6s %6s %6s %7s | %-7s %-7s" %
("source", "twist", "mode", "frame", "noise", "off", "ppm", "agc", "no-agc"))
for src, tw, mode, name, noise, off, ppm in cases:
oks = []
for seed in (1, 2, 3):
adc = channel(wave(name, src, tw), mode, noise, off, ppm, seed)
for agc in (True, False):
got = run(adc, agc=agc)
want = [] if name == "badfcs" else [fr[name]]
oks.append((agc, got == want))
res = ["%d/3" % sum(ok for a, ok in oks if a == agc) for agc in (True, False)]
print("%-7s %5.0f %-4s %-6s %6d %6d %7d | %-7s %-7s" %
(src, tw, mode, name, noise, off, ppm, res[0], res[1]))
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--one":
f = test_frames()["pos"]
got = run(channel(flipper_wave(f)))
print([decode(g) for g in got])
else:
main()
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""Compare demodulator variants on the hard cases of the sweep (dev tool).
Run from a file (multiprocessing re-imports __main__)."""
import os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from multiprocessing import Pool
from model_rx import *
FR = test_frames()
CASES = []
for mode in ("raw", "std"):
for noise in (3000, 4000):
CASES.append(("flipper", 0.0, mode, "pos", noise, 0, 0))
CASES.append(("flipper", 0.0, mode, "long", 2000, 0, 10000))
CASES.append(("flipper", 0.0, mode, "long", 2000, 0, -10000))
for tw in (-9.0, -6.0, -3.0, 0.0, 3.0, 6.0, 9.0):
CASES.append(("sine", tw, mode, "pos", 2500, 0, 0))
CASES.append(("flipper", 0.0, mode, "badfcs", 1000, 0, 0))
VARIANTS = {
"s1": dict(slicers=((1, 1),)),
"s3x2": dict(slicers=((1, 2), (1, 1), (2, 1))),
"s3x1.5": dict(slicers=((2, 3), (1, 1), (3, 2))),
"s5": dict(slicers=((1, 2), (2, 3), (1, 1), (3, 2), (2, 1))),
"s3x3": dict(slicers=((1, 3), (1, 1), (3, 1))),
}
SEEDS = (1, 2, 3, 4)
def job(c):
src, tw, mode, name, noise, off, ppm = c
w = flipper_wave(FR[name]) if src == "flipper" else sine_wave(FR[name], twist_db=tw)
row, mx = [], 0
for seed in SEEDS:
adc = channel(w, mode, noise, off, ppm, seed)
want = [] if name == "badfcs" else [FR[name]]
r = []
for kw in VARIANTS.values():
d = Demod(**kw)
for s in adc:
d.sample(s)
r.append(d.frames == want)
mx = max(mx, d.maxab)
row.append(r)
return c, [sum(r[i] for r in row) for i in range(len(VARIANTS))], mx
if __name__ == "__main__":
with Pool() as p:
res = p.map(job, CASES)
names = list(VARIANTS)
print("%-40s " % "case" + " ".join("%6s" % n for n in names))
tot = [0] * len(names)
mx = 0
for c, r, m in res:
print("%-40s " % (" ".join(str(x) for x in c)) + " ".join("%6d" % x for x in r))
tot = [a + b for a, b in zip(tot, r)]
mx = max(mx, m)
print("%-40s " % ("TOTAL /%d" % (len(CASES) * len(SEEDS))) + " ".join("%6d" % x for x in tot))
print("max(Mm*Ps, Ms*Pm) = %d (x3 = %d, int32 max 2147483647)" % (mx, 3 * mx))
+100
View File
@@ -0,0 +1,100 @@
# APRS TX: on-radio APRS position beacon (work in progress)
Sends one APRS position frame (AX.25 UI frame, Bell 202 AFSK 1200 bauds) on the
TX VFO at each press of PTT or MENU. Companion of [APRS RX](../aprsrx/README.md),
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 on the radio, frequency bottom right as Beacon (builds: 2812 B; radio test pending) |
| 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)
| Setting | Default |
|---|---|
| Source | the boot-message callsign (API `boot_callsign`, 1-6 letters/digits) + `SSID` = 7 |
| `DEST` | `APZK5` (APZ = experimental software) |
| `PATH` | `WIDE1-1` (0 to 2 entries) |
| `LAT`, `LON` | `4850.90N`, `00216.25E`: the **default** position, until one is edited on the radio (key 5) |
| `SYMBOL` | `/[` (person) |
| `COMMENT` | `UV-K5 APRS TX` (43 characters at most) |
Frame sent: `F4HWN-7>APZK5,WIDE1-1:!4850.90N/00216.25E[UV-K5 APRS TX` (58 bytes,
~0.73 s on the air: 50 ms of tone settle, 40 flags = 267 ms, the frame, 3 flags).
If the boot message is not a plain callsign (empty, more than 6 characters once
spaces are dropped, or with a `/`), the app shows `No boot callsign` and does not
transmit.
## Keys (UV-K5 and UV-K1)
| Key | Action |
|---|---|
| PTT or MENU | Send one frame |
| UP / DOWN | Tone level (deviation): REG_70 gain 10-127, step 4, default 66 (the firmware's tone gain) |
| 1 / 3 | Twist `tw` -4..+8: 2200 Hz gain = level × (8 + tw) / 8 (-6..+6 dB) |
| 5 | Edit the position |
| EXIT | Quit (level, twist and position are saved) |
### Position editor (v0.2)
```
LAT 48 50.90 N
^
LON 002 16.25 E
0-9 digit * N/S E/W
UP/DN move MENU ok EXIT
```
Type the 13 digits in a row, as a frequency: `485090` then `0021625` (degrees,
minutes, hundredths of a minute: the APRS format, read as is from a GPS or
aprs.fi in degrees-minutes). The cursor skips the separators and goes on from
the latitude to the longitude.
| Key | Action |
|---|---|
| 0-9 | Digit at the cursor, then the next one |
| * | N/S (on the latitude) or E/W (on the longitude) |
| UP / DOWN | Move the cursor (to fix one digit) |
| MENU | Check (degrees ≤ 90 / 180, minutes < 60) and keep: the frame is rebuilt; `Invalid position` otherwise |
| EXIT | Cancel |
The position is saved on exit with the level and twist (`cfg_save`, 10 bytes:
magic 0xA8, level, twist, 13 digits and the hemispheres in nibbles). A v0.1
config (magic 0xA7) is ignored: defaults.
## How it transmits
As the Beacon app keys MCW: `tx_set_params` (carrier + PA on the TX VFO),
REG_51 = 0 (no CTCSS/DCS under the AFSK), `tx_tone(1200)` (tone path on, mic ADC
off, 50 ms settle). Then each bit is timed from SysTick, 40000 cycles at 48 MHz
(exact), and at each NRZI transition the tone frequency REG_71 (1200 Hz = 12389,
2200 Hz = 22714, the driver's `scale_freq`) and gain REG_70 are rewritten: two
SPI writes, a few tens of µs out of 833. `tx_mute` + `tx_end` restore RX.
## Unknowns for the first on-air test (all fine with the defaults on 2026-09-30)
1. **Phase continuity** when REG_71 is rewritten (an NCO keeps it, probably).
The RX model decodes even with a phase reset at every transition (3/3 clean,
2/3 with noise in STD); a hardware TNC such as the FT3D may be stricter.
2. **Twist.** The tone probably enters after the pre-emphasis, so the signal is
flat and a receiver with de-emphasis sees 2200 Hz ~5 dB low. If the FT3D
misses frames that APRS RX decodes, raise `tw` (key 3): +4 is +3.5 dB.
3. **Deviation** at level 66: aim for ~3 kHz; UP/DOWN.
## Test plan
1. Simplex test frequency (not 144.800 for the first frames), low power.
2. Receivers: APRS RX on a second radio (its per-slicer counters `s a/b/c`
show the twist: frames only on the space-favouring slicer = 2200 Hz low) and
the FT3D (APRS on band B, same frequency).
3. PTT, check both; adjust level and twist; then 144.800.
`test/tx_model.py` (needs `../aprsrx/test`): frame from the real assets vs
`ax25.build`, bit stream vs `ax25.hdlc_bits`, bad boot callsigns refused, an
edited position (south/west) vs `ax25.build`, the config round trip, the editor
rows and caret, 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.
+43
View File
@@ -0,0 +1,43 @@
/* Overlay-app link script (POC).
*
* The blob is linked to run at the PY25Q16 sector-cache overlay VMA (shared with
* the multiboot RAM stub — never both active at once). Code + rodata + data +
* bss must all fit in the 4 KiB overlay. objcopy -O binary emits text+rodata+
* data only; the loader zeroes the overlay before copying, so bss starts clean.
*
* VMA must match ADDR(.mb_workspace) in Core/py32f071xb.ld. If the firmware RAM
* layout shifts, update APP_VMA and the loader's compile-time assert catches a
* mismatch. */
/* APP_VMA is the target firmware's __mb_workspace_start (read from its .map). It
* varies with the RAM layout, so pass it from the build: -Wl,--defsym,APP_VMA=0x...
* The default is only a fallback for a standalone size check. */
APP_VMA = DEFINED(APP_VMA) ? APP_VMA : 0x20000280;
APP_LENGTH = 0x1000; /* 4 KiB sector-cache overlay */
ENTRY(app_main)
MEMORY {
APP (rwx) : ORIGIN = APP_VMA, LENGTH = APP_LENGTH
}
SECTIONS {
.app APP_VMA : {
KEEP(*(.text.entry)) /* app_main pinned to offset 0 */
*(.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,
"APRS TX overlay app overflows the 4 KiB overlay")
/DISCARD/ : { *(.ARM.exidx*) *(.ARM.extab*) *(.eh_frame*) *(.comment) *(.note.*) }
}
+431
View File
@@ -0,0 +1,431 @@
/* 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.
*/
/*
* APRS TX - sends one APRS position frame (AX.25 UI frame, Bell 202 AFSK
* 1200 bauds) on the TX VFO at each press of PTT or MENU.
*
* The source is the boot-message callsign (API boot_callsign) with the SSID of
* gen_assets.py; destination, path, symbol and comment are AX.25-encoded there
* too (assets). The position is edited on the radio (key 5: 13 digits typed in
* a row, as a frequency) and saved; gen_assets.py holds the default. The frame
* is built at launch and after each edit, FCS included.
*
* AFSK comes from the BK4829 tone generator, as the Beacon app keys MCW: the tone
* frequency (REG_71) is rewritten at each NRZI transition, 1200 Hz mark / 2200 Hz
* space, bits timed to the cycle (40000 cycles at 48 MHz) from SysTick. The
* mic ADC is off while a tone is sent; any CTCSS/DCS of the VFO is cleared
* (REG_51) so nothing rides under the AFSK. test/tx_model.py mirrors the frame
* and bit stream and decodes them with the APRS RX model.
*
* Keys (UV-K5 and UV-K1): PTT or MENU send · UP/DOWN tone level (deviation,
* REG_70 gain 10-127) · 1/3 twist: 2200 Hz gain = level x (8 + tw) / 8,
* tw -4..+8 (-6..+6 dB), for a receiver's de-emphasis · 5 edit the position
* · EXIT quit.
* Position editor: 0-9 type a digit (the cursor skips the separators and moves
* on) · * toggle N/S or E/W on the cursor's line · UP/DOWN move the cursor ·
* MENU check and keep · EXIT cancel.
* Level, twist and position are saved on exit (cfg_save, 10 bytes).
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "../app_api.h"
#include "aprstx_assets.h" /* generated by gen_assets.py */
/* ---- SysTick (core at 48 MHz, 10 ms period) ---- */
#define SYST_LOAD (*(volatile uint32_t *)0xE000E014u)
#define SYST_VAL (*(volatile uint32_t *)0xE000E018u)
#define CYC_PER_BIT (48000000u / 1200u) /* 40000: exact */
/* ---- BK4829 ---- */
#define REG_51 0x51 /* sub-audio: 0 = no CTCSS/DCS (BK4819_ExitSubAu) */
#define REG_70 0x70 /* tone 1: <15> enable, <14:8> gain */
#define REG_71 0x71 /* tone 1 frequency, Hz x 10.32444 */
#define F_MARK 12389u /* 1200 Hz: (f * 1353245 + 2^16) >> 17, scale_freq */
#define F_SPACE 22714u /* 2200 Hz */
#define LVL_DEF 66 /* BK4819_TransmitTone's tone gain */
#define LVL_MIN 10
#define LVL_MAX 127
#define LVL_STEP 4
#define TW_MIN (-4) /* 2200 Hz gain x 0.5 (-6 dB) */
#define TW_MAX 8 /* 2200 Hz gain x 2 (+6 dB) */
/* ---- AX.25 ---- */
#define TXDELAY_FLAGS 40u /* 267 ms of flags before the frame */
#define TAIL_FLAGS 3u
#define CALL_MAX 6u
#define INFO_LEN (1u + 8u + 1u + 9u + 1u + F_COMMENT_LEN) /* !lat/lon[comment */
#define FRAME_MAX (7u + 7u + F_PATH_LEN + 2u + INFO_LEN + 2u)
/* position: 13 digits DDMMhh DDDMMhh, hemispheres bit 0 south, bit 1 west */
#define POS_DIGITS 13u
#define LAT_DIGITS 6u
#define TX_CAPS_X 40u /* "TRANSMIT" capsule in the status bar, after the title */
#define CFG_MAGIC 0xA8u /* 0xA7 (v0.1: level and twist only) is ignored */
#define CFG_LEN 10u /* magic, level, twist, 13 digits + hemispheres in nibbles */
enum { ST_IDLE = 0, ST_TX, ST_DENIED, ST_NOCALL, ST_BADPOS };
/* The app state in one struct: a function loads one base address instead of one
* literal-pool word per global. Words, then bytes. The loader zeroes the overlay
* (.bss) at every launch. */
static struct {
const app_api_t *A;
uint8_t *frm; /* the frame (app_main's stack) */
uint32_t tPrev, tCyc, next; /* SysTick cycle counter, next bit edge */
uint16_t sent; /* frames sent */
uint8_t flen, prevKey, space, ones, lvl, status;
int8_t tw;
bool running, edit;
uint8_t hemi, ehemi, cur; /* hemispheres, edited ones, cursor digit */
uint8_t pos[POS_DIGITS]; /* the position sent */
uint8_t ed[POS_DIGITS]; /* the position being edited */
} g;
static char str[34];
/* ---- tiny freestanding helpers: GCC may turn small copies into calls ---- */
void *memset(void *d, int c, size_t n) {
uint8_t *p = d; while (n--) *p++ = (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--) *p++ = *q++; return d;
}
/* ---- formatting ---- */
static char *put(char *o,const char *s){ while(*s)*o++=*s++; return o; }
/* Formatting by repeated subtraction: no division. 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 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(uint8_t i=0;i<5;i++){
uint8_t c=sub(&u,P10[i]);
if(c||lead||i==4u){ *o++=(char)('0'+c); lead=true; }
}
return o;
}
/* A tiny row holds 32 characters (4 px each): print_tiny does not clip, so
* every row built below stays within that. */
static void tiny(uint8_t y,char *end){ *end='\0'; g.A->print_tiny(str,0,y,false,true); }
/* The frequency (10 Hz units) drawn as the main screen draws it (ui/main.c,
* ENABLE_BIG_FREQ): the big digits up to the kHz on lines 4-5, the last two
* digits in the small font on line 5 right after them (from 1 GHz, the whole
* string in the big font), the group centred. Big digits are 13 px (drawn from
* +2), the point 3 px: the group shows (n big digits x 13 + 14) px. No division. */
static void drawFreq(uint32_t f){
const app_api_t *A=g.A;
uint16_t m=0;
while(f>=100000u){ f-=100000u; m++; }
char *o=puti(str,m);
*o++='.';
for(uint8_t i=0;i<5u;i++) *o++=(char)('0'+sub(&f,P10[i]));
*o='\0';
uint8_t n=(uint8_t)(o-str), big=(uint8_t)(n-3u); /* digits before "00" */
if(m>=1000u){ A->print_string(str,(uint8_t)((128u-n*8u)>>1),0,4,8); return; }
uint8_t x=(uint8_t)(((128u-(big*13u+14u))>>1)-2u);
A->print_normal(str+n-2u,(uint8_t)(x+big*13u+3u),0,5); /* "00" */
str[n-2u]='\0';
A->display_freq(str,x,4,false); /* "144.800" */
}
/* AX.25 address field -> "F4HWN-7" */
static char *putCall(char *o,const uint8_t *a){
for(uint8_t i=0;i<6u;i++){ char c=(char)(a[i]>>1); if(c!=' ') *o++=c; }
uint8_t ssid=(uint8_t)((a[6]>>1)&15u);
if(ssid){ *o++='-'; o=puti(o,ssid); }
return o;
}
/* ---- position ---- */
/* "4850.90N" (6 digits) or "00216.25E" (7 digits), as APRS sends it */
static uint8_t *putCoord(uint8_t *o,const uint8_t *d,uint8_t n,char h){
for(uint8_t i=0;i<n;i++){ if(i==n-2u) *o++='.'; *o++=(uint8_t)('0'+d[i]); }
*o++=(uint8_t)h;
return o;
}
/* degrees <= 90 / 180 (and nothing beyond), minutes < 60 */
static bool posOk(const uint8_t *d){
uint8_t la=(uint8_t)(d[0]*10u+d[1]), lam=(uint8_t)(d[2]*10u+d[3]);
uint8_t lom=(uint8_t)(d[9]*10u+d[10]);
uint16_t lo=(uint16_t)(d[6]*100u+d[7]*10u+d[8]);
for(uint8_t i=0;i<POS_DIGITS;i++) if(d[i]>9u) return false;
if(la>90u || lam>59u || lo>180u || lom>59u) return false;
if(la==90u && (lam|d[4]|d[5])) return false;
if(lo==180u && (lom|d[11]|d[12])) return false;
return true;
}
/* ---- the frame: dest, source (boot callsign + SSID), path, UI, info, FCS ---- */
static void build(void){
const app_api_t *A=g.A;
uint8_t *f=g.frm;
char call[12];
A->boot_callsign(call,sizeof call);
uint8_t n=0;
while(call[n]){ if(call[n]=='/') return; n++; } /* AX.25: letters and digits */
if(!n || n>CALL_MAX) return; /* flen stays 0: no TX */
A->asset_read(F_DEST,f,F_DEST_LEN);
for(uint8_t i=0;i<6u;i++) f[7+i]=(uint8_t)((i<n?call[i]:' ')<<1);
f[13]=(uint8_t)(0x60u|(CFG_SSID<<1)|(F_PATH_LEN?0u:1u)); /* end bit if no path */
uint8_t k=14;
A->asset_read(F_PATH,f+k,F_PATH_LEN); k+=F_PATH_LEN;
f[k++]=0x03; f[k++]=0xF0; /* UI frame, no layer 3 */
uint8_t sym[2];
A->asset_read(F_SYM,sym,2);
f[k++]='!'; /* position, no messaging */
k=(uint8_t)(putCoord(f+k,g.pos,LAT_DIGITS,(g.hemi&1u)?'S':'N')-f);
f[k++]=sym[0]; /* symbol table */
k=(uint8_t)(putCoord(f+k,g.pos+LAT_DIGITS,POS_DIGITS-LAT_DIGITS,(g.hemi&2u)?'W':'E')-f);
f[k++]=sym[1]; /* symbol code */
A->asset_read(F_COMMENT,f+k,F_COMMENT_LEN); k+=F_COMMENT_LEN;
uint16_t c=0xFFFFu; /* CRC-16/X.25 */
for(uint8_t i=0;i<k;i++){
c^=f[i];
for(uint8_t b=0;b<8u;b++) c=(c&1u)?(uint16_t)((c>>1)^0x8408u):(uint16_t)(c>>1);
}
c^=0xFFFFu;
f[k++]=(uint8_t)c; f[k++]=(uint8_t)(c>>8);
g.flen=k;
}
/* ---- SysTick cycle counter (call at least every 10 ms; compare with a signed
* difference) ---- */
static void clkStart(void){ g.tPrev=SYST_VAL; g.tCyc=0; }
static uint32_t clkCyc(void){
uint32_t v=SYST_VAL;
g.tCyc += (v<=g.tPrev) ? g.tPrev-v : g.tPrev+(SYST_LOAD+1u-v);
g.tPrev=v;
return g.tCyc;
}
/* ---- AFSK ---- */
static void setTone(void){
const app_api_t *A=g.A;
uint32_t v=g.lvl;
if(g.space){ v=(v*(uint32_t)(8+g.tw))>>3; if(v>LVL_MAX) v=LVL_MAX; }
A->bk_write(REG_71,(uint16_t)(g.space?F_SPACE:F_MARK));
A->bk_write(REG_70,(uint16_t)(0x8000u|(v<<8)));
}
/* one bit on the air: NRZI (a 0 changes the tone), from the next bit edge */
static void sendBit(uint8_t b){
while((int32_t)(clkCyc()-g.next)<0){}
if(!b){ g.space^=1u; setTone(); }
g.next+=CYC_PER_BIT;
}
/* one byte LSB first; inside the frame, a 0 is stuffed after five 1s */
static void sendByte(uint8_t v,bool stuff){
for(uint8_t i=0;i<8u;i++){
uint8_t b=(uint8_t)(v&1u);
v>>=1;
sendBit(b);
if(stuff){
if(!b) g.ones=0;
else if(++g.ones==5u){ sendBit(0); g.ones=0; }
}
}
}
/* "LAT 48 50.90 N" on a normal-font line (7 px per character), and a '^' under
* the digit at cursor cur (-1: none) */
static void editRow(const char *label,uint8_t line,const uint8_t *d,uint8_t n,char h,int8_t cur){
char *o=put(str,label);
uint8_t col=0;
for(uint8_t i=0;i<n;i++){
if(i==n-4u) *o++=' ';
if(i==n-2u) *o++='.';
if((int8_t)i==cur) col=(uint8_t)(o-str);
*o++=(char)('0'+d[i]);
}
*o++=' '; *o++=h; *o='\0';
g.A->print_normal(str,0,0,line);
if(cur>=0){ str[0]='^'; str[1]='\0'; g.A->print_tiny(str,(uint8_t)(col*7u+2u),(uint8_t)(line*8u+9u),false,true); }
}
/* ---- display ---- */
static void draw(void){
/* The UI texts live in the assets: one read per frame into this word-aligned
* stack block (gen_assets.py keeps every offset aligned). */
char s[UI_SIZE] __attribute__((aligned(4)));
const app_api_t *A=g.A;
char *o;
A->asset_read(0,s,UI_SIZE);
A->display_clear();
A->status_clear();
A->print_inverse(s+T_TITLE,2,0,true,true,(uint8_t)(2u+T_TITLE_CHARS*4u));
if(g.status==ST_TX) /* a second capsule while on the air */
A->print_inverse(s+T_TX,TX_CAPS_X,0,true,true,(uint8_t)(TX_CAPS_X+T_TX_CHARS*4u));
A->draw_battery();
if(g.edit){
int8_t c=(int8_t)g.cur;
editRow(s+T_LAT,1,g.ed,LAT_DIGITS,(g.ehemi&1u)?'S':'N',c<(int8_t)LAT_DIGITS?c:-1);
editRow(s+T_LON,3,g.ed+LAT_DIGITS,POS_DIGITS-LAT_DIGITS,(g.ehemi&2u)?'W':'E',
c>=(int8_t)LAT_DIGITS?(int8_t)(c-(int8_t)LAT_DIGITS):-1);
tiny(40,put(str,s+T_HELP1));
tiny(48,put(str,g.status==ST_BADPOS?s+T_BADPOS:s+T_HELP2));
A->blit_status();
A->blit_full();
return;
}
if(g.flen){
const uint8_t *f=g.frm;
o=putCall(str,f+7); *o='\0';
A->print_normal(str,0,0,0); /* source */
o=str; *o++='>'; o=putCall(o,f);
for(uint8_t e=13; !(f[e]&1u); ){ e+=7; *o++=','; o=putCall(o,f+e-6); }
tiny(9,o); /* ">APZK5,WIDE1-1" (<= 2 hops) */
uint8_t i=(uint8_t)(14u+F_PATH_LEN+2u), end=(uint8_t)(g.flen-2u); /* info */
for(uint8_t row=0;row<2u;row++){ /* info (<= 64), 2 rows of 32 */
o=str;
for(; o<str+32 && i<end; i++) *o++=(char)f[i];
tiny((uint8_t)(17u+row*7u),o);
}
}
/* dotted separator above the frequency: y = 30, bit 6 of line 3 (the big
* digits start at y = 33) */
for(uint8_t x=0;x<128u;x+=2u) A->fb[3][x]|=0x40u;
drawFreq(A->tx_freq());
/* bottom line: "lvl 66 tw +0 sent 3", or why nothing was sent */
if(g.status==ST_DENIED||g.status==ST_NOCALL)
o=put(str,g.status==ST_DENIED?s+T_DENIED:s+T_NOCALL);
else {
o=put(str,s+T_LVL); o=puti(o,g.lvl);
o=put(o,s+T_TW); if(g.tw>0) *o++='+'; o=puti(o,g.tw);
o=put(o,s+T_SENT); o=puti(o,g.sent);
}
tiny(49,o);
A->blit_status();
A->blit_full();
}
/* ---- one frame on the air ---- */
static void send(void){
const app_api_t *A=g.A;
if(!g.flen){ g.status=ST_NOCALL; return; }
if(A->tx_state()){ g.status=ST_DENIED; return; }
g.status=ST_TX;
draw();
A->tx_set_params(); /* carrier + PA on the TX VFO */
A->bk_write(REG_51,0); /* no CTCSS/DCS under the AFSK */
A->tx_tone(1200); /* tone path on, mic off, 50 ms settle */
g.space=0;
setTone(); /* mark at our level */
clkStart();
g.next=CYC_PER_BIT;
for(uint8_t i=0;i<TXDELAY_FLAGS;i++) sendByte(0x7E,false);
g.ones=0;
for(uint8_t i=0;i<g.flen;i++) sendByte(g.frm[i],true);
for(uint8_t i=0;i<TAIL_FLAGS;i++) sendByte(0x7E,false);
while((int32_t)(clkCyc()-g.next)<0){} /* the last bit plays out */
A->tx_mute(true);
A->tx_end(); /* PA off + RX restored */
if(g.sent<9999u) g.sent++;
g.status=ST_IDLE;
}
/* ---- input ---- */
static void handleKeys(void){
const app_api_t *A=g.A;
uint8_t key=A->get_key();
if(key==APP_KEY_INVALID||key==g.prevKey){ g.prevKey=key; return; }
g.prevKey=key;
A->backlight_on();
if(g.edit){ /* position editor */
g.status=ST_IDLE;
if(key<=APP_KEY_9){ g.ed[g.cur]=key; if(g.cur<POS_DIGITS-1u) g.cur++; }
else if(key==APP_KEY_STAR) g.ehemi^=(uint8_t)(g.cur<LAT_DIGITS?1u:2u);
else if(key==APP_KEY_MENU){
if(!posOk(g.ed)){ g.status=ST_BADPOS; return; }
memcpy(g.pos,g.ed,POS_DIGITS);
g.hemi=g.ehemi;
g.edit=false;
build();
if(!g.flen) g.status=ST_NOCALL;
}
else if(key==APP_KEY_EXIT) g.edit=false;
else {
int8_t d=A->nav_dir(key);
if(d<0 && g.cur) g.cur--;
if(d>0 && g.cur<POS_DIGITS-1u) g.cur++;
}
return;
}
if(key==APP_KEY_EXIT) g.running=false;
else if(key==APP_KEY_MENU||key==APP_KEY_PTT) send();
else if(key==APP_KEY_1){ if(g.tw>TW_MIN) g.tw--; }
else if(key==APP_KEY_3){ if(g.tw<TW_MAX) g.tw++; }
else if(key==APP_KEY_5){
memcpy(g.ed,g.pos,POS_DIGITS);
g.ehemi=g.hemi;
g.cur=0;
g.edit=true;
if(g.status!=ST_NOCALL) g.status=ST_IDLE;
}
else {
int8_t d=A->nav_dir(key);
if(d>0 && g.lvl<=LVL_MAX-LVL_STEP) g.lvl=(uint8_t)(g.lvl+LVL_STEP);
if(d<0 && g.lvl>=LVL_MIN+LVL_STEP) g.lvl=(uint8_t)(g.lvl-LVL_STEP);
}
}
__attribute__((section(".text.entry"),used))
void app_main(const app_api_t *api){
uint8_t frm[FRAME_MAX]; /* on the stack: the 4 KiB overlay also holds .bss */
uint8_t c[CFG_LEN];
g.A=api;
g.frm=frm;
g.prevKey=APP_KEY_INVALID; /* the rest of g starts at 0 (overlay zeroed by the loader) */
g.lvl=LVL_DEF;
api->asset_read(POS_DEF,g.pos,POS_DIGITS);
api->asset_read(POS_DEF+POS_DIGITS,&g.hemi,1);
api->cfg_load(c,CFG_LEN);
if(c[0]==CFG_MAGIC){
if(c[1]>=LVL_MIN && c[1]<=LVL_MAX) g.lvl=c[1];
if((int8_t)c[2]>=TW_MIN && (int8_t)c[2]<=TW_MAX) g.tw=(int8_t)c[2];
for(uint8_t i=0;i<POS_DIGITS;i++) g.ed[i]=(uint8_t)((c[3+i/2u]>>((i&1u)*4u))&15u);
uint8_t h=(uint8_t)(c[9]>>4);
if(posOk(g.ed) && h<=3u){ memcpy(g.pos,g.ed,POS_DIGITS); g.hemi=h; }
}
build();
if(!g.flen) g.status=ST_NOCALL;
api->backlight_on();
g.running=true;
while(g.running){
handleKeys();
if(!g.running) break;
draw();
api->battery_sample();
api->delay_ms(40);
}
c[0]=CFG_MAGIC; c[1]=g.lvl; c[2]=(uint8_t)g.tw;
for(uint8_t i=3;i<CFG_LEN;i++) c[i]=0;
for(uint8_t i=0;i<POS_DIGITS;i++) c[3+i/2u]|=(uint8_t)(g.pos[i]<<((i&1u)*4u));
c[9]|=(uint8_t)(g.hemi<<4); /* the 14th nibble */
api->cfg_save(c,CFG_LEN);
}
+43
View File
@@ -0,0 +1,43 @@
#!/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. Everything is
# derived from the directory name; only APP_NAME - the human label baked into
# the 64-byte blob header - is per-app. The .app file is named after it (spaces
# stripped), so "Broadcast FM" -> BroadcastFM.app.
set -euo pipefail
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
APP_NAME="APRS TX" # <-- the only per-app line
APP_VER="0.2"
APP_API_MIN=2
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
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 # drop stale artifacts so discovery is unambiguous
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"
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"
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
# APRS TX read-only assets: the screen texts and the fixed part of the frame.
#
# The UI texts come first, each padded to a multiple of 4 bytes: draw() reads
# them in one asset_read into a word-aligned stack block, so every text costs a
# 2-byte sp-relative add instead of a literal-pool load and a pool word.
# The frame parts are AX.25-encoded here (addresses shifted left, SSID bytes,
# end-of-address bit on the last path entry): the app adds the source
# (boot-message callsign + SSID), the position (edited on the radio, this one is
# the default) and the FCS. Edit the station settings below, then rebuild;
# test/tx_model.py checks the resulting frame.
#
# ./gen_assets.py aprstx_assets.bin aprstx_assets.h
import os, re, 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
# ---- station settings ----
SSID = 7 # F4HWN-7
DEST = "APZK5" # APZ = experimental software
PATH = ["WIDE1-1"] # [] for none
LAT, LON = "4850.90N", "00216.25E" # default position: DDMM.hhN, DDDMM.hhE
SYMBOL = "/[" # table, code: '[' = person
COMMENT = "UV-K5 APRS TX"
# info field: "!" + LAT + SYMBOL[0] + LON + SYMBOL[1] + COMMENT
TITLE = "APRS TX"
TX_CAPS = "TRANSMIT" # status-bar capsule while on the air
UI = [
("T_TITLE", TITLE),
("T_TX", TX_CAPS),
("T_DENIED", "TX denied"),
("T_NOCALL", "No boot callsign"),
("T_LVL", "lvl "),
("T_TW", " tw "),
("T_SENT", " sent "),
("T_LAT", "LAT "), # 2 spaces: digits aligned with LON
("T_LON", "LON "),
("T_HELP1", "0-9 digit * N/S E/W"),
("T_HELP2", "UP/DN move MENU ok EXIT"),
("T_BADPOS", "Invalid position"),
]
def addr(call, last=False):
call, _, ssid = call.upper().partition("-")
if not (1 <= len(call) <= 6 and call.isalnum()):
sys.exit(f"bad AX.25 address {call!r}")
b = bytes(ord(c) << 1 for c in call.ljust(6))
return b + bytes([0x60 | (int(ssid or 0) << 1) | (1 if last else 0)])
if not 0 <= SSID <= 15:
sys.exit("SSID must be 0-15")
if len(PATH) > 2:
sys.exit("at most 2 path entries (frame buffer)")
if not (re.fullmatch(r"\d{4}\.\d{2}[NS]", LAT) and re.fullmatch(r"\d{5}\.\d{2}[EW]", LON)):
sys.exit("LAT must be DDMM.hhN/S, LON DDDMM.hhE/W")
if len(SYMBOL) != 2 or len(COMMENT) > 43:
sys.exit("SYMBOL is 2 characters, COMMENT at most 43 (frame buffer)")
# 13 digits (DDMMhh DDDMMhh), then the hemispheres: bit 0 south, bit 1 west
POS = [int(c) for c in LAT + LON if c.isdigit()]
HEMI = (LAT[-1] == "S") | (LON[-1] == "W") << 1
a = Assets("APRSTX")
ui_size = 0
for name, s in UI:
pad = -(len(s) + 1) % 4 # keep every offset word-aligned
a.text(name, s + "\0" * pad)
ui_size += len(s) + 1 + pad
a.const("UI_SIZE", ui_size) # the UI block read by draw()
a.const("T_TITLE_CHARS", len(TITLE))
a.const("T_TX_CHARS", len(TX_CAPS))
a.const("CFG_SSID", SSID)
a.raw("F_DEST", addr(DEST))
a.raw("F_PATH", b"".join(addr(h, i == len(PATH) - 1) for i, h in enumerate(PATH)))
a.u8("POS_DEF", POS + [HEMI])
a.raw("F_SYM", SYMBOL.encode("ascii"))
a.raw("F_COMMENT", COMMENT.encode("ascii"))
a.main()
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Model of the APRS TX app: the frame and bit stream of aprstx_app.c rebuilt
from the real assets (gen_assets.py), checked against the AX.25 reference of
the RX app (ax25.py), then sent as AFSK and decoded by the APRS RX model
(model_rx.py) through a receiver audio path.
The tone generator is modelled with a phase-continuous NCO (expected: REG_71 is
rewritten, not the phase) and, as the worst case, with a phase reset at each
tone change. The TX tone path is assumed flat (no pre-emphasis): a receiver with
de-emphasis (STD path, FT3D) then sees 2200 Hz ~5 dB low, which the app's twist
setting (2200 Hz gain x (8 + tw) / 8) compensates.
tx_model.py runs the checks and the decode sweep
"""
import math, os, re, subprocess, sys, tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "..", "aprsrx", "test"))
from ax25 import build, hdlc_bits, crc16, decode
import model_rx as M
CALL = "F4HWN" # boot message
TXDELAY_FLAGS, TAIL_FLAGS = 40, 3
LEAD_S = 0.050 + 1 / 1200 # tx_tone's 50 ms settle, then the first bit edge
DEV_MARK = 3000.0 # Hz of deviation for the mark tone at the chosen level
def assets():
d = tempfile.mkdtemp()
b, h = os.path.join(d, "a.bin"), os.path.join(d, "a.h")
subprocess.run([sys.executable, os.path.join(HERE, "..", "gen_assets.py"), b, h], check=True)
defs = dict(re.findall(r"#define (\w+) (\d+)u", open(h).read()))
return open(b, "rb").read(), {k: int(v) for k, v in defs.items()}
def put_coord(d, h):
"""aprstx_app.c putCoord(): 4850.90N / 00216.25E"""
n = len(d)
return "".join(("." if i == n - 2 else "") + str(x) for i, x in enumerate(d)) + h
def pos_ok(d):
"""aprstx_app.c posOk()"""
if any(x > 9 for x in d): return False
la, lam, lo, lom = d[0] * 10 + d[1], d[2] * 10 + d[3], d[6] * 100 + d[7] * 10 + d[8], d[9] * 10 + d[10]
if la > 90 or lam > 59 or lo > 180 or lom > 59: return False
if la == 90 and (lam | d[4] | d[5]): return False
if lo == 180 and (lom | d[11] | d[12]): return False
return True
def cfg_pack(lvl, tw, pos, hemi):
c = [0xA8, lvl, tw & 0xFF] + [0] * 7
for i, x in enumerate(pos): c[3 + i // 2] |= x << ((i & 1) * 4)
c[9] |= hemi << 4
return bytes(c)
def cfg_unpack(c):
pos = [(c[3 + i // 2] >> ((i & 1) * 4)) & 15 for i in range(13)]
return pos, c[9] >> 4
def edit_row(label, d, h, cur):
"""aprstx_app.c editRow(): the text and the caret column"""
o, col, n = label, None, len(d)
for i, x in enumerate(d):
if i == n - 4: o += " "
if i == n - 2: o += "."
if i == cur: col = len(o)
o += str(x)
return o + " " + h, col
def build_c(blob, D, call=CALL, pos=None, hemi=None):
"""aprstx_app.c build()"""
if not call or len(call) > 6 or "/" in call:
return None
rd = lambda name: blob[D[name]:D[name] + D[name + "_LEN"]]
pd = rd("POS_DEF")
pos = list(pd[:13]) if pos is None else pos
hemi = pd[13] if hemi is None else hemi
sym = rd("F_SYM").decode()
info = "!" + put_coord(pos[:6], "S" if hemi & 1 else "N") + sym[0] + \
put_coord(pos[6:], "W" if hemi & 2 else "E") + sym[1] + rd("F_COMMENT").decode()
f = bytearray(rd("F_DEST"))
f += bytes(ord(call[i]) << 1 if i < len(call) else 0x40 for i in range(6))
f.append(0x60 | (D["CFG_SSID"] << 1) | (0 if D["F_PATH_LEN"] else 1))
f += rd("F_PATH") + b"\x03\xF0" + info.encode()
c = 0xFFFF
for x in f:
c ^= x
for _ in range(8):
c = (c >> 1) ^ 0x8408 if c & 1 else c >> 1
c ^= 0xFFFF
return bytes(f + bytes([c & 0xFF, c >> 8]))
def bits_c(frame):
"""aprstx_app.c sendByte()/sendBit(): the bits on the air, before NRZI"""
out, ones = [], 0
def byte(v, stuff):
nonlocal ones
for _ in range(8):
b = v & 1; v >>= 1
out.append(b)
if stuff:
if not b: ones = 0
else:
ones += 1
if ones == 5: out.append(0); ones = 0
for _ in range(TXDELAY_FLAGS): byte(0x7E, False)
ones = 0
for x in frame: byte(x, True)
for _ in range(TAIL_FLAGS): byte(0x7E, False)
return out
def afsk(bits, tw=0, reset=False, twist_path_db=0.0):
"""discriminator output (Hz) of the transmitted AFSK; tw as in the app,
twist_path_db an extra 2200 Hz gain of the TX audio path (unknown)"""
fs = M.FS_SIM
a_sp = DEV_MARK * min(127, 66 * (8 + tw) // 8) / 66 * 10 ** (twist_path_db / 20)
out, ph, space = [], 0.0, False
for i in range(int(LEAD_S * fs)): # plain mark before the flags
ph += 1200 / fs
out.append(DEV_MARK * math.sin(2 * math.pi * ph))
spb = fs / 1200
t = 0.0
for b in bits:
if not b:
space = not space
if reset: ph = 0.0
f, a = (2200, a_sp) if space else (1200, DEV_MARK)
t += spb
while len(out) < int(LEAD_S * fs + t):
ph += f / fs
out.append(a * math.sin(2 * math.pi * ph))
return out
def main():
blob, D = assets()
ok = True
f = build_c(blob, D)
ssid = D["CFG_SSID"]
info = "!4850.90N/00216.25E[UV-K5 APRS TX" # gen_assets.py defaults
ref = build("%s-%d" % (CALL, ssid) if ssid else CALL, dst="APZK5", path=["WIDE1-1"], info=info)
print("frame ", decode(f), "(%d bytes)" % len(f))
ok &= f == ref
print("frame == ax25.build:", f == ref)
bc = bits_c(f)
ok &= bc == hdlc_bits(f, TXDELAY_FLAGS, TAIL_FLAGS)
print("bits == ax25.hdlc_bits:", bc == hdlc_bits(f, TXDELAY_FLAGS, TAIL_FLAGS),
"(%d bits, %.0f ms + %.0f ms lead)" % (len(bc), len(bc) / 1.2, LEAD_S * 1000))
for bad in ("", "F4HWN/P", "F4HWN73"):
ok &= build_c(blob, D, bad) is None
print("bad boot callsigns refused:", True)
# position editor: another position, south/west, the frame and the config
pos = [3, 3, 5, 2, 1, 3, 1, 5, 1, 1, 2, 5, 6] # 33 52.13S 151 12.56W
f2 = build_c(blob, D, pos=pos, hemi=3)
want = build("F4HWN-7", dst="APZK5", path=["WIDE1-1"],
info="!3352.13S/15112.56W[UV-K5 APRS TX")
ok &= f2 == want
print("edited position frame:", f2 == want, decode(f2))
c = cfg_pack(70, -2, pos, 3)
ok &= len(c) == 10 and cfg_unpack(c) == (pos, 3)
print("config round trip:", cfg_unpack(c) == (pos, 3), c.hex())
rows = (edit_row("LAT ", pos[:6], "S", 2), edit_row("LON ", pos[6:], "W", -1))
ok &= rows == (("LAT 33 52.13 S", 8), ("LON 151 12.56 W", None))
print("editor rows:", rows)
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),
([4,8,0,0,0,0,0,0,0,6,0,0,0], False)]
good = all(pos_ok(d) == w for d, w in checks)
ok &= good
print("posOk limits:", good)
print("\n%-6s %-5s %4s %6s %5s | frames (3 seeds)" % ("phase", "rx", "tw", "path", "noise"))
for reset in (False, True):
for mode in ("raw", "std"):
for tw, path_db in ((0, 0.0), (4, 0.0), (0, 5.0)):
for noise in (0, 1500):
w = afsk(bc, tw, reset, path_db)
n = 0
for seed in (1, 2, 3):
adc = M.channel(w, mode, noise, 0, 0, seed)
d = M.Demod()
for s in adc:
d.sample(s)
n += d.frames == [f]
print("%-6s %-5s %4d %+5.0fdB %5d | %d/3" % (
"reset" if reset else "cont", mode, tw, path_db, noise, n))
if not reset and noise == 0:
ok &= n == 3
print("\nALL OK" if ok else "\nFAILURES")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())
+3 -1
View File
@@ -278,7 +278,9 @@ def main() -> int:
*args.source_root.rglob("*.h"),
)
for path in sorted(source_files):
if "external" in path.parts:
# external code, and host-side test programs (App/apps/*/test) that use
# the host printf and are never built into the firmware
if "external" in path.parts or "test" in path.parts:
continue
errors.extend(validate_file(path, debug_defined))