mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Add APRS RX and APRS TX overlay Apps
This commit is contained in:
1 parent
5e8469966b
commit
92f793bb2b
23 files changed
+2741
-1
No files matched your search
@@ -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.
|
||||
@@ -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.*) }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Executable
+43
@@ -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"
|
||||
Executable
+52
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)"))
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
@@ -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.
|
||||
@@ -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.*) }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Executable
+43
@@ -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"
|
||||
Executable
+83
@@ -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()
|
||||
@@ -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())
|
||||
@@ -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))
|
||||
|
||||
|
||||
Reference in new issue
Block a user