From 866cd1122ca817997d77aaa08b97039dba600f5c Mon Sep 17 00:00:00 2001 From: Riza Date: Tue, 21 Jul 2026 13:07:40 +0700 Subject: [PATCH 1/2] feat: add callback-based subghz protocol decoders Extend RfProtocolDef with decode/encode callbacks for protocols that do not fit the factor-based OOK model (Manchester, rolling code). Create extended registry (rf_registry_ext) with FAAC SLH, PowerSmart, and Intertechno V3 decoders. Update decode/encode dispatch to try callback decoders after factor matching. --- SUBGHZ_PROTOCOL_DECODER_LIBRARY.md | 195 ++++++++++++++++++ platformio.ini | 4 +- .../protocols/decoders/manchester_helpers.cpp | 104 ++++++++++ .../protocols/decoders/manchester_helpers.h | 32 +++ .../decoders/rf_decoder_faac_slh.cpp | 115 +++++++++++ .../protocols/decoders/rf_decoder_faac_slh.h | 12 ++ .../decoders/rf_decoder_intertechno_v3.cpp | 150 ++++++++++++++ .../decoders/rf_decoder_intertechno_v3.h | 10 + .../decoders/rf_decoder_power_smart.cpp | 86 ++++++++ .../decoders/rf_decoder_power_smart.h | 10 + src/modules/rf/protocols/rf_decoder.cpp | 11 + src/modules/rf/protocols/rf_encoder.cpp | 15 ++ src/modules/rf/protocols/rf_protocol.h | 28 ++- src/modules/rf/protocols/rf_registry.cpp | 3 +- src/modules/rf/protocols/rf_registry_ext.cpp | 41 ++++ src/modules/rf/protocols/rf_registry_ext.h | 19 ++ 16 files changed, 831 insertions(+), 4 deletions(-) create mode 100644 SUBGHZ_PROTOCOL_DECODER_LIBRARY.md create mode 100644 src/modules/rf/protocols/decoders/manchester_helpers.cpp create mode 100644 src/modules/rf/protocols/decoders/manchester_helpers.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_faac_slh.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_power_smart.h create mode 100644 src/modules/rf/protocols/rf_registry_ext.cpp create mode 100644 src/modules/rf/protocols/rf_registry_ext.h diff --git a/SUBGHZ_PROTOCOL_DECODER_LIBRARY.md b/SUBGHZ_PROTOCOL_DECODER_LIBRARY.md new file mode 100644 index 0000000000..14567566d2 --- /dev/null +++ b/SUBGHZ_PROTOCOL_DECODER_LIBRARY.md @@ -0,0 +1,195 @@ +# Sub-GHz Protocol Decoder Library + +## Masalah + +Bruce Firmware saat ini hanya bisa men-decode ~16 protokol OOK pasif (Princeton, CAME, NICE, Holtek, dll) + KeeLoq via factor matching (`te`, `sync`, `zero`, `one`). Sinyal yang tidak cocok dengan factor-faktor ini hanya bisa dicapture sebagai RAW — tidak diidentifikasi, tidak di-decode. + +Padahal CC1101 yang sama mampu menangkap **semua** protokol Sub-GHz di 300–928 MHz. Yang kurang adalah decoder library-nya. + +## Tujuan + +Menambahkan protocol decoder library yang komprehensif ke Bruce, mengadopsi decoder-decoder dari Flipper Zero protocol stack. Target: **59 protokol total** (Bruce saat ini ~17 + 12 generic = 29, tapi generic tidak berguna). + +## Arsitektur + +### Pendekatan: Hybrid Factor + Callback + +``` + ┌──────────────────────────┐ + │ RMT RX (CC1101 GDO0) │ + │ durations[] (signed µs)│ + └──────────┬───────────────┘ + │ + ┌──────────▼───────────────┐ + │ Signal Segmentation │ + │ (gap detection, noise │ + │ filtering, glitch │ + │ rejection) │ + └──────────┬───────────────┘ + │ + ┌──────────▼───────────────┐ + │ Protocol Scan Loop │ + │ │ + │ ┌─────────────────────┐ │ + │ │ Factor Match (Cepat) │──│── OOK protokol sederhana + │ │ te/sync/zero/one │ │ (Princeton, CAME, dll) + │ └─────────────────────┘ │ + │ │ + │ ┌─────────────────────┐ │ + │ │ Callback Decode │──│── Protokol kompleks + │ │ (decode_cb per │ │ (FAAC, Somfy, TPMS, + │ │ protokol) │ │ SecPlus, Hormann) + │ └─────────────────────┘ │ + └──────────┬───────────────┘ + │ + ┌──────────▼───────────────┐ + │ Post-Processing │ + │ - Rolling code unlock │ + │ - Manufacturer ident │ + │ - .sub serialization │ + └──────────────────────────┘ +``` + +### Extension pada RfProtocolDef + +```cpp +struct RfProtocolDef { + const char *name; // Nama protokol + uint16_t te; // Base pulse (µs) + HighLow sync; // Sync pulse factor + HighLow zero; // Bit 0 encoding + HighLow one; // Bit 1 encoding + uint8_t bits; // Panjang bit (0 = variable) + bool inverted; // Signal level inverted? + uint8_t flags; // RF_PF_HAS_SYNC, RF_PF_FIXED_LEN, dll + + // --- BARU: Callback untuk protokol kompleks --- + bool (*decode)(const std::vector& durations, RfCodes& out); + bool (*encode)(const RfCodes& in, std::vector& durations); +}; +``` + +Jika `decode` == NULL → gunakan factor matching existing. +Jika `decode` != NULL → panggil callback. +Encoder juga sama. + +## Protocol List Target + +### Existing di Bruce (13 protokol OOK + KeeLoq + Chamberlain) +Tetap dipertahankan dengan factor matching. + +### Baru dari Flipper Zero Port (41 protokol baru) + +#### Simple OOK (factor-based — mudah ditambah) +| Protocol | Bits | te | Sync | Zero | One | +|----------|------|----|------|------|-----| +| Megacode | 48 | 400 | - | - | - | +| PowerSmart | 48 | 382 | - | - | - | +| Marantec | 24 | 450 | - | - | - | +| Marantec24 | 24 | 500 | - | - | - | +| Intertechno_V3 | 56 | 560 | - | - | - | +| SMC5326 | 48 | 400 | - | - | - | +| Linear_Delta3 | 42 | 500 | - | - | - | +| Dooya | 48 | 600 | - | - | - | +| Honeywell | 48 | 400 | - | - | - | +| Magellan | 56 | 300 | - | - | - | +| Nero_Sketch | 48 | 600 | - | - | - | +| Nero_Radio | 48 | 350 | - | - | - | +| IDo | 56 | 650 | - | - | - | +| Bett | 48 | 450 | - | - | - | +| Doitrand | 42 | 400 | - | - | - | +| Gangi | - | - | - | - | - | +| HollArm | - | - | - | - | - | +| Hay21 | - | - | - | - | - | +| Revers_RB2 | - | - | - | - | - | +| Feron | - | - | - | - | - | +| Roger | - | - | - | - | - | +| Elplast | - | - | - | - | - | +| Treadmill37 | - | - | - | - | - | +| Beninca_Arc | - | - | - | - | - | +| Jarolift | - | - | - | - | - | +| Ditec_Gol4 | - | - | - | - | - | +| KeyFinder | - | - | - | - | - | +| Kinggates_Stylo_4K | - | - | - | - | - | +| Legrand | - | - | - | - | - | +| Dickert_MAHS | - | - | - | - | - | +| Alutech_at_4N | - | - | - | - | - | + +Note: Simple OOK di atas perlu dianalisis timing-nya lebih lanjut. Beberapa mungkin punya sync pattern atau encoding yang berbeda. + +#### Rolling Code / Kompleks (callback-based) +| Protocol | Tipe | Notes | +|----------|------|-------| +| FAAC_SLH | Rolling code | Proprietary FAAC algorithm | +| NiceFlor_S | Rolling code | Nice FLO-R (flor) | +| SecPlus_v1 | Rolling code | Chamberlain Security+ v1 | +| SecPlus_v2 | Rolling code | Chamberlain Security+ v2 | +| Hormann | Rolling code | HSM rolling code | +| Somfy_Telis | Rolling code | Somfy RTS | +| Somfy_Keytis | Rolling code | Somfy RTS | +| CAME_Twee | Rolling code | CAME Twin protocol | +| CAME_Atomo | Rolling code | CAME Atomo | +| Honeywell_WDB | Roll/timed | Honeywell wireless doorbell | +| Chamb_Code | Fixed | Chamberlain 9-bit (Bruce sudah punya) | + +#### TPMS (callback-based — Manchester/PWM decoding) +| Protocol | Notes | +|----------|-------| +| Schrader_GG4 | Tire pressure sensor | +| Ford | Ford TPMS | +| Renault | Renault TPMS | +| Citroen | Citroen TPMS | +| PMV107J | Toyota TPMS | + +## Dampak Positif + +1. **Dari 13 → ~55 protokol yang otomatis terdeteksi** — sinyal yang tadinya cuma RAW sekarang kebaca namanya, termasuk rolling code. +2. **Tidak perlu ganti hardware** — CC1101 yang sudah dipakai Bruce cukup. Decoder adalah pure software. +3. **.sub file compatibility dengan Flipper Zero** — format file yang sama bisa dipertukarkan. +4. **Protocol scanner jadi lebih cerdas** — bukan cuma nampilin "RAW signal found", tapi "Princeton 24bit key=0x123456 ditemukan". +5. **Transmit dengan parameter yang benar** — protokol yang terdeteksi bisa ditransmit ulang dengan encoding yang tepat, bukan sekedar RAW replay. + +## File yang Akan Dimodifikasi + +| File | Perubahan | +|------|-----------| +| `src/modules/rf/protocols/rf_protocol.h` | Tambah field decode/encode callback di RfProtocolDef | +| `src/modules/rf/protocols/rf_decoder.h/.cpp` | Tambah dispatch logic untuk callback decoder | +| `src/modules/rf/protocols/rf_encoder.h/.cpp` | Tambah dispatch logic untuk callback encoder | +| `src/modules/rf/protocols/rf_registry.h/.cpp` | Tambah entry untuk semua protokol baru | +| `src/modules/rf/rf_scan.cpp` | Wire callback decoder ke pipeline scan | +| `src/modules/rf/rf_send.cpp` | Wire callback encoder ke send pipeline | +| `src/modules/rf/structs.h` | Tambah field baru di RfCodes untuk protokol spesifik | +| `src/modules/rf/save.cpp` | Update .sub serializer untuk protokol baru | + +### File Protokol Baru +`src/modules/rf/protocols/decoders/` +Setiap protokol complex mendapat file sendiri. + +## Implementation Plan + +### Phase 1: Arsitektur +- [ ] Tambah field callback di RfProtocolDef +- [ ] Update rf_decode_ook() untuk coba callback +- [ ] Update sendRfCommand() untuk encoder callback +- [ ] Update rf_registry dengan protokol baru (simple OOK dulu) + +### Phase 2: Simple OOK Decoders +- [ ] Tambah semua OOK protokol yang cocok dengan factor model +- [ ] Uji dengan sinyal real + +### Phase 3: Rolling Code Decoders +- [ ] Buat decoder FAAC SLH +- [ ] Buat decoder SecPlus v1/v2 +- [ ] Buat decoder Somfy RTS +- [ ] Buat decoder Hormann +- [ ] Buat decoder NiceFlor_S +- [ ] Uji dengan sinyal real + +### Phase 4: TPMS Decoders +- [ ] Buat decoder Schrader GG4 +- [ ] Buat decoder Ford +- [ ] Buat decoder Renault +- [ ] Buat decoder Citroen +- [ ] Buat decoder PMV107J (Toyota) +- [ ] Uji dengan sinyal real diff --git a/platformio.ini b/platformio.ini index 3a343da8eb..36d7d19ada 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ [platformio] default_envs = - m5stack-cardputer + ;m5stack-cardputer ;m5stack-sticks3 ;m5stack-cplus2 ;m5stack-cplus1_1 @@ -50,7 +50,7 @@ default_envs = ;lilygo-t-lora-pager ;smoochiee-board ;xk404 - ;reaper + reaper ;Phantom_S024R ;LAUNCHER_Phantom_S024R ;Marauder-Mini diff --git a/src/modules/rf/protocols/decoders/manchester_helpers.cpp b/src/modules/rf/protocols/decoders/manchester_helpers.cpp new file mode 100644 index 0000000000..6030199e09 --- /dev/null +++ b/src/modules/rf/protocols/decoders/manchester_helpers.cpp @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Manchester decode helpers ported from Flipper Zero firmware +// (GPL-3.0-or-later), lib/subghz/blocks/decoder.c / manchester_advance. +#include "manchester_helpers.h" + +static inline unsigned int mh_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +void manchester_reset(ManchesterState &s) { + s.state = 0; +} + +bool manchester_advance(ManchesterState &s, ManchesterEvent event, bool *data) { + bool result = false; + *data = false; + + switch (s.state) { + case 0: // Start + switch (event) { + case ManchesterEventShortHigh: + s.state = 1; // MidBit + break; + case ManchesterEventShortLow: + s.state = 1; // MidBit + break; + case ManchesterEventLongHigh: + *data = true; + result = true; + break; + case ManchesterEventLongLow: + *data = true; + result = true; + break; + default: + break; + } + break; + + case 1: // MidBit + switch (event) { + case ManchesterEventShortLow: + s.state = 2; // Done + break; + case ManchesterEventShortHigh: + s.state = 2; // Done + break; + case ManchesterEventLongLow: + *data = false; + result = true; + break; + case ManchesterEventLongHigh: + *data = false; + result = true; + break; + default: + break; + } + break; + + case 2: // Done + switch (event) { + case ManchesterEventShortLow: + *data = true; + result = true; + s.state = 0; + break; + case ManchesterEventShortHigh: + s.state = 0; + break; + case ManchesterEventLongLow: + *data = false; + result = true; + s.state = 0; + break; + case ManchesterEventLongHigh: + s.state = 0; + break; + default: + break; + } + break; + } + + return result; +} + +ManchesterEvent manchester_event_for(bool level, unsigned int dur, + unsigned int te_short, unsigned int te_long, + unsigned int te_delta) { + if (!level) { + if (mh_diff(dur, te_short) < te_delta) + return ManchesterEventShortLow; + if (mh_diff(dur, te_long) < te_delta) + return ManchesterEventLongLow; + } else { + if (mh_diff(dur, te_short) < te_delta) + return ManchesterEventShortHigh; + if (mh_diff(dur, te_long) < te_delta) + return ManchesterEventLongHigh; + } + return ManchesterEventReset; +} diff --git a/src/modules/rf/protocols/decoders/manchester_helpers.h b/src/modules/rf/protocols/decoders/manchester_helpers.h new file mode 100644 index 0000000000..3f1b878223 --- /dev/null +++ b/src/modules/rf/protocols/decoders/manchester_helpers.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +// Manchester event types (mirrors Flipper Zero lib/subghz/blocks/const.h). +enum ManchesterEvent : uint8_t { + ManchesterEventReset = 0, + ManchesterEventShortLow, + ManchesterEventShortHigh, + ManchesterEventLongLow, + ManchesterEventLongHigh, +}; + +// Manchester decoder state (mirrors Flipper manchester_advance). +struct ManchesterState { + uint8_t state = 0; // 0=start, 1=mid_bit, 2=done +}; + +// Reset the Manchester state machine. +void manchester_reset(ManchesterState &s); + +// Feed one event to the Manchester decoder. When a complete bit is decoded, +// sets `*data` to the bit value and returns true. Returns false when more +// events are needed. +bool manchester_advance(ManchesterState &s, ManchesterEvent event, bool *data); + +// Map a (level, unsigned duration, te_short, te_long, te_delta) quad to a +// ManchesterEvent. Returns ManchesterEventReset if the duration doesn't match. +ManchesterEvent manchester_event_for(bool level, unsigned int dur, + unsigned int te_short, unsigned int te_long, + unsigned int te_delta); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp b/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp new file mode 100644 index 0000000000..8251d3bcc4 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_faac_slh.h" +#include "../rf_config.h" + +#define FAAC_TE_SHORT 255 +#define FAAC_TE_LONG 595 +#define FAAC_TE_DELTA 100 +#define FAAC_MIN_BITS 64 + +static inline unsigned int faac_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_faac_slh(const std::vector& durations, RfCodes& out) { + enum { + ST_RESET, + ST_PREAMBLE, + ST_SAVE, + ST_CHECK + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && faac_diff(dur, FAAC_TE_LONG * 2) < FAAC_TE_DELTA * 3) + step = ST_PREAMBLE; + break; + + case ST_PREAMBLE: + if (!level && faac_diff(dur, FAAC_TE_LONG * 2) < FAAC_TE_DELTA * 3) { + data = 0; + bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (level) { + if (dur >= (unsigned int)(FAAC_TE_SHORT * 3 + FAAC_TE_DELTA)) { + if (bits >= FAAC_MIN_BITS) { + out.key = data; + out.Bit = FAAC_MIN_BITS; + out.te = FAAC_TE_SHORT; + out.protocol = "FAAC_SLH"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + } else { + te_last = dur; + step = ST_CHECK; + } + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (faac_diff(te_last, FAAC_TE_SHORT) < FAAC_TE_DELTA && + faac_diff(dur, FAAC_TE_LONG) < FAAC_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (faac_diff(te_last, FAAC_TE_LONG) < FAAC_TE_DELTA && + faac_diff(dur, FAAC_TE_SHORT) < FAAC_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + if (bits >= FAAC_MIN_BITS) { + out.key = data; + out.Bit = FAAC_MIN_BITS; + out.te = FAAC_TE_SHORT; + out.protocol = "FAAC_SLH"; + out.preset = "Ook270Async"; + return true; + } + + return false; +} + +bool rf_encode_faac_slh(const RfCodes& in, std::vector& out) { + out.push_back(FAAC_TE_LONG * 2); + out.push_back(-(FAAC_TE_LONG * 2)); + + for (int i = 63; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(FAAC_TE_LONG); + out.push_back(-FAAC_TE_SHORT); + } else { + out.push_back(FAAC_TE_SHORT); + out.push_back(-FAAC_TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.h b/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.h new file mode 100644 index 0000000000..7e77b3e7c1 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +// FAAC SLH (rolling code) protocol decoder callback. +// Decodes the 64-bit FAAC SLH frame: preamble (2×te_long HIGH + LOW), then +// 64 PWM bits (short+long=0, long+short=1). +bool rf_decode_faac_slh(const std::vector& durations, RfCodes& out); + +// FAAC SLH encoder callback. +bool rf_encode_faac_slh(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp b/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp new file mode 100644 index 0000000000..50d3a3e46e --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Part of Bruce (AGPL-3.0-or-later). Intertechno V3 decoder ported from +// Flipper Zero firmware (GPL-3.0-or-later), +// lib/subghz/protocols/intertechno_v3.c. +#include "rf_decoder_intertechno_v3.h" +#include "../rf_config.h" + +#define ITV3_TE_SHORT 275 +#define ITV3_TE_LONG 1375 +#define ITV3_TE_DELTA 150 +#define ITV3_MIN_BITS 32 +#define ITV3_DIMMING_BITS 36 + +static inline unsigned int itv3_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_intertechno_v3(const std::vector& durations, RfCodes& out) { + enum { + ST_RESET, + ST_HEADER, ST_SYNC_H, ST_SYNC_L, + ST_SAVE, ST_CHECK, ST_END + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && itv3_diff(dur, ITV3_TE_SHORT * 38) < ITV3_TE_DELTA * 15) + step = ST_HEADER; + break; + + case ST_HEADER: + if (level && itv3_diff(dur, ITV3_TE_SHORT) < ITV3_TE_DELTA) + step = ST_SYNC_H; + else + step = ST_RESET; + break; + + case ST_SYNC_H: + if (!level && itv3_diff(dur, ITV3_TE_SHORT * 10) < ITV3_TE_DELTA * 3) { + data = 0; + bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (!level) { + if (dur >= ITV3_TE_SHORT * 11) { + if (bits == ITV3_MIN_BITS || bits == ITV3_DIMMING_BITS) { + out.key = data; + out.Bit = bits; + out.te = ITV3_TE_SHORT; + out.protocol = "Intertechno_V3"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + } else { + te_last = dur; + step = ST_CHECK; + } + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (level) { + bool short_low = itv3_diff(te_last, ITV3_TE_SHORT) < ITV3_TE_DELTA; + bool long_low = itv3_diff(te_last, ITV3_TE_LONG) < ITV3_TE_DELTA * 2; + bool short_high = itv3_diff(dur, ITV3_TE_SHORT) < ITV3_TE_DELTA; + + if (short_low && short_high) { + // bit 0: previous LOW was SHORT, current HIGH is SHORT + data = (data << 1) | 0ULL; + bits++; + step = ST_END; + } else if (long_low && short_high) { + // bit 1: previous LOW was LONG, current HIGH is SHORT + data = (data << 1) | 1ULL; + bits++; + step = ST_END; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + + case ST_END: + if (!level) { + if (itv3_diff(dur, ITV3_TE_SHORT) < ITV3_TE_DELTA || + itv3_diff(dur, ITV3_TE_LONG) < ITV3_TE_DELTA * 2) { + step = ST_SAVE; + } else if (dur >= ITV3_TE_SHORT * 11) { + if (bits == ITV3_MIN_BITS || bits == ITV3_DIMMING_BITS) { + out.key = data; + out.Bit = bits; + out.te = ITV3_TE_SHORT; + out.protocol = "Intertechno_V3"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_intertechno_v3(const RfCodes& in, std::vector& out) { + out.clear(); + out.push_back(ITV3_TE_SHORT); + out.push_back(-(ITV3_TE_SHORT * 38)); + out.push_back(ITV3_TE_SHORT); + out.push_back(-(ITV3_TE_SHORT * 10)); + + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(ITV3_TE_SHORT); + out.push_back(-ITV3_TE_LONG); + out.push_back(ITV3_TE_SHORT); + out.push_back(-ITV3_TE_SHORT); + } else { + out.push_back(ITV3_TE_SHORT); + out.push_back(-ITV3_TE_SHORT); + out.push_back(ITV3_TE_SHORT); + out.push_back(-ITV3_TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.h b/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.h new file mode 100644 index 0000000000..7db4ecb580 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +// Intertechno V3 (home automation) decoder callback. +// Pulse-train OOK protocol, 32 or 36 bits, te=275µs. +bool rf_decode_intertechno_v3(const std::vector& durations, RfCodes& out); + +bool rf_encode_intertechno_v3(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp b/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp new file mode 100644 index 0000000000..a8e24054c4 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Part of Bruce (AGPL-3.0-or-later). PowerSmart decoder ported from +// Flipper Zero firmware (GPL-3.0-or-later), lib/subghz/protocols/power_smart.c. +#include "rf_decoder_power_smart.h" +#include "manchester_helpers.h" +#include "../rf_config.h" + +#define PS_TE_SHORT 225 +#define PS_TE_LONG 450 +#define PS_TE_DELTA 100 +#define PS_MIN_BITS 64 + +// Sync word: 0xFDxxxxxxxxAAxxxxxxxx +#define PS_HEADER 0xFD000000AA000000ULL +#define PS_HEADER_MASK 0xFF000000FF000000ULL + +static bool ps_check_valid(uint64_t packet) { + uint32_t data_1 = (uint32_t)((packet >> 40) & 0xFFFF); + uint32_t data_2 = (uint32_t)((~packet >> 8) & 0xFFFF); + uint8_t data_3 = (uint8_t)(packet >> 32) & 0xFF; + uint8_t data_4 = (uint8_t)(((~packet) & 0xFF) - 1); + return (data_1 == data_2) && (data_3 == data_4); +} + +bool rf_decode_power_smart(const std::vector& durations, RfCodes& out) { + ManchesterState ms; + manchester_reset(ms); + + uint64_t data = 0; + int bits = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + ManchesterEvent ev = manchester_event_for(level, dur, PS_TE_SHORT, PS_TE_LONG, PS_TE_DELTA); + if (ev != ManchesterEventReset) { + bool bit_val = false; + if (manchester_advance(ms, ev, &bit_val)) { + data = (data << 1) | !bit_val; + bits++; + } + } else { + manchester_reset(ms); + data = 0; + bits = 0; + } + + if (bits >= PS_MIN_BITS) { + if ((data & PS_HEADER_MASK) == PS_HEADER && ps_check_valid(data)) { + out.key = data; + out.Bit = PS_MIN_BITS; + out.te = PS_TE_SHORT; + out.protocol = "PowerSmart"; + out.preset = "Ook270Async"; + return true; + } + data = 0; + bits = 0; + } + } + return false; +} + +bool rf_encode_power_smart(const RfCodes& in, std::vector& out) { + out.clear(); + out.reserve(in.Bit * 2 + 4); + + // Use Flipper-style Manchester encoder: each data bit produces a + // (short,long) or (long,short) HIGH/LOW pair. The data bits are + // inverted before Manchester encoding. + for (int i = in.Bit - 1; i >= 0; i--) { + bool bit_val = !((in.key >> i) & 1ULL); + if (!bit_val) { // Manchester 0: ShortHigh + LongLow + out.push_back(PS_TE_SHORT); + out.push_back(-PS_TE_LONG); + } else { // Manchester 1: LongHigh + ShortLow + out.push_back(PS_TE_LONG); + out.push_back(-PS_TE_SHORT); + } + } + // Trailing gap + out.push_back(-(PS_TE_LONG * 1111)); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_power_smart.h b/src/modules/rf/protocols/decoders/rf_decoder_power_smart.h new file mode 100644 index 0000000000..dae2456723 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_power_smart.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +// PowerSmart (Anderson/Novar) decoder callback. +// Manchester-encoded 64-bit protocol, te=225µs. +bool rf_decode_power_smart(const std::vector& durations, RfCodes& out); + +bool rf_encode_power_smart(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/rf_decoder.cpp b/src/modules/rf/protocols/rf_decoder.cpp index 6156708450..9db03345d8 100644 --- a/src/modules/rf/protocols/rf_decoder.cpp +++ b/src/modules/rf/protocols/rf_decoder.cpp @@ -11,6 +11,7 @@ #include "../rf_utils.h" // setup_rf_rx, find_pulse_index, crc64_ecma, RMT defines #include "rf_config.h" // RF_DBG #include "rf_registry.h" +#include "rf_registry_ext.h" #include // --- Decode tuning (mirrors the classic OOK receiver) ---------------------- @@ -362,6 +363,16 @@ static bool rf_decode_chamberlain_9bit(const std::vector &durations, RfCode } bool rf_decode_ook(const std::vector &durations, RfCodes &out) { + // Try callback-based extended decoders first. Each walks the full + // duration vector independently looking for its own preamble pattern. + const int extCount = rf_protocol_ext_count(); + for (int p = 0; p < extCount; p++) { + const RfProtocolDef *ext = rf_protocol_ext_at(p); + if (ext->decode && ext->decode(durations, out)) { + return true; + } + } + if (rf_decode_chamberlain_9bit(durations, out)) return true; unsigned int timings[RF_MAX_CHANGES]; diff --git a/src/modules/rf/protocols/rf_encoder.cpp b/src/modules/rf/protocols/rf_encoder.cpp index 67cc63e3fb..8fb83af9a1 100644 --- a/src/modules/rf/protocols/rf_encoder.cpp +++ b/src/modules/rf/protocols/rf_encoder.cpp @@ -153,6 +153,21 @@ bool rf_encode_protocol( } bool rf_tx_protocol(uint64_t data, unsigned int bits, int te, const RfProtocolDef *def, int repeat) { + // Extended protocols with an encode callback are dispatched differently. + if (def->encode) { + RfCodes codes; + codes.key = data; + codes.Bit = bits; + codes.protocol = def->name; + std::vector durs; + if (!def->encode(codes, durs)) return false; + std::vector repeated; + repeated.reserve(durs.size() * repeat); + for (int r = 0; r < repeat; r++) + repeated.insert(repeated.end(), durs.begin(), durs.end()); + return rf_tx_durations(repeated); + } + std::vector durs; if (!rf_encode_protocol(data, bits, te, def, repeat, durs)) return false; diff --git a/src/modules/rf/protocols/rf_protocol.h b/src/modules/rf/protocols/rf_protocol.h index 4747f7c820..a85576dd41 100644 --- a/src/modules/rf/protocols/rf_protocol.h +++ b/src/modules/rf/protocols/rf_protocol.h @@ -1,7 +1,21 @@ #pragma once -#include "../structs.h" // HighLow +#include "../structs.h" // HighLow, RfCodes #include +#include + +// Forward declarations for callback signatures. +struct RfCodes; + +// --------------------------------------------------------------------------- +// Callback signatures for protocol-specific decode / encode. +// A decode callback receives raw pulse durations (µs, +HIGH/-LOW) and, on +// success, fills `out` and returns true. +// An encode callback receives the `RfCodes` payload and appends signed-µs +// durations to the `out` vector; returns true on success. +// --------------------------------------------------------------------------- +typedef bool (*RfDecodeCallback)(const std::vector& durations, RfCodes& out); +typedef bool (*RfEncodeCallback)(const RfCodes& in, std::vector& out); // Single source of truth for static OOK protocol definitions and radio // presets used by the RF module. Consumers (send / scan / replay) read @@ -28,6 +42,10 @@ struct RfPreset { // every pulse is a multiple of `te` µs, expressed as {high, low} counts. // `name` is the protocol identity written to `Protocol:` in the `.sub` file // and used for replay dispatch — choose neutral, stable names. +// +// Extended protocols that do NOT fit the factor model provide decode/encode +// callbacks instead. When a protocol has a decode callback the factor model +// is bypassed entirely. // --------------------------------------------------------------------------- struct RfProtocolDef { const char *name; @@ -38,10 +56,18 @@ struct RfProtocolDef { uint8_t bits; // typical payload length in bits (0 = variable) bool inverted; // inverted signal level uint8_t flags; // bitmask, see RF_PF_* below + + // Optional callback for protocol-specific decode. When non-NULL the + // factor model is skipped and this callback is invoked instead. + RfDecodeCallback decode; + // Optional callback for protocol-specific encode. When non-NULL the + // generic factor-based encoder is replaced by this callback. + RfEncodeCallback encode; }; // Protocol flags bitmask. enum RfProtocolFlags : uint8_t { RF_PF_HAS_SYNC = 0x01, // protocol uses a sync/pilot pulse RF_PF_FIXED_LEN = 0x02, // payload length is fixed (== bits) + RF_PF_HAS_DECODER = 0x04, // protocol has a custom decode callback }; diff --git a/src/modules/rf/protocols/rf_registry.cpp b/src/modules/rf/protocols/rf_registry.cpp index 1248ea1b3e..059f9bf162 100644 --- a/src/modules/rf/protocols/rf_registry.cpp +++ b/src/modules/rf/protocols/rf_registry.cpp @@ -5,6 +5,7 @@ // and contributors; the Flipper-name mapping mirrors the Flipper Zero firmware // (GPL-3.0-or-later). See THIRD_PARTY.md for full attribution. #include "rf_registry.h" +#include "rf_registry_ext.h" // Canonical static OOK protocol table. Timings use the classic factor model // ({high,low} multiples of `te` µs). The numbered "RcSwitch_N" entries mirror @@ -90,7 +91,7 @@ const RfProtocolDef *rf_find_protocol(const String &name) { for (const auto &p : rf_protocols) { if (wanted == p.name) return &p; } - return nullptr; + return rf_find_ext_protocol(name); } String rf_flipper_protocol_name(const String &canonical) { diff --git a/src/modules/rf/protocols/rf_registry_ext.cpp b/src/modules/rf/protocols/rf_registry_ext.cpp new file mode 100644 index 0000000000..b3847ba360 --- /dev/null +++ b/src/modules/rf/protocols/rf_registry_ext.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Part of Bruce (AGPL-3.0-or-later). Extended protocol registry for +// callback-based decoders that do not fit the classic factor-based OOK model +// (e.g. Manchester, rolling code, pulse-train, FSK). Protocol timing +// definitions are DERIVED FROM the Flipper Zero firmware (GPL-3.0-or-later). +// See THIRD_PARTY.md for full attribution. +#include "rf_registry_ext.h" +#include "decoders/rf_decoder_faac_slh.h" +#include "decoders/rf_decoder_power_smart.h" +#include "decoders/rf_decoder_intertechno_v3.h" + +#define DECODE RF_PF_HAS_DECODER + +static const RfProtocolDef rf_ext_protocols[] = { + // name te sync zero one bits inv flags decode encode + {"FAAC_SLH", 255, {0, 0}, {0, 0}, {0, 0}, 64, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_faac_slh, rf_encode_faac_slh}, + {"PowerSmart", 225, {0, 0}, {0, 0}, {0, 0}, 64, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_power_smart, rf_encode_power_smart}, + {"Intertechno_V3", 275, {0, 0}, {0, 0}, {0, 0}, 32, false, DECODE, + rf_decode_intertechno_v3, rf_encode_intertechno_v3}, +}; + +#undef DECODE + +static const int rf_ext_count = sizeof(rf_ext_protocols) / sizeof(rf_ext_protocols[0]); + +int rf_protocol_ext_count() { return rf_ext_count; } + +const RfProtocolDef *rf_protocol_ext_at(int index) { + if (index < 0 || index >= rf_ext_count) return nullptr; + return &rf_ext_protocols[index]; +} + +const RfProtocolDef *rf_find_ext_protocol(const String &name) { + for (int i = 0; i < rf_ext_count; i++) { + if (name == rf_ext_protocols[i].name) return &rf_ext_protocols[i]; + } + return nullptr; +} diff --git a/src/modules/rf/protocols/rf_registry_ext.h b/src/modules/rf/protocols/rf_registry_ext.h new file mode 100644 index 0000000000..98737099cd --- /dev/null +++ b/src/modules/rf/protocols/rf_registry_ext.h @@ -0,0 +1,19 @@ +#pragma once + +#include "rf_protocol.h" + +// Extended protocol registry for protocols that do NOT fit the classic +// factor-based OOK model (e.g. rolling code, Manchester, FSK). These +// entries carry decode/encode callbacks instead of factor timings. +// +// Accessors mirror the factor-based registry so the decoder can iterate +// all known protocols transparently. + +// Total number of extended protocol entries. +int rf_protocol_ext_count(); + +// Look up an extended protocol by index (0 .. count-1). +const RfProtocolDef *rf_protocol_ext_at(int index); + +// Find an extended protocol by name (canonical). Returns nullptr if none. +const RfProtocolDef *rf_find_ext_protocol(const String &name); From 469da738dc9bdde6387e6221d3cfd546560294fb Mon Sep 17 00:00:00 2001 From: Riza Date: Tue, 21 Jul 2026 17:22:58 +0700 Subject: [PATCH 2/2] feat: add 36 sub-ghz decoders from flipper zero Port remaining Flipper-compatible decoders: rolling-code (SecPlus, Somfy, Hormann, CAME, NiceFlor), TPMS (Schrader, Ford, Renault, Citroen, Toyota), and simple OOK (Marantec, Dooya, Legrand, etc.). Wire into scan pipeline and fix decoder dispatch and bounds-check bugs. --- SUBGHZ_PROTOCOL_DECODER_LIBRARY.md | 195 ------------- platformio.ini | 4 +- .../rf/protocols/decoders/rf_decoder_bett.cpp | 90 ++++++ .../rf/protocols/decoders/rf_decoder_bett.h | 6 + .../decoders/rf_decoder_came_atomo.cpp | 142 +++++++++ .../decoders/rf_decoder_came_atomo.h | 7 + .../decoders/rf_decoder_came_twee.cpp | 190 ++++++++++++ .../protocols/decoders/rf_decoder_came_twee.h | 7 + .../decoders/rf_decoder_chamb_code.cpp | 134 +++++++++ .../decoders/rf_decoder_chamb_code.h | 6 + .../decoders/rf_decoder_citroen_tpms.cpp | 57 ++++ .../decoders/rf_decoder_citroen_tpms.h | 6 + .../decoders/rf_decoder_dickert_mahs.cpp | 89 ++++++ .../decoders/rf_decoder_dickert_mahs.h | 6 + .../decoders/rf_decoder_doitrand.cpp | 96 +++++++ .../protocols/decoders/rf_decoder_doitrand.h | 6 + .../protocols/decoders/rf_decoder_dooya.cpp | 117 ++++++++ .../rf/protocols/decoders/rf_decoder_dooya.h | 6 + .../protocols/decoders/rf_decoder_elplast.cpp | 100 +++++++ .../protocols/decoders/rf_decoder_elplast.h | 6 + .../decoders/rf_decoder_faac_slh.cpp | 7 +- .../protocols/decoders/rf_decoder_feron.cpp | 101 +++++++ .../rf/protocols/decoders/rf_decoder_feron.h | 6 + .../decoders/rf_decoder_ford_tpms.cpp | 57 ++++ .../protocols/decoders/rf_decoder_ford_tpms.h | 6 + .../protocols/decoders/rf_decoder_gangqi.cpp | 93 ++++++ .../rf/protocols/decoders/rf_decoder_gangqi.h | 6 + .../protocols/decoders/rf_decoder_hay21.cpp | 76 +++++ .../rf/protocols/decoders/rf_decoder_hay21.h | 5 + .../protocols/decoders/rf_decoder_hollarm.cpp | 95 ++++++ .../protocols/decoders/rf_decoder_hollarm.h | 6 + .../decoders/rf_decoder_honeywell_wdb.cpp | 106 +++++++ .../decoders/rf_decoder_honeywell_wdb.h | 7 + .../protocols/decoders/rf_decoder_hormann.cpp | 124 ++++++++ .../protocols/decoders/rf_decoder_hormann.h | 7 + .../rf/protocols/decoders/rf_decoder_ido.cpp | 120 ++++++++ .../rf/protocols/decoders/rf_decoder_ido.h | 7 + .../decoders/rf_decoder_intertechno_v3.cpp | 8 +- .../protocols/decoders/rf_decoder_legrand.cpp | 98 +++++++ .../protocols/decoders/rf_decoder_legrand.h | 6 + .../decoders/rf_decoder_linear_delta3.cpp | 116 ++++++++ .../decoders/rf_decoder_linear_delta3.h | 7 + .../decoders/rf_decoder_magellan.cpp | 151 ++++++++++ .../protocols/decoders/rf_decoder_magellan.h | 6 + .../decoders/rf_decoder_marantec.cpp | 115 ++++++++ .../protocols/decoders/rf_decoder_marantec.h | 7 + .../decoders/rf_decoder_marantec24.cpp | 93 ++++++ .../decoders/rf_decoder_marantec24.h | 6 + .../decoders/rf_decoder_megacode.cpp | 109 +++++++ .../protocols/decoders/rf_decoder_megacode.h | 6 + .../decoders/rf_decoder_nero_radio.cpp | 147 ++++++++++ .../decoders/rf_decoder_nero_radio.h | 7 + .../decoders/rf_decoder_nero_sketch.cpp | 137 +++++++++ .../decoders/rf_decoder_nero_sketch.h | 7 + .../decoders/rf_decoder_nice_flor_s.cpp | 136 +++++++++ .../decoders/rf_decoder_nice_flor_s.h | 7 + .../decoders/rf_decoder_power_smart.cpp | 7 +- .../decoders/rf_decoder_renault_tpms.cpp | 50 ++++ .../decoders/rf_decoder_renault_tpms.h | 6 + .../decoders/rf_decoder_revers_rb2.cpp | 96 +++++++ .../decoders/rf_decoder_revers_rb2.h | 6 + .../protocols/decoders/rf_decoder_roger.cpp | 102 +++++++ .../rf/protocols/decoders/rf_decoder_roger.h | 6 + .../decoders/rf_decoder_schrader_tpms.cpp | 53 ++++ .../decoders/rf_decoder_schrader_tpms.h | 6 + .../decoders/rf_decoder_secplus_v1.cpp | 243 ++++++++++++++++ .../decoders/rf_decoder_secplus_v1.h | 7 + .../decoders/rf_decoder_secplus_v2.cpp | 272 ++++++++++++++++++ .../decoders/rf_decoder_secplus_v2.h | 7 + .../protocols/decoders/rf_decoder_smc5326.cpp | 104 +++++++ .../protocols/decoders/rf_decoder_smc5326.h | 7 + .../decoders/rf_decoder_somfy_keytis.cpp | 193 +++++++++++++ .../decoders/rf_decoder_somfy_keytis.h | 7 + .../decoders/rf_decoder_somfy_telis.cpp | 177 ++++++++++++ .../decoders/rf_decoder_somfy_telis.h | 7 + .../decoders/rf_decoder_toyota_tpms.cpp | 66 +++++ .../decoders/rf_decoder_toyota_tpms.h | 6 + .../rf/protocols/decoders/rf_decoder_tpms.cpp | 15 + .../rf/protocols/decoders/rf_decoder_tpms.h | 5 + .../decoders/rf_decoder_tpms_helpers.h | 153 ++++++++++ src/modules/rf/protocols/rf_registry.cpp | 3 +- src/modules/rf/protocols/rf_registry_ext.cpp | 116 ++++++++ src/modules/rf/rf_scan.cpp | 14 +- 83 files changed, 4793 insertions(+), 209 deletions(-) delete mode 100644 SUBGHZ_PROTOCOL_DECODER_LIBRARY.md create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_bett.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_bett.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_came_atomo.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_came_atomo.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_came_twee.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_came_twee.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_chamb_code.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_chamb_code.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_doitrand.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_doitrand.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_dooya.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_dooya.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_elplast.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_elplast.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_feron.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_feron.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_gangqi.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_gangqi.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_hay21.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_hay21.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_hollarm.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_hollarm.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_hormann.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_hormann.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_ido.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_ido.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_legrand.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_legrand.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_magellan.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_magellan.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_marantec.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_marantec.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_marantec24.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_marantec24.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_megacode.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_megacode.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_nero_radio.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_nero_radio.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_roger.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_roger.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_smc5326.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_smc5326.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_tpms.cpp create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_tpms.h create mode 100644 src/modules/rf/protocols/decoders/rf_decoder_tpms_helpers.h diff --git a/SUBGHZ_PROTOCOL_DECODER_LIBRARY.md b/SUBGHZ_PROTOCOL_DECODER_LIBRARY.md deleted file mode 100644 index 14567566d2..0000000000 --- a/SUBGHZ_PROTOCOL_DECODER_LIBRARY.md +++ /dev/null @@ -1,195 +0,0 @@ -# Sub-GHz Protocol Decoder Library - -## Masalah - -Bruce Firmware saat ini hanya bisa men-decode ~16 protokol OOK pasif (Princeton, CAME, NICE, Holtek, dll) + KeeLoq via factor matching (`te`, `sync`, `zero`, `one`). Sinyal yang tidak cocok dengan factor-faktor ini hanya bisa dicapture sebagai RAW — tidak diidentifikasi, tidak di-decode. - -Padahal CC1101 yang sama mampu menangkap **semua** protokol Sub-GHz di 300–928 MHz. Yang kurang adalah decoder library-nya. - -## Tujuan - -Menambahkan protocol decoder library yang komprehensif ke Bruce, mengadopsi decoder-decoder dari Flipper Zero protocol stack. Target: **59 protokol total** (Bruce saat ini ~17 + 12 generic = 29, tapi generic tidak berguna). - -## Arsitektur - -### Pendekatan: Hybrid Factor + Callback - -``` - ┌──────────────────────────┐ - │ RMT RX (CC1101 GDO0) │ - │ durations[] (signed µs)│ - └──────────┬───────────────┘ - │ - ┌──────────▼───────────────┐ - │ Signal Segmentation │ - │ (gap detection, noise │ - │ filtering, glitch │ - │ rejection) │ - └──────────┬───────────────┘ - │ - ┌──────────▼───────────────┐ - │ Protocol Scan Loop │ - │ │ - │ ┌─────────────────────┐ │ - │ │ Factor Match (Cepat) │──│── OOK protokol sederhana - │ │ te/sync/zero/one │ │ (Princeton, CAME, dll) - │ └─────────────────────┘ │ - │ │ - │ ┌─────────────────────┐ │ - │ │ Callback Decode │──│── Protokol kompleks - │ │ (decode_cb per │ │ (FAAC, Somfy, TPMS, - │ │ protokol) │ │ SecPlus, Hormann) - │ └─────────────────────┘ │ - └──────────┬───────────────┘ - │ - ┌──────────▼───────────────┐ - │ Post-Processing │ - │ - Rolling code unlock │ - │ - Manufacturer ident │ - │ - .sub serialization │ - └──────────────────────────┘ -``` - -### Extension pada RfProtocolDef - -```cpp -struct RfProtocolDef { - const char *name; // Nama protokol - uint16_t te; // Base pulse (µs) - HighLow sync; // Sync pulse factor - HighLow zero; // Bit 0 encoding - HighLow one; // Bit 1 encoding - uint8_t bits; // Panjang bit (0 = variable) - bool inverted; // Signal level inverted? - uint8_t flags; // RF_PF_HAS_SYNC, RF_PF_FIXED_LEN, dll - - // --- BARU: Callback untuk protokol kompleks --- - bool (*decode)(const std::vector& durations, RfCodes& out); - bool (*encode)(const RfCodes& in, std::vector& durations); -}; -``` - -Jika `decode` == NULL → gunakan factor matching existing. -Jika `decode` != NULL → panggil callback. -Encoder juga sama. - -## Protocol List Target - -### Existing di Bruce (13 protokol OOK + KeeLoq + Chamberlain) -Tetap dipertahankan dengan factor matching. - -### Baru dari Flipper Zero Port (41 protokol baru) - -#### Simple OOK (factor-based — mudah ditambah) -| Protocol | Bits | te | Sync | Zero | One | -|----------|------|----|------|------|-----| -| Megacode | 48 | 400 | - | - | - | -| PowerSmart | 48 | 382 | - | - | - | -| Marantec | 24 | 450 | - | - | - | -| Marantec24 | 24 | 500 | - | - | - | -| Intertechno_V3 | 56 | 560 | - | - | - | -| SMC5326 | 48 | 400 | - | - | - | -| Linear_Delta3 | 42 | 500 | - | - | - | -| Dooya | 48 | 600 | - | - | - | -| Honeywell | 48 | 400 | - | - | - | -| Magellan | 56 | 300 | - | - | - | -| Nero_Sketch | 48 | 600 | - | - | - | -| Nero_Radio | 48 | 350 | - | - | - | -| IDo | 56 | 650 | - | - | - | -| Bett | 48 | 450 | - | - | - | -| Doitrand | 42 | 400 | - | - | - | -| Gangi | - | - | - | - | - | -| HollArm | - | - | - | - | - | -| Hay21 | - | - | - | - | - | -| Revers_RB2 | - | - | - | - | - | -| Feron | - | - | - | - | - | -| Roger | - | - | - | - | - | -| Elplast | - | - | - | - | - | -| Treadmill37 | - | - | - | - | - | -| Beninca_Arc | - | - | - | - | - | -| Jarolift | - | - | - | - | - | -| Ditec_Gol4 | - | - | - | - | - | -| KeyFinder | - | - | - | - | - | -| Kinggates_Stylo_4K | - | - | - | - | - | -| Legrand | - | - | - | - | - | -| Dickert_MAHS | - | - | - | - | - | -| Alutech_at_4N | - | - | - | - | - | - -Note: Simple OOK di atas perlu dianalisis timing-nya lebih lanjut. Beberapa mungkin punya sync pattern atau encoding yang berbeda. - -#### Rolling Code / Kompleks (callback-based) -| Protocol | Tipe | Notes | -|----------|------|-------| -| FAAC_SLH | Rolling code | Proprietary FAAC algorithm | -| NiceFlor_S | Rolling code | Nice FLO-R (flor) | -| SecPlus_v1 | Rolling code | Chamberlain Security+ v1 | -| SecPlus_v2 | Rolling code | Chamberlain Security+ v2 | -| Hormann | Rolling code | HSM rolling code | -| Somfy_Telis | Rolling code | Somfy RTS | -| Somfy_Keytis | Rolling code | Somfy RTS | -| CAME_Twee | Rolling code | CAME Twin protocol | -| CAME_Atomo | Rolling code | CAME Atomo | -| Honeywell_WDB | Roll/timed | Honeywell wireless doorbell | -| Chamb_Code | Fixed | Chamberlain 9-bit (Bruce sudah punya) | - -#### TPMS (callback-based — Manchester/PWM decoding) -| Protocol | Notes | -|----------|-------| -| Schrader_GG4 | Tire pressure sensor | -| Ford | Ford TPMS | -| Renault | Renault TPMS | -| Citroen | Citroen TPMS | -| PMV107J | Toyota TPMS | - -## Dampak Positif - -1. **Dari 13 → ~55 protokol yang otomatis terdeteksi** — sinyal yang tadinya cuma RAW sekarang kebaca namanya, termasuk rolling code. -2. **Tidak perlu ganti hardware** — CC1101 yang sudah dipakai Bruce cukup. Decoder adalah pure software. -3. **.sub file compatibility dengan Flipper Zero** — format file yang sama bisa dipertukarkan. -4. **Protocol scanner jadi lebih cerdas** — bukan cuma nampilin "RAW signal found", tapi "Princeton 24bit key=0x123456 ditemukan". -5. **Transmit dengan parameter yang benar** — protokol yang terdeteksi bisa ditransmit ulang dengan encoding yang tepat, bukan sekedar RAW replay. - -## File yang Akan Dimodifikasi - -| File | Perubahan | -|------|-----------| -| `src/modules/rf/protocols/rf_protocol.h` | Tambah field decode/encode callback di RfProtocolDef | -| `src/modules/rf/protocols/rf_decoder.h/.cpp` | Tambah dispatch logic untuk callback decoder | -| `src/modules/rf/protocols/rf_encoder.h/.cpp` | Tambah dispatch logic untuk callback encoder | -| `src/modules/rf/protocols/rf_registry.h/.cpp` | Tambah entry untuk semua protokol baru | -| `src/modules/rf/rf_scan.cpp` | Wire callback decoder ke pipeline scan | -| `src/modules/rf/rf_send.cpp` | Wire callback encoder ke send pipeline | -| `src/modules/rf/structs.h` | Tambah field baru di RfCodes untuk protokol spesifik | -| `src/modules/rf/save.cpp` | Update .sub serializer untuk protokol baru | - -### File Protokol Baru -`src/modules/rf/protocols/decoders/` -Setiap protokol complex mendapat file sendiri. - -## Implementation Plan - -### Phase 1: Arsitektur -- [ ] Tambah field callback di RfProtocolDef -- [ ] Update rf_decode_ook() untuk coba callback -- [ ] Update sendRfCommand() untuk encoder callback -- [ ] Update rf_registry dengan protokol baru (simple OOK dulu) - -### Phase 2: Simple OOK Decoders -- [ ] Tambah semua OOK protokol yang cocok dengan factor model -- [ ] Uji dengan sinyal real - -### Phase 3: Rolling Code Decoders -- [ ] Buat decoder FAAC SLH -- [ ] Buat decoder SecPlus v1/v2 -- [ ] Buat decoder Somfy RTS -- [ ] Buat decoder Hormann -- [ ] Buat decoder NiceFlor_S -- [ ] Uji dengan sinyal real - -### Phase 4: TPMS Decoders -- [ ] Buat decoder Schrader GG4 -- [ ] Buat decoder Ford -- [ ] Buat decoder Renault -- [ ] Buat decoder Citroen -- [ ] Buat decoder PMV107J (Toyota) -- [ ] Uji dengan sinyal real diff --git a/platformio.ini b/platformio.ini index 36d7d19ada..3a343da8eb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ [platformio] default_envs = - ;m5stack-cardputer + m5stack-cardputer ;m5stack-sticks3 ;m5stack-cplus2 ;m5stack-cplus1_1 @@ -50,7 +50,7 @@ default_envs = ;lilygo-t-lora-pager ;smoochiee-board ;xk404 - reaper + ;reaper ;Phantom_S024R ;LAUNCHER_Phantom_S024R ;Marauder-Mini diff --git a/src/modules/rf/protocols/decoders/rf_decoder_bett.cpp b/src/modules/rf/protocols/decoders/rf_decoder_bett.cpp new file mode 100644 index 0000000000..df3b2dd47e --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_bett.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_bett.h" +#include "../rf_config.h" + +#define BETT_TE_SHORT 340 +#define BETT_TE_LONG 2000 +#define BETT_TE_DELTA 150 +#define BETT_MIN_BITS 18 + +static inline unsigned int bett_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_bett(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_CHECK, ST_DATA } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && bett_diff(dur, BETT_TE_SHORT * 44) < BETT_TE_DELTA * 15) { + data = 0; bits = 0; + step = ST_CHECK; + } + break; + + case ST_CHECK: + if (level) { + if (bett_diff(dur, BETT_TE_LONG) < BETT_TE_DELTA * 3) { + data = (data << 1) | 1ULL; + bits++; + step = ST_DATA; + } else if (bett_diff(dur, BETT_TE_SHORT) < BETT_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_DATA; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + + case ST_DATA: + if (!level) { + if (bett_diff(dur, BETT_TE_SHORT * 44) < BETT_TE_DELTA * 15) { + if (bits == BETT_MIN_BITS) { + out.key = data; + out.Bit = BETT_MIN_BITS; + out.te = BETT_TE_SHORT; + out.protocol = "Bett"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_CHECK; + } else if (bett_diff(dur, BETT_TE_SHORT) < BETT_TE_DELTA || + bett_diff(dur, BETT_TE_LONG) < BETT_TE_DELTA * 3) { + step = ST_CHECK; + } else { + step = ST_RESET; + } + } + break; + } + } + + return false; +} + +bool rf_encode_bett(const RfCodes& in, std::vector& out) { + if (in.Bit != BETT_MIN_BITS) return false; + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(BETT_TE_LONG); + out.push_back(-BETT_TE_SHORT); + } else { + out.push_back(BETT_TE_SHORT); + out.push_back(-BETT_TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_bett.h b/src/modules/rf/protocols/decoders/rf_decoder_bett.h new file mode 100644 index 0000000000..934729c19f --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_bett.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_bett(const std::vector& durations, RfCodes& out); +bool rf_encode_bett(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_came_atomo.cpp b/src/modules/rf/protocols/decoders/rf_decoder_came_atomo.cpp new file mode 100644 index 0000000000..7ab28a2bbb --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_came_atomo.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_came_atomo.h" +#include "../rf_config.h" + +#define CA_TE_SHORT 600 +#define CA_TE_LONG 1200 +#define CA_TE_DELTA 250 +#define CA_MIN_BITS 62 + +static inline unsigned int ca_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +typedef enum { ME_RESET, ME_LOW, ME_HIGH } manchester_state; + +static bool manchester_advance(manchester_state& state, bool high_pulse, bool& bit) { + switch (state) { + case ME_RESET: + state = high_pulse ? ME_HIGH : ME_LOW; + return false; + case ME_LOW: + if (high_pulse) { state = ME_HIGH; return false; } + bit = 1; state = ME_LOW; return true; + case ME_HIGH: + if (!high_pulse) { state = ME_LOW; return false; } + bit = 0; state = ME_HIGH; return true; + } + return false; +} + +static void manchester_reset(manchester_state& state) { + state = ME_RESET; +} + +static const uint64_t ca_default_xor[32] = { + 0x1fafef3ed0f7d9efULL, 0x185fcc1531ee86e7ULL, 0x184fa96912c567ffULL, 0x187f8a42f3dc38f7ULL, + 0x186f63915492a5cdULL, 0x181f40bab58bfac5ULL, 0x180f25c696a01bddULL, 0x183f06ed77b944d5ULL, + 0x182ef661d83d21a9ULL, 0x18ded54a39247ea1ULL, 0x18ceb0361a0f9fb9ULL, 0x18fe931dfb16c0b1ULL, + 0x18ee7ace5c585d8bULL, 0x181e59e5bd410283ULL, 0x180e3c999e6ae39bULL, 0x183e1fb27f73bc93ULL, + 0x184fcc1531ee86e7ULL, 0x18bfef3ed0f7d9efULL, 0x18af8a42f3dc38f7ULL, 0x189fa96912c567ffULL, + 0x188f63915492a5cdULL, 0x187f40bab58bfac5ULL, 0x186f25c696a01bddULL, 0x185f06ed77b944d5ULL, + 0x182ef661d83d21a9ULL, 0x18ded54a39247ea1ULL, 0x18ceb0361a0f9fb9ULL, 0x18fe931dfb16c0b1ULL, + 0x18ee7ace5c585d8bULL, 0x181e59e5bd410283ULL, 0x180e3c999e6ae39bULL, 0x183e1fb27f73bc93ULL, +}; + +bool rf_decode_came_atomo(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_DATA } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + manchester_state man_state; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && ca_diff(dur, CA_TE_LONG * 60) < CA_TE_DELTA * 40) { + data = 0; bits = 1; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, false, dummy); + step = ST_DATA; + } + break; + + case ST_DATA: { + if (!level) { + if (ca_diff(dur, CA_TE_SHORT) < CA_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else if (ca_diff(dur, CA_TE_LONG) < CA_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else if (dur >= (uint32_t)(CA_TE_LONG * 2 + CA_TE_DELTA)) { + if (bits == CA_MIN_BITS) { + uint16_t parcel_counter = (uint16_t)(data >> 48); + parcel_counter = parcel_counter ^ 0x185F; + parcel_counter >>= 4; + uint8_t ind = (parcel_counter + 1) % 32; + uint64_t temp_data = data & 0x0000FFFFFFFFFFFFULL; + uint64_t magic = ca_default_xor[ind]; + + temp_data = temp_data ^ magic; + uint32_t cnt = (uint32_t)(temp_data >> 36); + uint32_t serial = (uint32_t)((temp_data >> 4) & 0x000FFFFFFFFULL); + uint8_t btn = (uint8_t)(temp_data & 0xF); + + out.key = data; + out.Bit = CA_MIN_BITS; + out.te = CA_TE_SHORT; + out.protocol = "CAME_Atomo"; + out.preset = "Ook270Async"; + out.serial = serial; + out.cnt = cnt; + out.btn = btn; + return true; + } + data = 0; bits = 1; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, false, dummy); + } else { + step = ST_RESET; + } + } else { + if (ca_diff(dur, CA_TE_SHORT) < CA_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else if (ca_diff(dur, CA_TE_LONG) < CA_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else { + step = ST_RESET; + } + } + break; + } + } + } + + return false; +} + +bool rf_encode_came_atomo(const RfCodes& in, std::vector& out) { + (void)in; (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_came_atomo.h b/src/modules/rf/protocols/decoders/rf_decoder_came_atomo.h new file mode 100644 index 0000000000..9a0fd53bfc --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_came_atomo.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_came_atomo(const std::vector& durations, RfCodes& out); +bool rf_encode_came_atomo(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_came_twee.cpp b/src/modules/rf/protocols/decoders/rf_decoder_came_twee.cpp new file mode 100644 index 0000000000..4120bc09d1 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_came_twee.cpp @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_came_twee.h" +#include "../rf_config.h" + +#define CT_TE_SHORT 500 +#define CT_TE_LONG 1000 +#define CT_TE_DELTA 250 +#define CT_MIN_BITS 54 + +static const uint32_t ct_magic_xor[15] = { + 0x0E0E0E00, 0x1D1D1D11, 0x2C2C2C22, 0x3B3B3B33, + 0x4A4A4A44, 0x59595955, 0x68686866, 0x77777777, + 0x86868688, 0x95959599, 0xA4A4A4AA, 0xB3B3B3BB, + 0xC2C2C2CC, 0xD1D1D1DD, 0xE0E0E0EE, +}; + +static inline unsigned int ct_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static uint16_t reverse_key_16(uint16_t data) { + uint16_t rev = 0; + for (int i = 0; i < 16; i++) { + rev = (rev << 1) | ((data >> i) & 1U); + } + return rev; +} + +typedef enum { ME_RESET, ME_LOW, ME_HIGH } manchester_state; + +static bool manchester_advance(manchester_state& state, bool high_pulse, bool& bit) { + switch (state) { + case ME_RESET: + state = high_pulse ? ME_HIGH : ME_LOW; + return false; + case ME_LOW: + if (high_pulse) { state = ME_HIGH; return false; } + bit = 1; state = ME_LOW; return true; + case ME_HIGH: + if (!high_pulse) { state = ME_LOW; return false; } + bit = 0; state = ME_HIGH; return true; + } + return false; +} + +static void manchester_reset(manchester_state& state) { + state = ME_RESET; +} + +bool rf_decode_came_twee(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_DATA } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + manchester_state man_state; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && ct_diff(dur, CT_TE_LONG * 51) < CT_TE_DELTA * 20) { + data = 0; bits = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, false, dummy); + manchester_advance(man_state, true, dummy); + manchester_advance(man_state, false, dummy); + step = ST_DATA; + } + break; + + case ST_DATA: { + if (!level) { + if (ct_diff(dur, CT_TE_SHORT) < CT_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else if (ct_diff(dur, CT_TE_LONG) < CT_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else if (dur >= (uint32_t)(CT_TE_LONG * 2 + CT_TE_DELTA)) { + if (bits == CT_MIN_BITS) { + uint8_t cnt_parcel = (uint8_t)(data & 0xF); + uint32_t d = (uint32_t)(data & 0x0FFFFFFFF); + d = d ^ ct_magic_xor[cnt_parcel]; + uint32_t serial = d; + d /= 4; + uint8_t btn = (d >> 4) & 0x0F; + d >>= 16; + uint16_t dip = reverse_key_16((uint16_t)d); + uint16_t cnt = dip >> 6; + + out.key = data; + out.Bit = CT_MIN_BITS; + out.te = CT_TE_SHORT; + out.protocol = "CAME_Twee"; + out.preset = "Ook270Async"; + out.serial = serial; + out.cnt = cnt; + out.btn = btn; + return true; + } + data = 0; bits = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, false, dummy); + manchester_advance(man_state, true, dummy); + manchester_advance(man_state, false, dummy); + } else { + step = ST_RESET; + } + } else { + if (ct_diff(dur, CT_TE_SHORT) < CT_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else if (ct_diff(dur, CT_TE_LONG) < CT_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 0ULL : 1ULL); + bits++; + } + } else { + step = ST_RESET; + } + } + break; + } + } + } + + return false; +} + +bool rf_encode_came_twee(const RfCodes& in, std::vector& out) { + if (in.Bit != CT_MIN_BITS) return false; + + for (int parcel = 14; parcel >= 0; parcel--) { + uint64_t temp = 0x003FFF7200000000ULL | + ((uint64_t)(in.serial ^ ct_magic_xor[parcel]) & 0xFFFFFFFF); + + manchester_state enc_state; + manchester_reset(enc_state); + + // Manchester encode + uint8_t man_bits[200]; + int man_idx = 0; + bool mid_bit = false; + bool need_mid = false; + + for (int i = CT_MIN_BITS - 1; i >= 0; i--) { + bool bit_val = !((temp >> i) & 1ULL); + if (!need_mid) { + if (bit_val) { + man_bits[man_idx++] = 0; + man_bits[man_idx++] = 1; + } else { + man_bits[man_idx++] = 1; + man_bits[man_idx++] = 0; + } + } + need_mid = !need_mid; + } + + for (int i = 0; i < man_idx; i++) { + if (man_bits[i]) { + out.push_back(CT_TE_LONG); + } else { + out.push_back(CT_TE_SHORT); + } + // Add low duration + if (i + 1 < man_idx) { + out.push_back(-CT_TE_SHORT); + } else { + out.push_back(-CT_TE_LONG * 51); + } + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_came_twee.h b/src/modules/rf/protocols/decoders/rf_decoder_came_twee.h new file mode 100644 index 0000000000..6330c9c51a --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_came_twee.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_came_twee(const std::vector& durations, RfCodes& out); +bool rf_encode_came_twee(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_chamb_code.cpp b/src/modules/rf/protocols/decoders/rf_decoder_chamb_code.cpp new file mode 100644 index 0000000000..adda316acc --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_chamb_code.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_chamb_code.h" +#include "../rf_config.h" + +#define TE_SHORT 1000 +#define TE_LONG 3000 +#define TE_DELTA 200 +#define MIN_BITS 10 + +#define BIT_STOP 0b0001 +#define BIT_1 0b0011 +#define BIT_0 0b0111 + +#define MASK_7 0xF000000FF0FULL +#define MASK_8 0xF00000F00FULL +#define MASK_9 0xF000000000FULL +#define MASK7_CHK 0x10000001101ULL +#define MASK8_CHK 0x1000001001ULL +#define MASK9_CHK 0x10000000001ULL + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +static bool chamb_code_to_bit(uint64_t& data, int size) { + uint64_t tmp = data; + data = 0; + for (int i = 0; i < size; i++) { + if ((tmp & 0xF) == BIT_0) { + } else if ((tmp & 0xF) == BIT_1) { + data |= (1ULL << i); + } else { + return false; + } + tmp >>= 4; + } + return true; +} + +static bool chamb_check_mask(uint64_t& data, int& nbits) { + if ((data & MASK_7) == MASK7_CHK) { + nbits = 7; + data &= ~MASK_7; + data = ((data >> 12) & 0xF) | ((data >> 4) & 0xF0); + } else if ((data & MASK_8) == MASK8_CHK) { + nbits = 8; + data &= ~MASK_8; + data = (data >> 4) | (BIT_0 << 8); + } else if ((data & MASK_9) == MASK9_CHK) { + nbits = 9; + data &= ~MASK_9; + data >>= 4; + } else { + return false; + } + return chamb_code_to_bit(data, nbits); +} + +bool rf_decode_chamb_code(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_START, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_SHORT * 39) < TE_DELTA * 20) { + step = ST_START; + } + break; + + case ST_START: + if (level && DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + data = 0; bits = 0; + data = (data << 4) | BIT_STOP; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (!level) { + if (dur > (unsigned int)(TE_SHORT * 5)) { + int nbits = bits; + if (bits >= MIN_BITS && chamb_check_mask(data, nbits)) { + out.key = data; + out.Bit = nbits; + out.te = TE_SHORT; + out.protocol = "Chamb_Code"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + } else { + te_last = dur; + step = ST_CHECK; + } + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (level) { + if (DURATION_DIFF((int)te_last, TE_SHORT * 3) < TE_DELTA && + DURATION_DIFF((int)dur, (int)TE_SHORT) < TE_DELTA) { + data = (data << 4) | BIT_STOP; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF((int)te_last, TE_SHORT * 2) < TE_DELTA && + DURATION_DIFF((int)dur, TE_SHORT * 2) < TE_DELTA) { + data = (data << 4) | BIT_1; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF((int)te_last, (int)TE_SHORT) < TE_DELTA && + DURATION_DIFF((int)dur, TE_SHORT * 3) < TE_DELTA) { + data = (data << 4) | BIT_0; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_chamb_code.h b/src/modules/rf/protocols/decoders/rf_decoder_chamb_code.h new file mode 100644 index 0000000000..1474ccf7c2 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_chamb_code.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_chamb_code(const std::vector& durations, RfCodes& out); +bool rf_encode_chamb_code(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.cpp b/src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.cpp new file mode 100644 index 0000000000..be40c1f17e --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.cpp @@ -0,0 +1,57 @@ +#include "rf_decoder_citroen_tpms.h" +#include "rf_decoder_tpms.h" +#include "rf_decoder_tpms_helpers.h" +#include "../rf_config.h" + +#define CI_TE 52 +#define CI_TE_DELTA 18 +#define CI_SYNC "10101010101010110" + +bool rf_decode_citroen_tpms(const std::vector& durations, RfCodes& out) { + if (durations.size() < 32) return false; + + auto bits = tpms_durations_to_bitmap(durations, CI_TE, CI_TE_DELTA); + if (bits.size() < 80) return false; + + uint32_t sync_len = 17; + uint32_t off = tpms_bitmap_seek_bits(bits, 0, (uint32_t)bits.size(), CI_SYNC); + if (off == TPMS_SEEK_NOT_FOUND) return false; + + off += sync_len; + + uint8_t raw[10]; + uint32_t decoded = tpms_convert_from_line_code( + raw, sizeof(raw), bits, off, (uint32_t)bits.size(), "01", "10"); + + if (decoded < 80) return false; + + uint8_t crc = 0; + for (int j = 1; j < 10; j++) crc ^= raw[j]; + if (crc != 0) return false; + + int repeat = raw[5] & 0xf; + float kpa = (float)raw[6] * 1.364f; + int temp = raw[7] - 50; + int battery = raw[8]; + + uint32_t id_val = ((uint32_t)raw[1] << 24) | ((uint32_t)raw[2] << 16) | + ((uint32_t)raw[3] << 8) | raw[4]; + + out.key = ((uint64_t)id_val << 32) | + ((uint32_t)((int)(kpa * 10)) << 16) | + ((uint32_t)(temp + 100) & 0xFFFF); + out.serial = id_val; + out.cnt = (uint16_t)repeat; + out.fix = (uint32_t)battery; + out.Bit = 80; + out.te = CI_TE; + out.protocol = "Citroen_TPMS"; + out.preset = "2FSKDev238Async"; + return true; +} + +bool rf_encode_citroen_tpms(const RfCodes& in, std::vector& out) { + (void)in; + (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.h b/src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.h new file mode 100644 index 0000000000..18d92ac4e8 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_citroen_tpms.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_citroen_tpms(const std::vector& durations, RfCodes& out); +bool rf_encode_citroen_tpms(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.cpp b/src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.cpp new file mode 100644 index 0000000000..3be71b74e5 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_dickert_mahs.h" +#include "../rf_config.h" + +#define TE_SHORT 400 +#define TE_LONG 800 +#define TE_DELTA 100 +#define MIN_BITS 36 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_dickert_mahs(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_INIT, ST_REC } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int tmp[2] = {0, 0}; + int tmp_cnt = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (bits >= MIN_BITS) { + out.key = data; + out.Bit = bits; + out.te = TE_SHORT; + out.protocol = "Dickert_MAHS"; + out.preset = "Ook270Async"; + return true; + } + if (!level && DURATION_DIFF(dur, TE_LONG * 50) < TE_DELTA * 70) { + step = ST_INIT; + } + break; + + case ST_INIT: + if (!level) { + break; + } else if (DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + data = 0; bits = 0; tmp_cnt = 0; + step = ST_REC; + } else { + step = ST_RESET; + } + break; + + case ST_REC: + if ((!level && tmp_cnt == 0) || (level && tmp_cnt == 1)) { + tmp[tmp_cnt] = dur; + tmp_cnt++; + if (tmp_cnt == 2) { + if (DURATION_DIFF((int)(tmp[0] + tmp[1]), 1200) < TE_DELTA) { + if (DURATION_DIFF((int)tmp[0], (int)TE_LONG) < TE_DELTA) + { data = (data << 1) | 1ULL; bits++; } + else if (DURATION_DIFF((int)tmp[0], (int)TE_SHORT) < TE_DELTA) + { data = (data << 1) | 0ULL; bits++; } + tmp_cnt = 0; + } else { + tmp_cnt = 0; + step = ST_RESET; + } + } + } else { + tmp_cnt = 0; + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_dickert_mahs(const RfCodes& in, std::vector& out) { + if (in.Bit < MIN_BITS) return false; + out.push_back(-(TE_LONG * 50)); + out.push_back(TE_SHORT); + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(-TE_LONG); + out.push_back(TE_SHORT); + } else { + out.push_back(-TE_SHORT); + out.push_back(TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.h b/src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.h new file mode 100644 index 0000000000..53646520cd --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_dickert_mahs.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_dickert_mahs(const std::vector& durations, RfCodes& out); +bool rf_encode_dickert_mahs(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_doitrand.cpp b/src/modules/rf/protocols/decoders/rf_decoder_doitrand.cpp new file mode 100644 index 0000000000..10046fd567 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_doitrand.cpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_doitrand.h" +#include "../rf_config.h" + +#define TE_SHORT 400 +#define TE_LONG 1100 +#define TE_DELTA 150 +#define MIN_BITS 37 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_doitrand(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_START, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_SHORT * 62) < TE_DELTA * 30) { + step = ST_START; + } + break; + + case ST_START: + if (level && DURATION_DIFF(dur, TE_SHORT * 2) < TE_DELTA * 3) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (!level) { + if (dur >= (unsigned int)(TE_SHORT * 10 + TE_DELTA)) { + if (bits == MIN_BITS) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "Doitrand"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_START; + } else { + te_last = dur; + step = ST_CHECK; + } + } + break; + + case ST_CHECK: + if (level) { + if (DURATION_DIFF(te_last, TE_SHORT) < TE_DELTA && + DURATION_DIFF(dur, TE_LONG) < TE_DELTA * 3) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, TE_LONG) < TE_DELTA * 3 && + DURATION_DIFF(dur, TE_SHORT) < TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_doitrand(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + out.push_back(-(TE_SHORT * 62)); + out.push_back(TE_SHORT * 2 - 100); + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(-TE_LONG); + out.push_back(TE_SHORT); + } else { + out.push_back(-TE_SHORT); + out.push_back(TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_doitrand.h b/src/modules/rf/protocols/decoders/rf_decoder_doitrand.h new file mode 100644 index 0000000000..66e86357f7 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_doitrand.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_doitrand(const std::vector& durations, RfCodes& out); +bool rf_encode_doitrand(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_dooya.cpp b/src/modules/rf/protocols/decoders/rf_decoder_dooya.cpp new file mode 100644 index 0000000000..42e7b866c4 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_dooya.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_dooya.h" +#include "../rf_config.h" + +#define TE_SHORT 366 +#define TE_LONG 733 +#define TE_DELTA 120 +#define MIN_BITS 40 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_dooya(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_START, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_LONG * 12) < TE_DELTA * 20) { + step = ST_START; + } + break; + + case ST_START: + if (!level) { + if (DURATION_DIFF(dur, TE_LONG * 2) < TE_DELTA * 3) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else if (DURATION_DIFF(dur, TE_SHORT * 13) < TE_DELTA * 5) { + break; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (dur >= (unsigned int)(TE_LONG * 4)) { + if (DURATION_DIFF(te_last, TE_SHORT) < TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + } else if (DURATION_DIFF(te_last, TE_LONG) < TE_DELTA * 2) { + data = (data << 1) | 1ULL; + bits++; + } else { + step = ST_RESET; + break; + } + if (bits == MIN_BITS) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "Dooya"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_START; + } else if (DURATION_DIFF(te_last, TE_SHORT) < TE_DELTA && + DURATION_DIFF(dur, TE_LONG) < TE_DELTA * 2) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, TE_LONG) < TE_DELTA * 2 && + DURATION_DIFF(dur, TE_SHORT) < TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_dooya(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + if ((in.key >> 0) & 1ULL) { + out.push_back(-(TE_LONG * 12 + TE_LONG)); + } else { + out.push_back(-(TE_LONG * 12 + TE_SHORT)); + } + out.push_back(TE_SHORT * 13); + out.push_back(-(TE_LONG * 2)); + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(TE_LONG); + out.push_back(-TE_SHORT); + } else { + out.push_back(TE_SHORT); + out.push_back(-TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_dooya.h b/src/modules/rf/protocols/decoders/rf_decoder_dooya.h new file mode 100644 index 0000000000..5d619a8b41 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_dooya.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_dooya(const std::vector& durations, RfCodes& out); +bool rf_encode_dooya(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_elplast.cpp b/src/modules/rf/protocols/decoders/rf_decoder_elplast.cpp new file mode 100644 index 0000000000..d6e8d1be69 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_elplast.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_elplast.h" +#include "../rf_config.h" + +#define ELP_TE_SHORT 230 +#define ELP_TE_LONG 1550 +#define ELP_TE_DELTA 160 +#define ELP_MIN_BITS 18 +#define ELP_GAP (ELP_TE_LONG * 8) + +static inline unsigned int elp_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_elplast(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && elp_diff(dur, ELP_GAP) < ELP_TE_DELTA * 13) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (elp_diff(te_last, ELP_TE_LONG) < ELP_TE_DELTA && + elp_diff(dur, ELP_TE_SHORT) < ELP_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (elp_diff(te_last, ELP_TE_SHORT) < ELP_TE_DELTA && + elp_diff(dur, ELP_TE_LONG) < ELP_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (elp_diff(dur, ELP_GAP) < ELP_TE_DELTA * 13) { + if (elp_diff(te_last, ELP_TE_LONG) < ELP_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + } else if (elp_diff(te_last, ELP_TE_SHORT) < ELP_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + } + if (bits == ELP_MIN_BITS) { + out.key = data; + out.Bit = ELP_MIN_BITS; + out.te = ELP_TE_SHORT; + out.protocol = "Elplast"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_elplast(const RfCodes& in, std::vector& out) { + if (in.Bit != ELP_MIN_BITS) return false; + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(ELP_TE_LONG); + out.push_back(-ELP_TE_SHORT); + } else { + out.push_back(ELP_TE_SHORT); + out.push_back(-ELP_TE_LONG); + } + } + out.push_back(-ELP_GAP); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_elplast.h b/src/modules/rf/protocols/decoders/rf_decoder_elplast.h new file mode 100644 index 0000000000..489061b78d --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_elplast.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_elplast(const std::vector& durations, RfCodes& out); +bool rf_encode_elplast(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp b/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp index 8251d3bcc4..caea0c1288 100644 --- a/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp +++ b/src/modules/rf/protocols/decoders/rf_decoder_faac_slh.cpp @@ -12,6 +12,8 @@ static inline unsigned int faac_diff(unsigned int a, unsigned int b) { } bool rf_decode_faac_slh(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + enum { ST_RESET, ST_PREAMBLE, @@ -44,9 +46,10 @@ bool rf_decode_faac_slh(const std::vector& durations, RfCodes& out) { break; case ST_SAVE: + if (bits > 64) { step = ST_RESET; break; } if (level) { if (dur >= (unsigned int)(FAAC_TE_SHORT * 3 + FAAC_TE_DELTA)) { - if (bits >= FAAC_MIN_BITS) { + if (bits == FAAC_MIN_BITS) { out.key = data; out.Bit = FAAC_MIN_BITS; out.te = FAAC_TE_SHORT; @@ -86,7 +89,7 @@ bool rf_decode_faac_slh(const std::vector& durations, RfCodes& out) { } } - if (bits >= FAAC_MIN_BITS) { + if (bits == FAAC_MIN_BITS) { out.key = data; out.Bit = FAAC_MIN_BITS; out.te = FAAC_TE_SHORT; diff --git a/src/modules/rf/protocols/decoders/rf_decoder_feron.cpp b/src/modules/rf/protocols/decoders/rf_decoder_feron.cpp new file mode 100644 index 0000000000..db8d16c15c --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_feron.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_feron.h" +#include "../rf_config.h" + +#define FER_TE_SHORT 350 +#define FER_TE_LONG 750 +#define FER_TE_DELTA 150 +#define FER_MIN_BITS 32 + +static inline unsigned int fer_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_feron(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && fer_diff(dur, FER_TE_LONG * 6) < FER_TE_DELTA * 4) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (fer_diff(te_last, FER_TE_SHORT) < FER_TE_DELTA && + fer_diff(dur, FER_TE_LONG) < FER_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (fer_diff(te_last, FER_TE_LONG) < FER_TE_DELTA && + fer_diff(dur, FER_TE_SHORT) < FER_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (fer_diff(dur, FER_TE_SHORT + 150) < FER_TE_DELTA) { + if (fer_diff(te_last, FER_TE_SHORT) < FER_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + } else if (fer_diff(te_last, FER_TE_LONG) < FER_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + } + if (bits == FER_MIN_BITS) { + out.key = data; + out.Bit = FER_MIN_BITS; + out.te = FER_TE_SHORT; + out.protocol = "Feron"; + out.preset = "Ook270Async"; + out.serial = (uint32_t)(data >> 16); + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_feron(const RfCodes& in, std::vector& out) { + if (in.Bit != FER_MIN_BITS) return false; + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(FER_TE_LONG); + out.push_back(-(FER_TE_SHORT)); + } else { + out.push_back(FER_TE_SHORT); + out.push_back(-(FER_TE_LONG)); + } + } + out.push_back(FER_TE_SHORT + 150); + out.push_back(-(FER_TE_LONG * 6)); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_feron.h b/src/modules/rf/protocols/decoders/rf_decoder_feron.h new file mode 100644 index 0000000000..1208d05bb4 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_feron.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_feron(const std::vector& durations, RfCodes& out); +bool rf_encode_feron(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.cpp b/src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.cpp new file mode 100644 index 0000000000..447431c152 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.cpp @@ -0,0 +1,57 @@ +#include "rf_decoder_ford_tpms.h" +#include "rf_decoder_tpms.h" +#include "rf_decoder_tpms_helpers.h" +#include "../rf_config.h" + +#define FO_TE 52 +#define FO_TE_DELTA 18 +#define FO_SYNC "010101010101" "0110" + +bool rf_decode_ford_tpms(const std::vector& durations, RfCodes& out) { + if (durations.size() < 32) return false; + + auto bits = tpms_durations_to_bitmap(durations, FO_TE, FO_TE_DELTA); + if (bits.size() < 64) return false; + + uint32_t sync_len = 16; + uint32_t off = tpms_bitmap_seek_bits(bits, 0, (uint32_t)bits.size(), FO_SYNC); + if (off == TPMS_SEEK_NOT_FOUND) return false; + + off += sync_len; + + uint8_t raw[8]; + uint32_t decoded = tpms_convert_from_line_code( + raw, sizeof(raw), bits, off, (uint32_t)bits.size(), "01", "10"); + + if (decoded < 64) return false; + + uint8_t crc = 0; + for (int j = 0; j < 7; j++) crc += raw[j]; + if (crc != raw[7]) return false; + + float psi = 0.25f * (float)(((raw[6] & 0x20) << 3) | raw[4]); + int temp = (raw[5] & 0x80) ? 0 : raw[5] - 56; + int flags = raw[5] & 0x7f; + int car_moving = (raw[6] & 0x44) == 0x44; + + uint32_t id_val = ((uint32_t)raw[0] << 24) | ((uint32_t)raw[1] << 16) | + ((uint32_t)raw[2] << 8) | raw[3]; + + out.key = ((uint64_t)id_val << 32) | + ((uint32_t)((int)(psi * 10)) << 16) | + ((uint32_t)(flags & 0xFF) << 8) | + (uint32_t)(car_moving ? 1 : 0); + out.serial = id_val; + out.btn = (uint8_t)(flags & 0x7F); + out.Bit = 64; + out.te = FO_TE; + out.protocol = "Ford_TPMS"; + out.preset = "2FSKDev238Async"; + return true; +} + +bool rf_encode_ford_tpms(const RfCodes& in, std::vector& out) { + (void)in; + (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.h b/src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.h new file mode 100644 index 0000000000..bab73c9fe7 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_ford_tpms.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_ford_tpms(const std::vector& durations, RfCodes& out); +bool rf_encode_ford_tpms(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_gangqi.cpp b/src/modules/rf/protocols/decoders/rf_decoder_gangqi.cpp new file mode 100644 index 0000000000..4f0e2542ae --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_gangqi.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_gangqi.h" +#include "../rf_config.h" + +#define TE_SHORT 500 +#define TE_LONG 1200 +#define TE_DELTA 200 +#define MIN_BITS 34 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_gangqi(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_LONG * 2) < TE_DELTA * 5) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(dur, TE_LONG * 2) < TE_DELTA * 5) { + if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) + { data = (data << 1) | 0ULL; bits++; } + if (DURATION_DIFF(te_last, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA) + { data = (data << 1) | 1ULL; bits++; } + if (bits == MIN_BITS) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "GangQi"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_gangqi(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + out.push_back(-(TE_LONG * 2)); + for (int r = 0; r < 5; r++) { + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(TE_LONG); + out.push_back(i == 0 ? -(TE_SHORT * 4 + TE_DELTA) : -TE_SHORT); + } else { + out.push_back(TE_SHORT); + out.push_back(i == 0 ? -(TE_SHORT * 4 + TE_DELTA) : -TE_LONG); + } + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_gangqi.h b/src/modules/rf/protocols/decoders/rf_decoder_gangqi.h new file mode 100644 index 0000000000..ebd5bd64a6 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_gangqi.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_gangqi(const std::vector& durations, RfCodes& out); +bool rf_encode_gangqi(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_hay21.cpp b/src/modules/rf/protocols/decoders/rf_decoder_hay21.cpp new file mode 100644 index 0000000000..438fa8c778 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_hay21.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_hay21.h" +#include "../rf_config.h" + +#define TE_SHORT 300 +#define TE_LONG 700 +#define TE_DELTA 150 +#define MIN_BITS 21 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_hay21(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_LONG * 6) < TE_DELTA * 3) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (DURATION_DIFF(te_last, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(dur, TE_LONG * 6) < TE_DELTA * 2) { + if (DURATION_DIFF(te_last, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA) + { data = (data << 1) | 1ULL; bits++; } + if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) + { data = (data << 1) | 0ULL; bits++; } + if (bits == MIN_BITS) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "Hay21"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_hay21.h b/src/modules/rf/protocols/decoders/rf_decoder_hay21.h new file mode 100644 index 0000000000..af89248020 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_hay21.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_hay21(const std::vector& durations, RfCodes& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_hollarm.cpp b/src/modules/rf/protocols/decoders/rf_decoder_hollarm.cpp new file mode 100644 index 0000000000..7f6baafdbf --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_hollarm.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_hollarm.h" +#include "../rf_config.h" + +#define TE_SHORT 200 +#define TE_LONG 1000 +#define TE_DELTA 200 +#define MIN_BITS 42 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_hollarm(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_SHORT * 12) < TE_DELTA * 2) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, TE_SHORT * 8) < (unsigned int)TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(dur, TE_SHORT * 12) < TE_DELTA * 2) { + data = (data << 1) | 0ULL; + bits++; + if (bits == MIN_BITS) { + uint64_t k = data >> 2; + uint8_t sum = ((k >> 32) & 0xFF) + ((k >> 24) & 0xFF) + + ((k >> 16) & 0xFF) + ((k >> 8) & 0xFF); + if (sum == (k & 0xFF)) { + out.key = k; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "Hollarm"; + out.preset = "Ook270Async"; + return true; + } + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_hollarm(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + uint64_t k = in.key; + uint8_t sum = ((k >> 32) & 0xFF) + ((k >> 24) & 0xFF) + + ((k >> 16) & 0xFF) + ((k >> 8) & 0xFF); + k = (k & 0xFFFFFFFFFF00ULL) | sum; + for (int i = MIN_BITS - 1; i >= 0; i--) { + out.push_back(TE_SHORT); + if ((k >> i) & 1ULL) + out.push_back(i == 0 ? -(TE_SHORT * 12) : -(TE_SHORT * 8)); + else + out.push_back(i == 0 ? -(TE_SHORT * 12) : -(TE_LONG)); + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_hollarm.h b/src/modules/rf/protocols/decoders/rf_decoder_hollarm.h new file mode 100644 index 0000000000..7a61826a32 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_hollarm.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_hollarm(const std::vector& durations, RfCodes& out); +bool rf_encode_hollarm(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.cpp b/src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.cpp new file mode 100644 index 0000000000..2b50ba873a --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.cpp @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Port of Flipper Zero Honeywell Wireless Doorbell protocol decoder +// (GPL-3.0-or-later), lib/subghz/protocols/honeywell_wdb.c — PWM 48-bit +// protocol with parity LSB. te_short=160, te_long=320. +// https://github.com/klohner/honeywell-wireless-doorbell +#include "rf_decoder_honeywell_wdb.h" +#include "../rf_config.h" + +#define HW_TE_SHORT 160 +#define HW_TE_LONG 320 +#define HW_TE_DELTA 60 +#define HW_MIN_BITS 48 + +static inline unsigned int hw_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static unsigned int hw_parity(uint64_t data, int bits) { + unsigned int p = 0; + for (int i = 0; i < bits; i++) { + if ((data >> i) & 1ULL) p++; + } + return p & 1; +} + +bool rf_decode_honeywell_wdb(const std::vector& durations, RfCodes& out) { + if (durations.size() < 10) return false; + + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && hw_diff(dur, HW_TE_SHORT * 3) < HW_TE_DELTA) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + if (hw_diff(dur, HW_TE_SHORT * 3) < HW_TE_DELTA) { + if (bits == HW_MIN_BITS && (data & 1) == hw_parity(data >> 1, HW_MIN_BITS - 1)) { + out.key = data; + out.Bit = HW_MIN_BITS; + out.te = HW_TE_SHORT; + out.protocol = "Honeywell_WDB"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + break; + } + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if ((hw_diff(te_last, HW_TE_SHORT) < HW_TE_DELTA && + hw_diff(dur, HW_TE_LONG) < HW_TE_DELTA)) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if ((hw_diff(te_last, HW_TE_LONG) < HW_TE_DELTA && + hw_diff(dur, HW_TE_SHORT) < HW_TE_DELTA)) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_honeywell_wdb(const RfCodes& in, std::vector& out) { + out.clear(); + out.push_back(-(HW_TE_SHORT * 3)); + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(HW_TE_LONG); + out.push_back(-HW_TE_SHORT); + } else { + out.push_back(HW_TE_SHORT); + out.push_back(-HW_TE_LONG); + } + } + out.push_back(HW_TE_SHORT * 3); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.h b/src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.h new file mode 100644 index 0000000000..5b449123bc --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_honeywell_wdb.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_honeywell_wdb(const std::vector& durations, RfCodes& out); +bool rf_encode_honeywell_wdb(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_hormann.cpp b/src/modules/rf/protocols/decoders/rf_decoder_hormann.cpp new file mode 100644 index 0000000000..6157c02731 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_hormann.cpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_hormann.h" +#include "../rf_config.h" + +#define HORMANN_TE_SHORT 500 +#define HORMANN_TE_LONG 1000 +#define HORMANN_TE_DELTA 200 +#define HORMANN_MIN_BITS 44 +#define HORMANN_PATTERN 0xFF000000003ULL + +static inline unsigned int hormann_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_hormann(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_START_H, + ST_START_L, + ST_SAVE, + ST_CHECK + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && hormann_diff(dur, HORMANN_TE_SHORT * 24) < HORMANN_TE_DELTA * 24) + step = ST_START_H; + break; + + case ST_START_H: + if (!level && hormann_diff(dur, HORMANN_TE_SHORT) < HORMANN_TE_DELTA) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (bits > HORMANN_MIN_BITS) { step = ST_RESET; break; } + if (level) { + if (dur >= (unsigned int)(HORMANN_TE_SHORT * 5) && + (data & HORMANN_PATTERN) == HORMANN_PATTERN) { + if (bits >= HORMANN_MIN_BITS) { + out.key = data; + out.Bit = bits; + out.te = HORMANN_TE_SHORT; + out.protocol = "Hormann_HSM"; + out.preset = "Ook270Async"; + out.btn = (data >> 8) & 0xF; + return true; + } + step = ST_START_L; + break; + } + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_START_L: + if (!level && hormann_diff(dur, HORMANN_TE_SHORT) < HORMANN_TE_DELTA) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (hormann_diff(te_last, HORMANN_TE_SHORT) < HORMANN_TE_DELTA && + hormann_diff(dur, HORMANN_TE_LONG) < HORMANN_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (hormann_diff(te_last, HORMANN_TE_LONG) < HORMANN_TE_DELTA && + hormann_diff(dur, HORMANN_TE_SHORT) < HORMANN_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_hormann(const RfCodes& in, std::vector& out) { + for (int r = 0; r < 20; r++) { + out.push_back(HORMANN_TE_SHORT * 24); + out.push_back(-HORMANN_TE_SHORT); + + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(HORMANN_TE_LONG); + out.push_back(-HORMANN_TE_SHORT); + } else { + out.push_back(HORMANN_TE_SHORT); + out.push_back(-HORMANN_TE_LONG); + } + } + } + out.push_back(HORMANN_TE_SHORT * 24); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_hormann.h b/src/modules/rf/protocols/decoders/rf_decoder_hormann.h new file mode 100644 index 0000000000..30a23c8d93 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_hormann.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_hormann(const std::vector& durations, RfCodes& out); +bool rf_encode_hormann(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_ido.cpp b/src/modules/rf/protocols/decoders/rf_decoder_ido.cpp new file mode 100644 index 0000000000..7ee12f5745 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_ido.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_ido.h" +#include "../rf_config.h" + +#define IDO_TE_SHORT 450 +#define IDO_TE_LONG 1450 +#define IDO_TE_DELTA 150 +#define IDO_MIN_BITS 48 + +static inline unsigned int ido_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static uint64_t reverse_key(uint64_t data, int bits) { + uint64_t rev = 0; + for (int i = 0; i < bits; i++) { + rev = (rev << 1) | ((data >> i) & 1ULL); + } + return rev; +} + +bool rf_decode_ido(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_PREAMBLE_H, + ST_SAVE, + ST_CHECK + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && ido_diff(dur, IDO_TE_SHORT * 10) < IDO_TE_DELTA * 5) + step = ST_PREAMBLE_H; + break; + + case ST_PREAMBLE_H: + if (!level && ido_diff(dur, IDO_TE_SHORT * 10) < IDO_TE_DELTA * 5) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (bits > IDO_MIN_BITS) { step = ST_RESET; break; } + if (level) { + if (dur >= (unsigned int)(IDO_TE_SHORT * 5 + IDO_TE_DELTA)) { + if (bits >= IDO_MIN_BITS) { + uint64_t rev = reverse_key(data, bits); + uint32_t fix = (uint32_t)(rev & 0xFFFFFF); + out.key = data; + out.Bit = bits; + out.te = IDO_TE_SHORT; + out.protocol = "IDO"; + out.preset = "Ook270Async"; + out.serial = fix & 0xFFFFF; + out.btn = (fix >> 20) & 0x0F; + return true; + } + step = ST_RESET; + } else { + te_last = dur; + step = ST_CHECK; + } + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (ido_diff(te_last, IDO_TE_SHORT) < IDO_TE_DELTA && + ido_diff(dur, IDO_TE_LONG) < IDO_TE_DELTA * 3) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (ido_diff(te_last, IDO_TE_SHORT) < IDO_TE_DELTA * 3 && + ido_diff(dur, IDO_TE_SHORT) < IDO_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_ido(const RfCodes& in, std::vector& out) { + out.push_back(IDO_TE_SHORT * 10); + out.push_back(-(IDO_TE_SHORT * 10)); + + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(IDO_TE_SHORT); + out.push_back(-IDO_TE_SHORT); + } else { + out.push_back(IDO_TE_SHORT); + out.push_back(-IDO_TE_LONG); + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_ido.h b/src/modules/rf/protocols/decoders/rf_decoder_ido.h new file mode 100644 index 0000000000..77184d2630 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_ido.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_ido(const std::vector& durations, RfCodes& out); +bool rf_encode_ido(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp b/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp index 50d3a3e46e..2f0194b963 100644 --- a/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp +++ b/src/modules/rf/protocols/decoders/rf_decoder_intertechno_v3.cpp @@ -17,9 +17,11 @@ static inline unsigned int itv3_diff(unsigned int a, unsigned int b) { } bool rf_decode_intertechno_v3(const std::vector& durations, RfCodes& out) { + if (durations.size() < 8) return false; + enum { ST_RESET, - ST_HEADER, ST_SYNC_H, ST_SYNC_L, + ST_HEADER, ST_SYNC_H, ST_SAVE, ST_CHECK, ST_END } step = ST_RESET; @@ -55,6 +57,7 @@ bool rf_decode_intertechno_v3(const std::vector& durations, RfCodes& out) { break; case ST_SAVE: + if (bits > ITV3_DIMMING_BITS) { step = ST_RESET; break; } if (!level) { if (dur >= ITV3_TE_SHORT * 11) { if (bits == ITV3_MIN_BITS || bits == ITV3_DIMMING_BITS) { @@ -103,7 +106,8 @@ bool rf_decode_intertechno_v3(const std::vector& durations, RfCodes& out) { if (!level) { if (itv3_diff(dur, ITV3_TE_SHORT) < ITV3_TE_DELTA || itv3_diff(dur, ITV3_TE_LONG) < ITV3_TE_DELTA * 2) { - step = ST_SAVE; + if (bits <= ITV3_DIMMING_BITS) step = ST_SAVE; + else step = ST_RESET; } else if (dur >= ITV3_TE_SHORT * 11) { if (bits == ITV3_MIN_BITS || bits == ITV3_DIMMING_BITS) { out.key = data; diff --git a/src/modules/rf/protocols/decoders/rf_decoder_legrand.cpp b/src/modules/rf/protocols/decoders/rf_decoder_legrand.cpp new file mode 100644 index 0000000000..0589661afb --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_legrand.cpp @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_legrand.h" +#include "../rf_config.h" + +#define TE_SHORT 375 +#define TE_LONG 1125 +#define TE_DELTA 150 +#define MIN_BITS 18 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_legrand(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_FIRST, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + int te_sum = 0; + uint64_t last_data = 0; + unsigned int te_last_low = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, (unsigned int)(TE_SHORT * 16)) < TE_DELTA * 8) { + data = 0; bits = 0; te_sum = 0; te_last_low = 0; + step = ST_FIRST; + } + break; + + case ST_FIRST: + if (level) { + if (DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + te_sum += dur * 4; + } + if (DURATION_DIFF(dur, (unsigned int)TE_LONG) < TE_DELTA * 3) { + data = (data << 1) | 1ULL; + bits++; + te_sum += (dur / 3) * 4; + } + if (bits > 0) { + step = ST_SAVE; + break; + } + } + step = ST_RESET; + break; + + case ST_SAVE: + if (!level) { + te_last_low = dur; + te_sum += dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (level) { + int found = 0; + if (DURATION_DIFF(te_last_low, (unsigned int)TE_LONG) < TE_DELTA * 3 && + DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + found = 1; + data = (data << 1) | 0ULL; + bits++; + } + if (DURATION_DIFF(te_last_low, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, (unsigned int)TE_LONG) < TE_DELTA * 3) { + found = 1; + data = (data << 1) | 1ULL; + bits++; + } + if (found) { + te_sum += dur; + if (bits >= MIN_BITS && last_data && last_data == data) { + te_sum /= bits * 4; + out.key = data; + out.Bit = MIN_BITS; + out.te = te_sum; + out.protocol = "Legrand"; + out.preset = "Ook270Async"; + return true; + } + last_data = data; + step = ST_SAVE; + break; + } + } + step = ST_RESET; + break; + } + } + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_legrand.h b/src/modules/rf/protocols/decoders/rf_decoder_legrand.h new file mode 100644 index 0000000000..6ea82ba73c --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_legrand.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_legrand(const std::vector& durations, RfCodes& out); +bool rf_encode_legrand(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.cpp b/src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.cpp new file mode 100644 index 0000000000..3bb71baa40 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.cpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Port of Flipper Zero Linear Delta-3 protocol decoder (GPL-3.0-or-later), +// lib/subghz/protocols/linear_delta3.c — Simple PWM 8-bit DIP switch protocol. +// te_short=500, bit1=short+7*short, bit0=long+long. +#include "rf_decoder_linear_delta3.h" +#include "../rf_config.h" + +#define LD_TE_SHORT 500 +#define LD_TE_LONG 2000 +#define LD_TE_DELTA 150 +#define LD_MIN_BITS 8 + +static inline unsigned int ld_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_linear_delta3(const std::vector& durations, RfCodes& out) { + if (durations.size() < 6) return false; + + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + uint64_t last_data = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && ld_diff(dur, LD_TE_SHORT * 70) < LD_TE_DELTA * 24) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (dur >= (unsigned int)(LD_TE_SHORT * 10)) { + if (ld_diff(te_last, LD_TE_SHORT) < LD_TE_DELTA) + data = (data << 1) | 1ULL; + else if (ld_diff(te_last, LD_TE_LONG) < LD_TE_DELTA) + data = (data << 1) | 0ULL; + else { step = ST_RESET; break; } + bits++; + + if (bits == LD_MIN_BITS) { + if (last_data == data && last_data) { + out.key = data; + out.Bit = LD_MIN_BITS; + out.te = LD_TE_SHORT; + out.protocol = "Linear_Delta3"; + out.preset = "Ook270Async"; + return true; + } + last_data = data; + data = 0; bits = 0; + } + step = ST_SAVE; + break; + } + + if ((ld_diff(te_last, LD_TE_SHORT) < LD_TE_DELTA && + ld_diff(dur, LD_TE_SHORT * 7) < LD_TE_DELTA)) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if ((ld_diff(te_last, LD_TE_LONG) < LD_TE_DELTA && + ld_diff(dur, LD_TE_LONG) < LD_TE_DELTA)) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_linear_delta3(const RfCodes& in, std::vector& out) { + out.clear(); + out.push_back(-(LD_TE_SHORT * 70)); + for (int i = in.Bit - 1; i > 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(LD_TE_SHORT); + out.push_back(-(LD_TE_SHORT * 7)); + } else { + out.push_back(LD_TE_LONG); + out.push_back(-LD_TE_LONG); + } + } + if ((in.key >> 0) & 1ULL) { + out.push_back(LD_TE_SHORT); + out.push_back(-(LD_TE_SHORT * 73)); + } else { + out.push_back(LD_TE_LONG); + out.push_back(-(LD_TE_SHORT * 70)); + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.h b/src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.h new file mode 100644 index 0000000000..26832da619 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_linear_delta3.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_linear_delta3(const std::vector& durations, RfCodes& out); +bool rf_encode_linear_delta3(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_magellan.cpp b/src/modules/rf/protocols/decoders/rf_decoder_magellan.cpp new file mode 100644 index 0000000000..a890688b4c --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_magellan.cpp @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_magellan.h" +#include "../rf_config.h" + +#define TE_SHORT 200 +#define TE_LONG 400 +#define TE_DELTA 100 +#define MIN_BITS 32 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +static uint8_t magellan_crc8(uint8_t* data, size_t len) { + uint8_t crc = 0x00; + for (size_t i = 0; i < len; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + if (crc & 0x80) + crc = (uint8_t)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; +} + +bool rf_decode_magellan(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_HEADER, ST_PREAMBLE, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + int header_count = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (level && DURATION_DIFF(dur, TE_SHORT) < TE_DELTA) { + te_last = dur; + header_count = 0; + step = ST_HEADER; + } + break; + + case ST_HEADER: + if (level) { + te_last = dur; + } else { + if (DURATION_DIFF(te_last, TE_SHORT) < TE_DELTA && + DURATION_DIFF(dur, TE_SHORT) < TE_DELTA) { + header_count++; + } else if (DURATION_DIFF(te_last, TE_SHORT) < TE_DELTA && + DURATION_DIFF(dur, TE_LONG) < TE_DELTA * 2 && + header_count > 10) { + step = ST_PREAMBLE; + } else { + step = ST_RESET; + } + } + break; + + case ST_PREAMBLE: + if (level) { + te_last = dur; + } else { + if (DURATION_DIFF(te_last, TE_SHORT * 6) < TE_DELTA * 3 && + DURATION_DIFF(dur, TE_LONG) < TE_DELTA * 2) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (DURATION_DIFF(te_last, TE_SHORT) < TE_DELTA && + DURATION_DIFF(dur, TE_LONG) < TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, TE_LONG) < TE_DELTA && + DURATION_DIFF(dur, TE_SHORT) < TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (dur >= (unsigned int)(TE_LONG * 3)) { + if (bits == MIN_BITS) { + uint8_t cdata[3] = { + (uint8_t)(data >> 24), + (uint8_t)(data >> 16), + (uint8_t)(data >> 8)}; + if ((data & 0xFF) == magellan_crc8(cdata, 3)) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "Magellan"; + out.preset = "Ook270Async"; + return true; + } + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_magellan(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + out.push_back(TE_SHORT * 4); + out.push_back(-TE_SHORT); + for (int i = 0; i < 12; i++) { + out.push_back(TE_SHORT); + out.push_back(-TE_SHORT); + } + out.push_back(TE_SHORT); + out.push_back(-TE_LONG); + out.push_back(TE_LONG * 3); + out.push_back(-TE_LONG); + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(TE_SHORT); + out.push_back(-TE_LONG); + } else { + out.push_back(TE_LONG); + out.push_back(-TE_SHORT); + } + } + out.push_back(TE_SHORT); + out.push_back(-(TE_LONG * 100)); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_magellan.h b/src/modules/rf/protocols/decoders/rf_decoder_magellan.h new file mode 100644 index 0000000000..f0d4f5ff9f --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_magellan.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_magellan(const std::vector& durations, RfCodes& out); +bool rf_encode_magellan(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_marantec.cpp b/src/modules/rf/protocols/decoders/rf_decoder_marantec.cpp new file mode 100644 index 0000000000..8e41930753 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_marantec.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Port of Flipper Zero Marantec protocol decoder (GPL-3.0-or-later), +// lib/subghz/protocols/marantec.c — Manchester-encoded 49-bit garage door +// protocol with CRC-8 (poly 0x1D). +#include "rf_decoder_marantec.h" +#include "manchester_helpers.h" +#include "../rf_config.h" + +#define MT_TE_SHORT 1000 +#define MT_TE_LONG 2000 +#define MT_TE_DELTA 200 +#define MT_MIN_BITS 49 + +static inline unsigned int mt_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static uint8_t mt_crc8(const uint8_t* data, size_t len) { + uint8_t crc = 0x01; + for (size_t i = 0; i < len; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + if (crc & 0x80) + crc = (uint8_t)((crc << 1) ^ 0x1D); + else + crc <<= 1; + } + } + return crc; +} + +bool rf_decode_marantec(const std::vector& durations, RfCodes& out) { + if (durations.size() < 20) return false; + + for (size_t start = 0; start + 1 < durations.size(); start++) { + int raw = durations[start]; + if (raw > 0) continue; + unsigned int dur = (unsigned int)(-raw); + if (mt_diff(dur, MT_TE_LONG * 5) >= MT_TE_DELTA * 8) continue; + + ManchesterState ms; + manchester_reset(ms); + uint64_t data = 1; + int bits = 1; + + for (size_t i = start + 1; i < durations.size(); i++) { + bool level = durations[i] > 0; + unsigned int d = (unsigned int)(durations[i] > 0 ? durations[i] : -durations[i]); + + if (!level && d >= (unsigned int)(MT_TE_LONG * 2 + MT_TE_DELTA)) { + if (bits == MT_MIN_BITS) { + uint8_t tdata[6] = { + (uint8_t)(data >> 48), (uint8_t)(data >> 40), + (uint8_t)(data >> 32), (uint8_t)(data >> 24), + (uint8_t)(data >> 16), (uint8_t)(data >> 8)}; + if (mt_crc8(tdata, 6) == (data & 0xFF)) { + out.key = data; + out.Bit = MT_MIN_BITS; + out.te = MT_TE_SHORT; + out.protocol = "Marantec"; + out.preset = "Ook270Async"; + return true; + } + } + break; + } + + ManchesterEvent ev; + if (level) { + if (mt_diff(d, MT_TE_SHORT) < MT_TE_DELTA) ev = ManchesterEventShortHigh; + else if (mt_diff(d, MT_TE_LONG) < MT_TE_DELTA) ev = ManchesterEventLongHigh; + else { manchester_reset(ms); continue; } + } else { + if (mt_diff(d, MT_TE_SHORT) < MT_TE_DELTA) ev = ManchesterEventShortLow; + else if (mt_diff(d, MT_TE_LONG) < MT_TE_DELTA) ev = ManchesterEventLongLow; + else { manchester_reset(ms); continue; } + } + + bool bit_val = false; + if (manchester_advance(ms, ev, &bit_val)) { + if (bits >= MT_MIN_BITS) break; + data = (data << 1) | (bit_val ? 1ULL : 0ULL); + bits++; + } + } + } + return false; +} + +bool rf_encode_marantec(const RfCodes& in, std::vector& out) { + out.clear(); + out.push_back(-(MT_TE_LONG * 5)); + ManchesterState ms; + manchester_reset(ms); + bool bit_val = false; + if (manchester_advance(ms, ManchesterEventShortHigh, &bit_val)) { + out.push_back(MT_TE_SHORT); + out.push_back(-MT_TE_LONG); + } else { + out.push_back(MT_TE_SHORT); + } + for (int i = in.Bit - 2; i >= 0; i--) { + bool bit = (in.key >> (i + 1)) & 1ULL; + if (manchester_advance(ms, bit ? ManchesterEventLongHigh : ManchesterEventShortHigh, &bit_val)) { + if (bit_val) { out.push_back(MT_TE_LONG); out.push_back(-MT_TE_SHORT); } + else { out.push_back(MT_TE_SHORT); out.push_back(-MT_TE_LONG); } + } else { + if (bit) { out.push_back(MT_TE_LONG); } + else { out.push_back(MT_TE_SHORT); } + } + } + out.push_back(-(MT_TE_LONG * 5)); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_marantec.h b/src/modules/rf/protocols/decoders/rf_decoder_marantec.h new file mode 100644 index 0000000000..5e7598c286 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_marantec.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_marantec(const std::vector& durations, RfCodes& out); +bool rf_encode_marantec(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_marantec24.cpp b/src/modules/rf/protocols/decoders/rf_decoder_marantec24.cpp new file mode 100644 index 0000000000..2f426ee8c6 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_marantec24.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_marantec24.h" +#include "../rf_config.h" + +#define TE_SHORT 800 +#define TE_LONG 1600 +#define TE_DELTA 200 +#define MIN_BITS 24 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_marantec24(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_LONG * 9) < TE_DELTA * 6) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (DURATION_DIFF(te_last, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, TE_SHORT * 3) < (unsigned int)TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA && + DURATION_DIFF(dur, TE_LONG * 2) < (unsigned int)TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (DURATION_DIFF(dur, TE_LONG * 9) < TE_DELTA * 6) { + if (DURATION_DIFF(te_last, (unsigned int)TE_LONG) < (unsigned int)TE_DELTA) + { data = (data << 1) | 0ULL; bits++; } + if (DURATION_DIFF(te_last, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) + { data = (data << 1) | 1ULL; bits++; } + if (bits == MIN_BITS) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "Marantec24"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_marantec24(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + out.push_back(-(TE_LONG * 9)); + for (int r = 0; r < 4; r++) { + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(TE_SHORT); + out.push_back(i == 0 ? -(TE_LONG * 9 + TE_SHORT) : -(TE_LONG * 2)); + } else { + out.push_back(TE_LONG); + out.push_back(i == 0 ? -(TE_LONG * 9 + TE_SHORT) : -(TE_SHORT * 3)); + } + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_marantec24.h b/src/modules/rf/protocols/decoders/rf_decoder_marantec24.h new file mode 100644 index 0000000000..4e8ef54dd8 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_marantec24.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_marantec24(const std::vector& durations, RfCodes& out); +bool rf_encode_marantec24(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_megacode.cpp b/src/modules/rf/protocols/decoders/rf_decoder_megacode.cpp new file mode 100644 index 0000000000..d78cc97e2d --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_megacode.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_megacode.h" +#include "../rf_config.h" + +#define TE_SHORT 1000 +#define TE_DELTA 200 +#define MIN_BITS 24 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_megacode(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_START, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + int last_bit = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, TE_SHORT * 13) < TE_DELTA * 17) { + step = ST_START; + } + break; + + case ST_START: + if (level && DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + data = 0; bits = 0; + data = (data << 1) | 1ULL; + bits++; + last_bit = 1; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (!level) { + if (dur >= (unsigned int)(TE_SHORT * 10)) { + if (bits == MIN_BITS) { + out.key = data; + out.Bit = MIN_BITS; + out.te = TE_SHORT; + out.protocol = "MegaCode"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + } else { + if (!last_bit) + te_last = dur - TE_SHORT * 3; + else + te_last = dur; + step = ST_CHECK; + } + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (level) { + if (DURATION_DIFF((int)te_last, TE_SHORT * 5) < TE_DELTA * 5 && + DURATION_DIFF((int)dur, (int)TE_SHORT) < TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + last_bit = 1; + step = ST_SAVE; + } else if (DURATION_DIFF((int)te_last, TE_SHORT * 2) < TE_DELTA * 2 && + DURATION_DIFF((int)dur, (int)TE_SHORT) < TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + last_bit = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_megacode(const RfCodes& in, std::vector& out) { + if (in.Bit != MIN_BITS) return false; + int last_bit = (in.key >> 0) & 1ULL; + out.resize(in.Bit * 2); + int idx = in.Bit * 2 - 1; + out[idx--] = TE_SHORT; + for (int i = 1; i < in.Bit; i++) { + if ((in.key >> i) & 1ULL) { + out[idx--] = -(last_bit ? TE_SHORT * 5 : TE_SHORT * 2); + last_bit = 1; + } else { + out[idx--] = -(last_bit ? TE_SHORT * 8 : TE_SHORT * 5); + last_bit = 0; + } + out[idx--] = TE_SHORT; + } + out.insert(out.begin(), last_bit ? -(TE_SHORT * 11) : -(TE_SHORT * 14)); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_megacode.h b/src/modules/rf/protocols/decoders/rf_decoder_megacode.h new file mode 100644 index 0000000000..341a8bf220 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_megacode.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_megacode(const std::vector& durations, RfCodes& out); +bool rf_encode_megacode(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_nero_radio.cpp b/src/modules/rf/protocols/decoders/rf_decoder_nero_radio.cpp new file mode 100644 index 0000000000..53b5aef446 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_nero_radio.cpp @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_nero_radio.h" +#include "../rf_config.h" + +#define NR_TE_SHORT 200 +#define NR_TE_LONG 400 +#define NR_TE_DELTA 80 +#define NR_MIN_BITS 56 +#define NR_HEADER_COUNT 49 + +static inline unsigned int nr_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_nero_radio(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_PREAMBLE, + ST_SAVE, + ST_CHECK + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + int header_cnt = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && nr_diff(dur, NR_TE_SHORT) < NR_TE_DELTA) { + te_last = dur; + header_cnt = 0; + step = ST_PREAMBLE; + } + break; + + case ST_PREAMBLE: + if (level) { + if (nr_diff(dur, NR_TE_SHORT) < NR_TE_DELTA || + nr_diff(dur, NR_TE_SHORT * 4) < NR_TE_DELTA) { + te_last = dur; + } else { + step = ST_RESET; + } + } else if (nr_diff(dur, NR_TE_SHORT) < NR_TE_DELTA) { + if (nr_diff(te_last, NR_TE_SHORT) < NR_TE_DELTA) { + header_cnt++; + } else if (nr_diff(te_last, NR_TE_SHORT * 4) < NR_TE_DELTA) { + if (header_cnt > 40) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (bits > NR_MIN_BITS) { step = ST_RESET; break; } + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (dur >= (unsigned int)(NR_TE_SHORT * 10 + NR_TE_DELTA * 2)) { + if (nr_diff(te_last, NR_TE_SHORT) < NR_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + } else if (nr_diff(te_last, NR_TE_LONG) < NR_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + } + if (bits == NR_MIN_BITS) { + out.key = data; + out.Bit = NR_MIN_BITS; + out.te = NR_TE_SHORT; + out.protocol = "Nero_Radio"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else if (nr_diff(te_last, NR_TE_SHORT) < NR_TE_DELTA && + nr_diff(dur, NR_TE_LONG) < NR_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (nr_diff(te_last, NR_TE_LONG) < NR_TE_DELTA && + nr_diff(dur, NR_TE_SHORT) < NR_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_nero_radio(const RfCodes& in, std::vector& out) { + for (int i = 0; i < NR_HEADER_COUNT; i++) { + out.push_back(NR_TE_SHORT); + out.push_back(-NR_TE_SHORT); + } + out.push_back(NR_TE_SHORT * 4); + out.push_back(-NR_TE_SHORT); + + for (int i = in.Bit - 1; i > 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(NR_TE_LONG); + out.push_back(-NR_TE_SHORT); + } else { + out.push_back(NR_TE_SHORT); + out.push_back(-NR_TE_LONG); + } + } + if ((in.key >> 0) & 1ULL) { + out.push_back(NR_TE_LONG); + out.push_back(-(NR_TE_SHORT * 37)); + } else { + out.push_back(NR_TE_SHORT); + out.push_back(-(NR_TE_SHORT * 37)); + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_nero_radio.h b/src/modules/rf/protocols/decoders/rf_decoder_nero_radio.h new file mode 100644 index 0000000000..e326ee7989 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_nero_radio.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_nero_radio(const std::vector& durations, RfCodes& out); +bool rf_encode_nero_radio(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.cpp b/src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.cpp new file mode 100644 index 0000000000..7756ffe9f6 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_nero_sketch.h" +#include "../rf_config.h" + +#define NS_TE_SHORT 330 +#define NS_TE_LONG 660 +#define NS_TE_DELTA 150 +#define NS_MIN_BITS 40 +#define NS_HEADER_COUNT 47 + +static inline unsigned int ns_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_nero_sketch(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_PREAMBLE, + ST_SAVE, + ST_CHECK + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + int header_cnt = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && ns_diff(dur, NS_TE_SHORT) < NS_TE_DELTA) { + te_last = dur; + header_cnt = 0; + step = ST_PREAMBLE; + } + break; + + case ST_PREAMBLE: + if (level) { + if (ns_diff(dur, NS_TE_SHORT) < NS_TE_DELTA || + ns_diff(dur, NS_TE_SHORT * 4) < NS_TE_DELTA) { + te_last = dur; + } else { + step = ST_RESET; + } + } else if (ns_diff(dur, NS_TE_SHORT) < NS_TE_DELTA) { + if (ns_diff(te_last, NS_TE_SHORT) < NS_TE_DELTA) { + header_cnt++; + } else if (ns_diff(te_last, NS_TE_SHORT * 4) < NS_TE_DELTA) { + if (header_cnt > 40) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (bits > NS_MIN_BITS) { step = ST_RESET; break; } + if (level) { + if (dur >= (unsigned int)(NS_TE_SHORT * 2 + NS_TE_DELTA * 2)) { + if (bits == NS_MIN_BITS) { + out.key = data; + out.Bit = NS_MIN_BITS; + out.te = NS_TE_SHORT; + out.protocol = "Nero_Sketch"; + out.preset = "Ook270Async"; + return true; + } + step = ST_RESET; + } else { + te_last = dur; + step = ST_CHECK; + } + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (ns_diff(te_last, NS_TE_SHORT) < NS_TE_DELTA && + ns_diff(dur, NS_TE_LONG) < NS_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (ns_diff(te_last, NS_TE_LONG) < NS_TE_DELTA && + ns_diff(dur, NS_TE_SHORT) < NS_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_nero_sketch(const RfCodes& in, std::vector& out) { + for (int i = 0; i < NS_HEADER_COUNT; i++) { + out.push_back(NS_TE_SHORT); + out.push_back(-NS_TE_SHORT); + } + out.push_back(NS_TE_SHORT * 4); + out.push_back(-NS_TE_SHORT); + + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(NS_TE_LONG); + out.push_back(-NS_TE_SHORT); + } else { + out.push_back(NS_TE_SHORT); + out.push_back(-NS_TE_LONG); + } + } + + out.push_back(NS_TE_SHORT * 3); + out.push_back(-NS_TE_SHORT); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.h b/src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.h new file mode 100644 index 0000000000..c3e4e8485d --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_nero_sketch.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_nero_sketch(const std::vector& durations, RfCodes& out); +bool rf_encode_nero_sketch(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.cpp b/src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.cpp new file mode 100644 index 0000000000..c5d7d2c1bc --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_nice_flor_s.h" +#include "../rf_config.h" + +#define NFS_TE_SHORT 500 +#define NFS_TE_LONG 1000 +#define NFS_TE_DELTA 300 +#define NFS_MIN_BITS 52 + +static inline unsigned int nfs_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static void nfs_magic_xor(uint8_t* p, uint8_t k) { + for (uint8_t i = 1; i < 6; i++) p[i] ^= k; +} + +static uint64_t nfs_decrypt(uint64_t data) { + uint8_t* p = (uint8_t*)&data; + uint8_t k = 0; + + k = ~p[4]; p[5] = ~p[5]; p[4] = ~p[2]; p[2] = ~p[0]; p[0] = k; + k = ~p[3]; p[3] = ~p[1]; p[1] = k; + + // Simplified decryption without rainbow table - uses XOR with fixed constants + // Full decryption requires external rainbow table file + for (uint8_t y = 0; y < 2; y++) { + k = 0x25; + nfs_magic_xor(p, k); + p[5] &= 0x0f; + p[0] ^= k & 0x7; + k = 0x55; + nfs_magic_xor(p, k); + p[5] &= 0x0f; + p[0] ^= k & 0xe0; + if (y == 0) { k = p[0]; p[0] = p[1]; p[1] = k; } + } + + return data; +} + +bool rf_decode_nice_flor_s(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_CHECK_HEADER, + ST_FOUND_HEADER, + ST_SAVE, + ST_CHECK + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + uint64_t saved_data = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && nfs_diff(dur, NFS_TE_SHORT * 38) < NFS_TE_DELTA * 38) + step = ST_CHECK_HEADER; + break; + + case ST_CHECK_HEADER: + if (level && nfs_diff(dur, NFS_TE_SHORT * 3) < NFS_TE_DELTA * 3) + step = ST_FOUND_HEADER; + else + step = ST_RESET; + break; + + case ST_FOUND_HEADER: + if (!level && nfs_diff(dur, NFS_TE_SHORT * 3) < NFS_TE_DELTA * 3) { + data = 0; bits = 0; + step = ST_SAVE; + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (bits > NFS_MIN_BITS) { step = ST_RESET; break; } + if (level) { + if (nfs_diff(dur, NFS_TE_SHORT * 3) < NFS_TE_DELTA) { + step = ST_RESET; + if (bits == NFS_MIN_BITS) { + uint64_t dec = nfs_decrypt(data); + out.key = data; + out.Bit = NFS_MIN_BITS; + out.te = NFS_TE_SHORT; + out.protocol = "Nice_Flor_S"; + out.preset = "Ook270Async"; + out.cnt = (uint32_t)(dec & 0xFFFF); + out.serial = (uint32_t)((dec >> 16) & 0xFFFFFFF); + out.btn = (uint8_t)((dec >> 48) & 0xF); + return true; + } + break; + } + te_last = dur; + step = ST_CHECK; + } + break; + + case ST_CHECK: + if (!level) { + if (nfs_diff(te_last, NFS_TE_SHORT) < NFS_TE_DELTA && + nfs_diff(dur, NFS_TE_LONG) < NFS_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (nfs_diff(te_last, NFS_TE_LONG) < NFS_TE_DELTA && + nfs_diff(dur, NFS_TE_SHORT) < NFS_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_nice_flor_s(const RfCodes& in, std::vector& out) { + (void)in; (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.h b/src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.h new file mode 100644 index 0000000000..0c3326228b --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_nice_flor_s.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_nice_flor_s(const std::vector& durations, RfCodes& out); +bool rf_encode_nice_flor_s(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp b/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp index a8e24054c4..1fcfc682b3 100644 --- a/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp +++ b/src/modules/rf/protocols/decoders/rf_decoder_power_smart.cpp @@ -24,6 +24,8 @@ static bool ps_check_valid(uint64_t packet) { } bool rf_decode_power_smart(const std::vector& durations, RfCodes& out) { + if (durations.size() < 16) return false; + ManchesterState ms; manchester_reset(ms); @@ -47,7 +49,10 @@ bool rf_decode_power_smart(const std::vector& durations, RfCodes& out) { bits = 0; } - if (bits >= PS_MIN_BITS) { + if (bits >= 128) { + data = 0; + bits = 0; + } else if (bits >= PS_MIN_BITS) { if ((data & PS_HEADER_MASK) == PS_HEADER && ps_check_valid(data)) { out.key = data; out.Bit = PS_MIN_BITS; diff --git a/src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.cpp b/src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.cpp new file mode 100644 index 0000000000..14c50f4996 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.cpp @@ -0,0 +1,50 @@ +#include "rf_decoder_renault_tpms.h" +#include "rf_decoder_tpms.h" +#include "rf_decoder_tpms_helpers.h" +#include "../rf_config.h" + +#define RE_TE 48 +#define RE_TE_DELTA 16 +#define RE_SYNC "01010101010101010110" + +bool rf_decode_renault_tpms(const std::vector& durations, RfCodes& out) { + if (durations.size() < 32) return false; + + auto bits = tpms_durations_to_bitmap(durations, RE_TE, RE_TE_DELTA); + if (bits.size() < 80) return false; + + uint32_t off = tpms_bitmap_seek_bits(bits, 0, (uint32_t)bits.size(), RE_SYNC); + if (off == TPMS_SEEK_NOT_FOUND) return false; + + off += 20; + + uint8_t raw[9]; + uint32_t decoded = tpms_convert_from_line_code( + raw, sizeof(raw), bits, off, (uint32_t)bits.size(), "01", "10"); + + if (decoded < 72) return false; + if (tpms_crc8(raw, 8, 0, 7) != raw[8]) return false; + + uint8_t flags = raw[0] >> 2; + float kpa = 0.75f * (float)(((uint32_t)(raw[0] & 3) << 8) | raw[1]); + int temp = raw[2] - 30; + + uint32_t id_val = ((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | raw[5]; + + out.key = ((uint64_t)id_val << 32) | + ((uint32_t)((int)(kpa * 10)) << 16) | + ((uint32_t)(temp + 100) & 0xFFFF); + out.serial = id_val; + out.btn = flags; + out.Bit = 72; + out.te = RE_TE; + out.protocol = "Renault_TPMS"; + out.preset = "2FSKDev238Async"; + return true; +} + +bool rf_encode_renault_tpms(const RfCodes& in, std::vector& out) { + (void)in; + (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.h b/src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.h new file mode 100644 index 0000000000..71c96bcdf6 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_renault_tpms.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_renault_tpms(const std::vector& durations, RfCodes& out); +bool rf_encode_renault_tpms(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.cpp b/src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.cpp new file mode 100644 index 0000000000..084da502a0 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.cpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_revers_rb2.h" +#include "../rf_config.h" +#include "manchester_helpers.h" + +#define TE_SHORT 250 +#define TE_LONG 500 +#define TE_DELTA 160 +#define MIN_BITS 64 + +static inline int DURATION_DIFF(int a, int b) { return (a > b) ? (a - b) : (b - a); } + +bool rf_decode_revers_rb2(const std::vector& durations, RfCodes& out) { + enum { ST_RESET, ST_HEADER, ST_DATA } step = ST_RESET; + uint64_t data = 0xF; + int bits = 4; + int header_count = 0; + unsigned int te_last = 0; + ManchesterState manchester; + manchester_reset(manchester); + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? raw : -raw; + + switch (step) { + case ST_RESET: + if (!level && DURATION_DIFF(dur, 600) < TE_DELTA) { + manchester_reset(manchester); + step = ST_HEADER; + header_count = 0; + te_last = 0; + } + break; + + case ST_HEADER: + if (!level) { + if (DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + if (te_last == 1) header_count++; + te_last = 0; + } else { + header_count = 0; te_last = 0; + step = ST_RESET; + } + } else { + if (DURATION_DIFF(dur, (unsigned int)TE_SHORT) < (unsigned int)TE_DELTA) { + if (te_last == 0) header_count++; + te_last = 1; + } else { + header_count = 0; te_last = 0; + step = ST_RESET; + } + } + if (header_count == 4) { + header_count = 0; + data = 0xF; + bits = 4; + step = ST_DATA; + } + break; + + case ST_DATA: { + ManchesterEvent event = manchester_event_for(level, dur, TE_SHORT, TE_LONG, TE_DELTA); + if (event == ManchesterEventReset) { + step = ST_RESET; + break; + } + bool d; + if (manchester_advance(manchester, event, &d)) { + data = (data << 1) | (d ? 1ULL : 0ULL); + bits++; + if (bits > 65) { data = 0; bits = 0; step = ST_RESET; break; } + if (bits >= MIN_BITS) { + uint16_t preamble = (data >> 48) & 0xFF; + uint16_t stop_code = data & 0x3FF; + if (preamble == 0xFF && stop_code == 0x200) { + out.key = data; + out.Bit = bits; + out.te = TE_SHORT; + out.protocol = "Revers_RB2"; + out.preset = "Ook270Async"; + return true; + } + } + } + break; + } + } + } + return false; +} + +bool rf_encode_revers_rb2(const RfCodes& in, std::vector& out) { + (void)in; (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.h b/src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.h new file mode 100644 index 0000000000..23b08247d0 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_revers_rb2.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_revers_rb2(const std::vector& durations, RfCodes& out); +bool rf_encode_revers_rb2(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_roger.cpp b/src/modules/rf/protocols/decoders/rf_decoder_roger.cpp new file mode 100644 index 0000000000..2294a1f5a9 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_roger.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_roger.h" +#include "../rf_config.h" + +#define ROG_TE_SHORT 500 +#define ROG_TE_LONG 1000 +#define ROG_TE_DELTA 270 +#define ROG_MIN_BITS 28 +#define ROG_GAP (ROG_TE_SHORT * 19) + +static inline unsigned int rog_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_roger(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && rog_diff(dur, ROG_GAP) < ROG_TE_DELTA * 5) { + data = 0; bits = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (rog_diff(te_last, ROG_TE_LONG) < ROG_TE_DELTA && + rog_diff(dur, ROG_TE_SHORT) < ROG_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else if (rog_diff(te_last, ROG_TE_SHORT) < ROG_TE_DELTA && + rog_diff(dur, ROG_TE_LONG) < ROG_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (rog_diff(dur, ROG_GAP) < ROG_TE_DELTA * 5) { + if (rog_diff(te_last, ROG_TE_LONG) < ROG_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + } else if (rog_diff(te_last, ROG_TE_SHORT) < ROG_TE_DELTA) { + data = (data << 1) | 0ULL; + bits++; + } + if (bits == ROG_MIN_BITS) { + out.key = data; + out.Bit = ROG_MIN_BITS; + out.te = ROG_TE_SHORT; + out.protocol = "Roger"; + out.preset = "Ook270Async"; + out.serial = (uint32_t)(data >> 12); + out.btn = (uint8_t)((data >> 8) & 0xF); + return true; + } + data = 0; bits = 0; + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + + return false; +} + +bool rf_encode_roger(const RfCodes& in, std::vector& out) { + if (in.Bit != ROG_MIN_BITS) return false; + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(ROG_TE_LONG); + out.push_back(-ROG_TE_SHORT); + } else { + out.push_back(ROG_TE_SHORT); + out.push_back(-ROG_TE_LONG); + } + } + out.push_back(-ROG_GAP); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_roger.h b/src/modules/rf/protocols/decoders/rf_decoder_roger.h new file mode 100644 index 0000000000..e4734797a2 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_roger.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_roger(const std::vector& durations, RfCodes& out); +bool rf_encode_roger(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.cpp b/src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.cpp new file mode 100644 index 0000000000..334bd287c4 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.cpp @@ -0,0 +1,53 @@ +#include "rf_decoder_schrader_tpms.h" +#include "rf_decoder_tpms.h" +#include "rf_decoder_tpms_helpers.h" +#include "../rf_config.h" + +#define SR_TE 120 +#define SR_TE_DELTA 40 +#define SR_SYNC "1111010101" "01011010" + +bool rf_decode_schrader_tpms(const std::vector& durations, RfCodes& out) { + if (durations.size() < 32) return false; + + auto bits = tpms_durations_to_bitmap(durations, SR_TE, SR_TE_DELTA); + if (bits.size() < 64) return false; + + uint32_t off = tpms_bitmap_seek_bits(bits, 0, (uint32_t)bits.size(), SR_SYNC); + if (off == TPMS_SEEK_NOT_FOUND) return false; + + off += 10; + + uint8_t raw[8]; + uint32_t decoded = tpms_convert_from_line_code( + raw, sizeof(raw), bits, off, (uint32_t)bits.size(), "01", "10"); + + if (decoded < 64) return false; + + raw[0] |= 0xf0; + uint8_t cksum = tpms_crc8(raw, sizeof(raw) - 1, 0xf0, 0x07); + if (cksum != raw[7]) return false; + + float kpa = (float)raw[5] * 2.5f; + int temp = raw[6] - 50; + + uint8_t id[4] = {raw[1] & 7, raw[2], raw[3], raw[4]}; + uint32_t id_val = ((uint32_t)id[0] << 24) | ((uint32_t)id[1] << 16) | + ((uint32_t)id[2] << 8) | id[3]; + + out.key = ((uint64_t)id_val << 32) | + ((uint32_t)((int)(kpa * 10)) << 16) | + ((uint32_t)(temp + 100) & 0xFFFF); + out.serial = id_val; + out.Bit = 64; + out.te = SR_TE; + out.protocol = "Schrader_TPMS"; + out.preset = "Ook270Async"; + return true; +} + +bool rf_encode_schrader_tpms(const RfCodes& in, std::vector& out) { + (void)in; + (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.h b/src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.h new file mode 100644 index 0000000000..bdb4a6bd00 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_schrader_tpms.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_schrader_tpms(const std::vector& durations, RfCodes& out); +bool rf_encode_schrader_tpms(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.cpp b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.cpp new file mode 100644 index 0000000000..8eee4b720c --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.cpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_secplus_v1.h" +#include "../rf_config.h" + +#define SP1_TE_SHORT 500 +#define SP1_TE_LONG 1500 +#define SP1_TE_DELTA 100 +#define SP1_MIN_BITS 21 + +#define SP1_BIT_ERR (-1) +#define SP1_BIT_0 0 +#define SP1_BIT_1 1 +#define SP1_BIT_2 2 + +#define SP1_PACKET_1_BASE 0 +#define SP1_PACKET_2_BASE 21 + +static inline unsigned int sp1_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static uint32_t reverse_key_32(uint32_t data, int bits) { + uint32_t rev = 0; + for (int i = 0; i < bits; i++) { + rev = (rev << 1) | ((data >> i) & 1U); + } + return rev; +} + +static bool sp1_decode_payload(uint8_t data_array[44], uint32_t& fixed, uint32_t& rolling) { + uint32_t acc = 0; + uint8_t digit = 0; + + for (uint8_t i = 1; i < 21; i += 2) { + digit = data_array[i]; + rolling = (rolling * 3) + digit; + acc += digit; + digit = (60 + data_array[i + 1] - acc) % 3; + fixed = (fixed * 3) + digit; + acc += digit; + } + + acc = 0; + for (uint8_t i = 22; i < 42; i += 2) { + digit = data_array[i]; + rolling = (rolling * 3) + digit; + acc += digit; + digit = (60 + data_array[i + 1] - acc) % 3; + fixed = (fixed * 3) + digit; + acc += digit; + } + + rolling = reverse_key_32(rolling, 32); + return true; +} + +bool rf_decode_secplus_v1(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_SEARCH_START, + ST_SAVE, + ST_DATA + } step = ST_RESET; + + uint8_t data_array[44] = {0}; + uint8_t base_index = 0; + uint8_t packet_accepted = 0; + int bits = 0; + unsigned int te_last = 0; + int raw_idx = 0; + int total = (int)durations.size(); + + while (raw_idx < total) { + int raw = durations[raw_idx]; + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && sp1_diff(dur, SP1_TE_SHORT * 120) < SP1_TE_DELTA * 120) { + bits = 0; + packet_accepted = 0; + memset(data_array, 0, sizeof(data_array)); + step = ST_SEARCH_START; + } + break; + + case ST_SEARCH_START: + if (level) { + if (sp1_diff(dur, SP1_TE_SHORT) < SP1_TE_DELTA) { + base_index = SP1_PACKET_1_BASE; + data_array[bits + base_index] = SP1_BIT_0; + bits++; + step = ST_SAVE; + } else if (sp1_diff(dur, SP1_TE_LONG) < SP1_TE_DELTA) { + base_index = SP1_PACKET_2_BASE; + data_array[bits + base_index] = SP1_BIT_2; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + + case ST_SAVE: + if (!level) { + if (sp1_diff(dur, SP1_TE_SHORT * 120) < SP1_TE_DELTA * 120) { + if (bits == SP1_MIN_BITS) { + if (base_index == SP1_PACKET_1_BASE) packet_accepted |= 1; + if (base_index == SP1_PACKET_2_BASE) packet_accepted |= 2; + + if (packet_accepted == 3) { + uint32_t fixed = 0, rolling = 0; + sp1_decode_payload(data_array, fixed, rolling); + out.key = ((uint64_t)fixed << 32) | rolling; + out.Bit = 42; + out.te = SP1_TE_SHORT; + out.protocol = "SecPlus_v1"; + out.preset = "Ook270Async"; + out.serial = (fixed >= 27) ? (fixed / 27) : 0; + out.cnt = rolling; + out.btn = fixed % 3; + return true; + } + } + bits = 0; + step = ST_SEARCH_START; + } else { + te_last = dur; + step = ST_DATA; + } + } else { + step = ST_RESET; + } + break; + + case ST_DATA: + if (level && bits <= SP1_MIN_BITS) { + if (sp1_diff(te_last, SP1_TE_SHORT * 3) < SP1_TE_DELTA * 3 && + sp1_diff(dur, SP1_TE_SHORT) < SP1_TE_DELTA) { + data_array[bits + base_index] = SP1_BIT_0; + bits++; + step = ST_SAVE; + } else if (sp1_diff(te_last, SP1_TE_SHORT * 2) < SP1_TE_DELTA * 2 && + sp1_diff(dur, SP1_TE_SHORT * 2) < SP1_TE_DELTA * 2) { + data_array[bits + base_index] = SP1_BIT_1; + bits++; + step = ST_SAVE; + } else if (sp1_diff(te_last, SP1_TE_SHORT) < SP1_TE_DELTA && + sp1_diff(dur, SP1_TE_SHORT * 3) < SP1_TE_DELTA * 3) { + data_array[bits + base_index] = SP1_BIT_2; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + raw_idx++; + } + + return false; +} + +bool rf_encode_secplus_v1(const RfCodes& in, std::vector& out) { + uint32_t fixed = (uint32_t)(in.key >> 32); + uint32_t rolling = (uint32_t)(in.key & 0xFFFFFFFF); + + rolling += 2; + if (rolling == 0xFFFFFFFF) rolling = 0xE6000000; + + rolling = reverse_key_32(rolling, 32); + + uint8_t rolling_array[20] = {0}; + uint8_t fixed_array[20] = {0}; + uint32_t tmp_r = rolling, tmp_f = fixed; + + for (int i = 19; i >= 0; i--) { + rolling_array[i] = tmp_r % 3; + tmp_r /= 3; + fixed_array[i] = tmp_f % 3; + tmp_f /= 3; + } + + uint8_t data_array[44] = {0}; + data_array[0] = 0; + data_array[21] = 2; + + uint32_t acc = 0; + for (uint8_t i = 1; i < 11; i++) { + acc += rolling_array[i - 1]; + data_array[i * 2 - 1] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + data_array[i * 2] = acc % 3; + } + + acc = 0; + for (uint8_t i = 11; i < 21; i++) { + acc += rolling_array[i - 1]; + data_array[i * 2] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + data_array[i * 2 + 1] = acc % 3; + } + + // Packet 1 header + out.push_back(-(SP1_TE_SHORT * (116 + 3))); + out.push_back(SP1_TE_SHORT); + + for (uint8_t i = 1; i < 21; i++) { + switch (data_array[i]) { + case 0: + out.push_back(-(SP1_TE_SHORT * 3)); out.push_back(SP1_TE_SHORT); break; + case 1: + out.push_back(-(SP1_TE_SHORT * 2)); out.push_back(SP1_TE_SHORT * 2); break; + case 2: + out.push_back(-SP1_TE_SHORT); out.push_back(SP1_TE_SHORT * 3); break; + } + } + + // Packet 2 header + out.push_back(-(SP1_TE_SHORT * 116)); + out.push_back(SP1_TE_SHORT * 3); + + for (uint8_t i = 22; i < 42; i++) { + switch (data_array[i]) { + case 0: + out.push_back(-(SP1_TE_SHORT * 3)); out.push_back(SP1_TE_SHORT); break; + case 1: + out.push_back(-(SP1_TE_SHORT * 2)); out.push_back(SP1_TE_SHORT * 2); break; + case 2: + out.push_back(-SP1_TE_SHORT); out.push_back(SP1_TE_SHORT * 3); break; + } + } + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.h b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.h new file mode 100644 index 0000000000..20fc0295b6 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v1.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_secplus_v1(const std::vector& durations, RfCodes& out); +bool rf_encode_secplus_v1(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.cpp b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.cpp new file mode 100644 index 0000000000..a5c65a3744 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.cpp @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_secplus_v2.h" +#include "../rf_config.h" + +#define SP2_TE_SHORT 250 +#define SP2_TE_LONG 500 +#define SP2_TE_DELTA 110 +#define SP2_MIN_BITS 62 +#define SP2_HEADER_MASK 0xFFFF3C0000000000ULL +#define SP2_HEADER_VAL 0x00003C0000000000ULL +#define SP2_PACKET_MASK 0x30000000000ULL +#define SP2_PACKET_1 0x00000000000ULL +#define SP2_PACKET_2 0x10000000000ULL + +static inline unsigned int sp2_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static bool sp2_mix_invert(uint8_t invert, uint16_t p[3]) { + switch (invert) { + case 0x00: p[0] = ~p[0] & 0x03FF; p[1] = ~p[1] & 0x03FF; break; + case 0x01: p[1] = ~p[1] & 0x03FF; break; + case 0x02: p[2] = ~p[2] & 0x03FF; break; + case 0x04: p[0] = ~p[0] & 0x03FF; p[1] = ~p[1] & 0x03FF; p[2] = ~p[2] & 0x03FF; break; + case 0x05: case 0x0a: p[0] = ~p[0] & 0x03FF; p[2] = ~p[2] & 0x03FF; break; + case 0x06: p[1] = ~p[1] & 0x03FF; p[2] = ~p[2] & 0x03FF; break; + case 0x08: p[0] = ~p[0] & 0x03FF; break; + case 0x09: break; + default: return false; + } + return true; +} + +static void sp2_mix_order_decode(uint8_t order, uint16_t p[3]) { + uint16_t a = p[0], b = p[1], c = p[2]; + switch (order) { + case 0x06: case 0x09: p[2] = a; p[0] = c; break; + case 0x08: case 0x04: p[1] = a; p[2] = b; p[0] = c; break; + case 0x01: p[2] = a; p[0] = b; p[1] = c; break; + case 0x00: p[2] = b; p[1] = c; break; + case 0x05: p[1] = a; p[0] = b; break; + case 0x02: case 0x0A: break; + } +} + +static void sp2_mix_order_encode(uint8_t order, uint16_t p[3]) { + uint16_t a = p[0], b = p[1], c = p[2]; + switch (order) { + case 0x06: case 0x09: a = p[2]; b = p[1]; c = p[0]; break; + case 0x08: case 0x04: a = p[1]; b = p[2]; c = p[0]; break; + case 0x01: a = p[2]; b = p[0]; c = p[1]; break; + case 0x00: a = p[0]; b = p[2]; c = p[1]; break; + case 0x05: a = p[1]; b = p[0]; c = p[2]; break; + case 0x02: case 0x0A: break; + } + p[0] = a; p[1] = b; p[2] = c; +} + +static bool sp2_decode_half(uint64_t data, uint8_t roll_array[9], uint32_t& fixed) { + uint8_t order = (data >> 34) & 0x0f; + uint8_t invert = (data >> 30) & 0x0f; + uint16_t p[3] = {0}; + + for (int i = 29; i >= 0; i -= 3) { + p[0] = (p[0] << 1) | ((data >> i) & 1ULL); + p[1] = (p[1] << 1) | ((data >> (i - 1)) & 1ULL); + p[2] = (p[2] << 1) | ((data >> (i - 2)) & 1ULL); + } + + if (!sp2_mix_invert(invert, p)) return false; + sp2_mix_order_decode(order, p); + + data = (uint64_t)order << 4 | invert; + int k = 0; + for (int i = 6; i >= 0; i -= 2) { + roll_array[k] = (data >> i) & 0x03; + if (roll_array[k++] == 3) return false; + } + for (int i = 8; i >= 0; i -= 2) { + roll_array[k] = (p[2] >> i) & 0x03; + if (roll_array[k++] == 3) return false; + } + + fixed = (p[0] << 10) | p[1]; + return true; +} + +static void sp2_remote_controller(uint64_t packet_1, uint64_t packet_2, + uint32_t& serial, uint32_t& cnt, uint8_t& btn) { + uint32_t fixed_1 = 0, fixed_2 = 0; + uint8_t roll_1[9] = {0}, roll_2[9] = {0}; + uint8_t rolling_digits[18] = {0}; + + if (!sp2_decode_half(packet_1, roll_1, fixed_1) || + !sp2_decode_half(packet_2, roll_2, fixed_2)) { + cnt = 0; btn = 0; serial = 0; + return; + } + + rolling_digits[0] = roll_2[8]; + rolling_digits[1] = roll_1[8]; + rolling_digits[2] = roll_2[4]; + rolling_digits[3] = roll_2[5]; + rolling_digits[4] = roll_2[6]; + rolling_digits[5] = roll_2[7]; + rolling_digits[6] = roll_1[4]; + rolling_digits[7] = roll_1[5]; + rolling_digits[8] = roll_1[6]; + rolling_digits[9] = roll_1[7]; + rolling_digits[10] = roll_2[0]; + rolling_digits[11] = roll_2[1]; + rolling_digits[12] = roll_2[2]; + rolling_digits[13] = roll_2[3]; + rolling_digits[14] = roll_1[0]; + rolling_digits[15] = roll_1[1]; + rolling_digits[16] = roll_1[2]; + rolling_digits[17] = roll_1[3]; + + uint32_t rolling = 0; + for (int i = 0; i < 18; i++) { + rolling = (rolling * 3) + rolling_digits[i]; + } + + if (rolling >= 0x10000000) { + cnt = 0; btn = 0; serial = 0; + } else { + uint32_t rev = 0; + for (int i = 0; i < 28; i++) { + rev = (rev << 1) | ((rolling >> i) & 1U); + } + cnt = rev; + btn = (fixed_1 >> 12) & 0xF; + serial = (fixed_1 << 20) | fixed_2; + } +} + +static bool sp2_check_packet(uint64_t data, uint64_t& packet_1) { + if ((data & SP2_HEADER_MASK) == SP2_HEADER_VAL) { + if ((data & SP2_PACKET_MASK) == SP2_PACKET_1) { + packet_1 = data; + } else if (((data & SP2_PACKET_MASK) == SP2_PACKET_2) && packet_1) { + return true; + } + } + return false; +} + +// Manchester helpers for decoding +typedef enum { ME_RESET, ME_LOW, ME_HIGH } manchester_state; + +static bool manchester_advance(manchester_state& state, bool high_pulse, bool& bit) { + switch (state) { + case ME_RESET: + state = high_pulse ? ME_HIGH : ME_LOW; + return false; + case ME_LOW: + if (high_pulse) { + state = ME_HIGH; + return false; + } + bit = 1; state = ME_LOW; + return true; + case ME_HIGH: + if (!high_pulse) { + state = ME_LOW; + return false; + } + bit = 0; state = ME_HIGH; + return true; + } + return false; +} + +static void manchester_reset(manchester_state& state) { + state = ME_RESET; +} + +bool rf_decode_secplus_v2(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { ST_RESET, ST_DATA } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + uint64_t packet_1 = 0; + manchester_state man_state; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && sp2_diff(dur, SP2_TE_LONG * 130) < SP2_TE_DELTA * 100) { + data = 0; bits = 0; packet_1 = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, true, dummy); // prime with long high + manchester_advance(man_state, false, dummy); // then short low + step = ST_DATA; + } + break; + + case ST_DATA: { + if (!level) { + if (sp2_diff(dur, SP2_TE_SHORT) < SP2_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else if (sp2_diff(dur, SP2_TE_LONG) < SP2_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else if (dur >= (uint32_t)(SP2_TE_LONG * 2 + SP2_TE_DELTA)) { + if (bits == SP2_MIN_BITS) { + if (sp2_check_packet(data, packet_1)) { + uint32_t serial = 0, cnt = 0; + uint8_t btn = 0; + sp2_remote_controller(packet_1, data, serial, cnt, btn); + out.key = data; + out.Bit = SP2_MIN_BITS; + out.te = SP2_TE_SHORT; + out.protocol = "SecPlus_v2"; + out.preset = "Ook270Async"; + out.serial = serial; + out.cnt = cnt; + out.btn = btn; + return true; + } + } + data = 0; bits = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, true, dummy); + manchester_advance(man_state, false, dummy); + } else { + step = ST_RESET; + } + } else { + if (sp2_diff(dur, SP2_TE_SHORT) < SP2_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else if (sp2_diff(dur, SP2_TE_LONG) < SP2_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else { + step = ST_RESET; + } + } + break; + } + } + } + + return false; +} + +bool rf_encode_secplus_v2(const RfCodes& in, std::vector& out) { + (void)in; (void)out; + // Encoder requires two interdependent half-messages; use the saved .sub file + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.h b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.h new file mode 100644 index 0000000000..e90cba1a11 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_secplus_v2.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_secplus_v2(const std::vector& durations, RfCodes& out); +bool rf_encode_secplus_v2(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_smc5326.cpp b/src/modules/rf/protocols/decoders/rf_decoder_smc5326.cpp new file mode 100644 index 0000000000..437654ed38 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_smc5326.cpp @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Port of Flipper Zero SMC5326/AX5326 protocol decoder (GPL-3.0-or-later), +// lib/subghz/protocols/smc5326.c — PWM 25-bit rolling/static code with +// auto-calibrated TE and DIP-switch encoding (2 bits per switch: +/o/-). +#include "rf_decoder_smc5326.h" +#include "../rf_config.h" + +#define SM_TE_SHORT 300 +#define SM_TE_LONG 900 +#define SM_TE_DELTA 200 +#define SM_MIN_BITS 25 + +static inline unsigned int sm_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +bool rf_decode_smc5326(const std::vector& durations, RfCodes& out) { + if (durations.size() < 10) return false; + + enum { ST_RESET, ST_SAVE, ST_CHECK } step = ST_RESET; + uint64_t data = 0; + int bits = 0; + unsigned int te_last = 0; + uint32_t te_accum = 0; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (!level && sm_diff(dur, SM_TE_SHORT * 24) < SM_TE_DELTA * 12) { + data = 0; bits = 0; te_accum = 0; + step = ST_SAVE; + } + break; + + case ST_SAVE: + if (level) { + te_last = dur; + te_accum += dur; + step = ST_CHECK; + } else { + step = ST_RESET; + } + break; + + case ST_CHECK: + if (!level) { + if (dur >= (unsigned int)(SM_TE_LONG * 2)) { + te_accum += dur; + if (bits == SM_MIN_BITS) { + te_accum /= (SM_MIN_BITS * 4 + 1); + out.key = data; + out.Bit = SM_MIN_BITS; + out.te = (int)te_accum; + out.protocol = "SMC5326"; + out.preset = "Ook270Async"; + return true; + } + data = 0; bits = 0; te_accum = 0; + step = ST_SAVE; + break; + } + te_accum += dur; + if (sm_diff(te_last, SM_TE_SHORT) < SM_TE_DELTA && + sm_diff(dur, SM_TE_LONG) < SM_TE_DELTA * 3) { + data = (data << 1) | 0ULL; + bits++; + step = ST_SAVE; + } else if (sm_diff(te_last, SM_TE_LONG) < SM_TE_DELTA * 3 && + sm_diff(dur, SM_TE_SHORT) < SM_TE_DELTA) { + data = (data << 1) | 1ULL; + bits++; + step = ST_SAVE; + } else { + step = ST_RESET; + } + } else { + step = ST_RESET; + } + break; + } + } + return false; +} + +bool rf_encode_smc5326(const RfCodes& in, std::vector& out) { + out.clear(); + out.push_back(-(SM_TE_SHORT * 24)); + for (int i = in.Bit - 1; i >= 0; i--) { + if ((in.key >> i) & 1ULL) { + out.push_back(SM_TE_LONG); + out.push_back(-SM_TE_SHORT); + } else { + out.push_back(SM_TE_SHORT); + out.push_back(-SM_TE_LONG); + } + } + out.push_back(SM_TE_SHORT); + out.push_back(-(SM_TE_SHORT * 25)); + return true; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_smc5326.h b/src/modules/rf/protocols/decoders/rf_decoder_smc5326.h new file mode 100644 index 0000000000..b112f198e1 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_smc5326.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_smc5326(const std::vector& durations, RfCodes& out); +bool rf_encode_smc5326(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.cpp b/src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.cpp new file mode 100644 index 0000000000..cec7007680 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.cpp @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_somfy_keytis.h" +#include "../rf_config.h" + +#define KEYTIS_TE_SHORT 640 +#define KEYTIS_TE_LONG 1280 +#define KEYTIS_TE_DELTA 250 +#define KEYTIS_MIN_BITS 80 + +static inline unsigned int keytis_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static uint8_t keytis_crc(uint64_t data) { + uint8_t crc = 0; + data &= 0xFFF0FFFFFFFFFFULL; + for (uint8_t i = 0; i < 56; i += 8) { + crc = crc ^ (uint8_t)(data >> i) ^ (uint8_t)(data >> (i + 4)); + } + return crc & 0xf; +} + +typedef enum { ME_RESET, ME_LOW, ME_HIGH } manchester_state; + +static bool manchester_advance(manchester_state& state, bool high_pulse, bool& bit) { + switch (state) { + case ME_RESET: + state = high_pulse ? ME_HIGH : ME_LOW; + return false; + case ME_LOW: + if (high_pulse) { + state = ME_HIGH; + return false; + } + bit = 1; state = ME_LOW; + return true; + case ME_HIGH: + if (!high_pulse) { + state = ME_LOW; + return false; + } + bit = 0; state = ME_HIGH; + return true; + } + return false; +} + +static void manchester_reset(manchester_state& state) { + state = ME_RESET; +} + +bool rf_decode_somfy_keytis(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_PREAMBLE_H, + ST_PREAMBLE_L, + ST_CHECK_PREAMBLE, + ST_DATA + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + int header_cnt = 0; + uint32_t pdc = 0; + manchester_state man_state; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && keytis_diff(dur, KEYTIS_TE_SHORT * 4) < KEYTIS_TE_DELTA * 4) { + header_cnt++; + step = ST_PREAMBLE_H; + } + break; + + case ST_PREAMBLE_H: + if (!level && keytis_diff(dur, KEYTIS_TE_SHORT * 4) < KEYTIS_TE_DELTA * 4) { + step = ST_CHECK_PREAMBLE; + } else { + header_cnt = 0; + step = ST_RESET; + } + break; + + case ST_CHECK_PREAMBLE: + if (level) { + if (keytis_diff(dur, KEYTIS_TE_SHORT * 4) < KEYTIS_TE_DELTA * 4) { + header_cnt++; + step = ST_PREAMBLE_H; + } else if (header_cnt > 1 && + keytis_diff(dur, KEYTIS_TE_SHORT * 7) < KEYTIS_TE_DELTA * 4) { + data = 0; bits = 0; pdc = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, true, dummy); + step = ST_DATA; + } else { + header_cnt = 0; + step = ST_RESET; + } + } + break; + + case ST_DATA: { + if (!level) { + if (keytis_diff(dur, KEYTIS_TE_SHORT) < KEYTIS_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + if (bits < 56) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + } else { + pdc = (pdc << 1) | (bit_out ? 1U : 0U); + } + bits++; + } + } else if (keytis_diff(dur, KEYTIS_TE_LONG) < KEYTIS_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + if (bits < 56) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + } else { + pdc = (pdc << 1) | (bit_out ? 1U : 0U); + } + bits++; + } + } else if (dur >= (uint32_t)(KEYTIS_TE_LONG + KEYTIS_TE_DELTA)) { + if (bits == KEYTIS_MIN_BITS) { + uint64_t tmp = data ^ (data >> 8); + uint8_t crc_calc = ((tmp >> 40) & 0xF); + uint8_t crc_exp = keytis_crc(tmp); + if (crc_calc == crc_exp) { + uint64_t dec = data ^ (data >> 8); + out.key = data; + out.Bit = KEYTIS_MIN_BITS; + out.te = KEYTIS_TE_SHORT; + out.protocol = "Somfy_Keytis"; + out.preset = "Ook270Async"; + out.btn = (dec >> 48) & 0xF; + out.cnt = (dec >> 24) & 0xFFFF; + out.serial = dec & 0xFFFFFF; + return true; + } + } + data = 0; bits = 0; pdc = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, true, dummy); + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + if (keytis_diff(dur, KEYTIS_TE_SHORT) < KEYTIS_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + if (bits < 56) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + } else { + pdc = (pdc << 1) | (bit_out ? 1U : 0U); + } + bits++; + } + } else if (keytis_diff(dur, KEYTIS_TE_LONG) < KEYTIS_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + if (bits < 56) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + } else { + pdc = (pdc << 1) | (bit_out ? 1U : 0U); + } + bits++; + } + } else { + step = ST_RESET; + } + } + break; + } + } + } + + return false; +} + +bool rf_encode_somfy_keytis(const RfCodes& in, std::vector& out) { + (void)in; (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.h b/src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.h new file mode 100644 index 0000000000..a76e1276e7 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_somfy_keytis.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_somfy_keytis(const std::vector& durations, RfCodes& out); +bool rf_encode_somfy_keytis(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.cpp b/src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.cpp new file mode 100644 index 0000000000..68929c491a --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.cpp @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#include "rf_decoder_somfy_telis.h" +#include "../rf_config.h" + +#define SOMFY_TE_SHORT 640 +#define SOMFY_TE_LONG 1280 +#define SOMFY_TE_DELTA 250 +#define SOMFY_MIN_BITS 56 + +static inline unsigned int somfy_diff(unsigned int a, unsigned int b) { + return (a > b) ? (a - b) : (b - a); +} + +static uint8_t somfy_crc(uint64_t data) { + uint8_t crc = 0; + data &= 0xFFF0FFFFFFFFFFULL; + for (uint8_t i = 0; i < 56; i += 8) { + crc = crc ^ (uint8_t)(data >> i) ^ (uint8_t)(data >> (i + 4)); + } + return crc & 0xf; +} + +typedef enum { ME_RESET, ME_LOW, ME_HIGH } manchester_state; + +static bool manchester_advance(manchester_state& state, bool high_pulse, bool& bit) { + switch (state) { + case ME_RESET: + state = high_pulse ? ME_HIGH : ME_LOW; + return false; + case ME_LOW: + if (high_pulse) { + state = ME_HIGH; + return false; + } + bit = 1; state = ME_LOW; + return true; + case ME_HIGH: + if (!high_pulse) { + state = ME_LOW; + return false; + } + bit = 0; state = ME_HIGH; + return true; + } + return false; +} + +static void manchester_reset(manchester_state& state) { + state = ME_RESET; +} + +bool rf_decode_somfy_telis(const std::vector& durations, RfCodes& out) { + if (durations.size() < 4) return false; + + enum { + ST_RESET, + ST_PREAMBLE_H, + ST_PREAMBLE_L, + ST_CHECK_PREAMBLE, + ST_DATA + } step = ST_RESET; + + uint64_t data = 0; + int bits = 0; + int header_cnt = 0; + manchester_state man_state; + + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = (unsigned int)(raw > 0 ? raw : -raw); + + switch (step) { + case ST_RESET: + if (level && somfy_diff(dur, SOMFY_TE_SHORT * 4) < SOMFY_TE_DELTA * 4) { + header_cnt++; + step = ST_PREAMBLE_H; + } + break; + + case ST_PREAMBLE_H: + if (!level && somfy_diff(dur, SOMFY_TE_SHORT * 4) < SOMFY_TE_DELTA * 4) { + step = ST_CHECK_PREAMBLE; + } else { + header_cnt = 0; + step = ST_RESET; + } + break; + + case ST_CHECK_PREAMBLE: + if (level) { + if (somfy_diff(dur, SOMFY_TE_SHORT * 4) < SOMFY_TE_DELTA * 4) { + header_cnt++; + step = ST_PREAMBLE_H; + } else if (header_cnt > 1 && + somfy_diff(dur, SOMFY_TE_SHORT * 7) < SOMFY_TE_DELTA * 4) { + data = 0; bits = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, true, dummy); + step = ST_DATA; + } else { + header_cnt = 0; + step = ST_RESET; + } + } + break; + + case ST_DATA: { + if (!level) { + if (somfy_diff(dur, SOMFY_TE_SHORT) < SOMFY_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else if (somfy_diff(dur, SOMFY_TE_LONG) < SOMFY_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, false, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else if (dur >= (uint32_t)(SOMFY_TE_LONG + SOMFY_TE_DELTA)) { + if (bits == SOMFY_MIN_BITS) { + uint64_t tmp = data ^ (data >> 8); + uint8_t crc_calc = ((tmp >> 40) & 0xF); + uint8_t crc_exp = somfy_crc(tmp); + if (crc_calc == crc_exp) { + uint64_t dec = data ^ (data >> 8); + out.key = data; + out.Bit = SOMFY_MIN_BITS; + out.te = SOMFY_TE_SHORT; + out.protocol = "Somfy_Telis"; + out.preset = "Ook270Async"; + out.btn = (dec >> 44) & 0xF; + out.cnt = (dec >> 24) & 0xFFFF; + out.serial = dec & 0xFFFFFF; + return true; + } + } + data = 0; bits = 0; + manchester_reset(man_state); + bool dummy = false; + manchester_advance(man_state, true, dummy); + step = ST_RESET; + } else { + step = ST_RESET; + } + } else { + if (somfy_diff(dur, SOMFY_TE_SHORT) < SOMFY_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else if (somfy_diff(dur, SOMFY_TE_LONG) < SOMFY_TE_DELTA) { + bool bit_out = false; + if (manchester_advance(man_state, true, bit_out)) { + data = (data << 1) | (bit_out ? 1ULL : 0ULL); + bits++; + } + } else { + step = ST_RESET; + } + } + break; + } + } + } + + return false; +} + +bool rf_encode_somfy_telis(const RfCodes& in, std::vector& out) { + (void)in; (void)out; + // Somfy Telis encoder requires rolling code increment; use saved .sub file + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.h b/src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.h new file mode 100644 index 0000000000..fef0bab2c0 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_somfy_telis.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../rf_protocol.h" + +bool rf_decode_somfy_telis(const std::vector& durations, RfCodes& out); +bool rf_encode_somfy_telis(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.cpp b/src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.cpp new file mode 100644 index 0000000000..6f286a6afc --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.cpp @@ -0,0 +1,66 @@ +#include "rf_decoder_toyota_tpms.h" +#include "rf_decoder_tpms.h" +#include "rf_decoder_tpms_helpers.h" +#include "../rf_config.h" + +#define TO_TE 48 +#define TO_TE_DELTA 16 + +static const char *toyota_sync_patterns[] = { + "00111100", + "001111100", + "00111101", + "001111101", + NULL +}; + +bool rf_decode_toyota_tpms(const std::vector& durations, RfCodes& out) { + if (durations.size() < 32) return false; + + auto bits = tpms_durations_to_bitmap(durations, TO_TE, TO_TE_DELTA); + if (bits.size() < 128) return false; + + uint32_t off = 0; + int sync_idx = 0; + bool found = false; + + for (int j = 0; toyota_sync_patterns[j]; j++) { + off = tpms_bitmap_seek_bits(bits, 0, (uint32_t)bits.size(), toyota_sync_patterns[j]); + if (off != TPMS_SEEK_NOT_FOUND) { + sync_idx = j; + found = true; + off += (uint32_t)strlen(toyota_sync_patterns[j]) - 2; + break; + } + } + if (!found) return false; + + uint8_t raw[9]; + uint32_t decoded = tpms_convert_from_diff_manchester( + raw, sizeof(raw), bits, off, (uint32_t)bits.size(), true); + + if (decoded < 72) return false; + if (tpms_crc8(raw, 8, 0x80, 7) != raw[8]) return false; + + float psi = (float)(((raw[4] & 0x7f) << 1) | (raw[5] >> 7)) * 0.25f - 7.0f; + int temp = (((raw[5] & 0x7f) << 1) | (raw[6] >> 7)) - 40; + + uint32_t id_val = ((uint32_t)raw[0] << 24) | ((uint32_t)raw[1] << 16) | + ((uint32_t)raw[2] << 8) | raw[3]; + + out.key = ((uint64_t)id_val << 32) | + ((uint32_t)((int)(psi * 10)) << 16) | + ((uint32_t)(temp + 100) & 0xFFFF); + out.serial = id_val; + out.Bit = 72; + out.te = TO_TE; + out.protocol = "Toyota_TPMS"; + out.preset = "2FSKDev238Async"; + return true; +} + +bool rf_encode_toyota_tpms(const RfCodes& in, std::vector& out) { + (void)in; + (void)out; + return false; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.h b/src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.h new file mode 100644 index 0000000000..70965d4861 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_toyota_tpms.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include "../rf_protocol.h" + +bool rf_decode_toyota_tpms(const std::vector& durations, RfCodes& out); +bool rf_encode_toyota_tpms(const RfCodes& in, std::vector& out); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_tpms.cpp b/src/modules/rf/protocols/decoders/rf_decoder_tpms.cpp new file mode 100644 index 0000000000..20ae129760 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_tpms.cpp @@ -0,0 +1,15 @@ +#include "rf_decoder_tpms.h" + +uint8_t tpms_crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly) { + uint8_t crc = init; + for (size_t i = 0; i < len; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + if (crc & 0x80) + crc = (uint8_t)((crc << 1) ^ poly); + else + crc <<= 1; + } + } + return crc; +} diff --git a/src/modules/rf/protocols/decoders/rf_decoder_tpms.h b/src/modules/rf/protocols/decoders/rf_decoder_tpms.h new file mode 100644 index 0000000000..63cf73fb40 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_tpms.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +uint8_t tpms_crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly); diff --git a/src/modules/rf/protocols/decoders/rf_decoder_tpms_helpers.h b/src/modules/rf/protocols/decoders/rf_decoder_tpms_helpers.h new file mode 100644 index 0000000000..fcdccce550 --- /dev/null +++ b/src/modules/rf/protocols/decoders/rf_decoder_tpms_helpers.h @@ -0,0 +1,153 @@ +#pragma once +#include +#include +#include + +static inline std::vector tpms_durations_to_bitmap( + const std::vector& durations, + unsigned int te, + unsigned int te_delta) +{ + std::vector bits; + for (int raw : durations) { + bool level = raw > 0; + unsigned int dur = raw > 0 ? (unsigned)raw : (unsigned)(-raw); + unsigned int count = (dur + te/2) / te; + if (count == 0) count = 1; + for (unsigned int i = 0; i < count; i++) + bits.push_back(level); + } + return bits; +} + +#define TPMS_SEEK_NOT_FOUND UINT32_MAX + +static inline uint32_t tpms_bitmap_seek_bits( + const std::vector& bits, + uint32_t startpos, + uint32_t maxbits, + const char *pattern) +{ + uint32_t patlen = (uint32_t)strlen(pattern); + if (startpos + patlen > maxbits) return TPMS_SEEK_NOT_FOUND; + if (patlen == 0) return TPMS_SEEK_NOT_FOUND; + + for (uint32_t i = startpos; i + patlen <= maxbits; i++) { + bool match = true; + for (uint32_t j = 0; j < patlen; j++) { + bool expected = (pattern[j] == '1'); + if (bits[i + j] != expected) { match = false; break; } + } + if (match) return i; + } + return TPMS_SEEK_NOT_FOUND; +} + +static inline uint32_t tpms_convert_from_line_code( + uint8_t *buf, uint32_t buflen, + const std::vector& bits, + uint32_t offset, uint32_t maxbits, + const char *zero_pattern, + const char *one_pattern) +{ + uint32_t zlen = (uint32_t)strlen(zero_pattern); + uint32_t olen = (uint32_t)strlen(one_pattern); + if (zlen != olen) return 0; + uint32_t symlen = zlen; + uint32_t bitpos = 0; + uint32_t byte_pos = 0; + + while (offset + symlen <= maxbits && byte_pos < buflen) { + // Check for zero pattern + bool is_zero = true; + for (uint32_t j = 0; j < symlen; j++) { + bool expected = (zero_pattern[j] == '1'); + if (bits[offset + j] != expected) { is_zero = false; break; } + } + if (is_zero) { + if (bitpos == 0) buf[byte_pos] = 0; + buf[byte_pos] = (uint8_t)((buf[byte_pos] << 1) | 0); + bitpos++; + offset += symlen; + if (bitpos == 8) { bitpos = 0; byte_pos++; } + continue; + } + + // Check for one pattern + bool is_one = true; + for (uint32_t j = 0; j < symlen; j++) { + bool expected = (one_pattern[j] == '1'); + if (bits[offset + j] != expected) { is_one = false; break; } + } + if (is_one) { + if (bitpos == 0) buf[byte_pos] = 0; + buf[byte_pos] = (uint8_t)((buf[byte_pos] << 1) | 1); + bitpos++; + offset += symlen; + if (bitpos == 8) { bitpos = 0; byte_pos++; } + continue; + } + + // Neither pattern matched - stop + break; + } + + return byte_pos * 8 + bitpos; +} + +static inline uint32_t tpms_convert_from_diff_manchester( + uint8_t *buf, uint32_t buflen, + const std::vector& bits, + uint32_t offset, uint32_t maxbits, + bool previous) +{ + uint32_t byte_pos = 0; + int bitpos = 0; + + while (offset + 2 <= maxbits && byte_pos < buflen) { + // Differential Manchester: bit is encoded by the TRANSITION + // at the start of the symbol period. + // Level at offset is the start level. + // If level toggles at offset+1, bit = 0 (transition at start) + // If level stays same at offset+1, bit = 1 (no transition at start) + // Reference: previous == level at start of symbol + + bool start_level = bits[offset]; + bool mid_level = bits[offset + 1]; + + // In differential Manchester, if level changes from start to mid, + // that's our transition detection + bool bit_val; + if (start_level != previous) { + // Transition at start of symbol = data 0 + bit_val = false; + } else { + // No transition at start = data 1 + bit_val = true; + } + previous = mid_level; + + if (bitpos == 0) buf[byte_pos] = 0; + buf[byte_pos] = (uint8_t)((buf[byte_pos] << 1) | (bit_val ? 1 : 0)); + bitpos++; + offset += 2; + + if (bitpos == 8) { bitpos = 0; byte_pos++; } + } + + return byte_pos * 8 + (uint32_t)bitpos; +} + +static inline bool tpms_bitmap_match_bits( + const std::vector& bits, + uint32_t bitpos, uint32_t maxbits, + const char *pattern) +{ + uint32_t patlen = (uint32_t)strlen(pattern); + if (bitpos + patlen > maxbits) return false; + for (uint32_t j = 0; j < patlen; j++) { + if (bits[bitpos + j] != (pattern[j] == '1')) + return false; + } + return true; +} diff --git a/src/modules/rf/protocols/rf_registry.cpp b/src/modules/rf/protocols/rf_registry.cpp index 059f9bf162..a25c47f30d 100644 --- a/src/modules/rf/protocols/rf_registry.cpp +++ b/src/modules/rf/protocols/rf_registry.cpp @@ -77,6 +77,7 @@ static const RfProtoAlias rf_proto_aliases[] = { {"Holtec 12bit", "Holtek_12bit"}, {"Holtek_HT12X", "Holtek_HT12"}, {"Phoenix_V2", "PhoenixV2"}, + {"hormann", "Hormann_HSM"}, }; const RfProtocolDef *rf_find_protocol(const String &name) { @@ -91,7 +92,7 @@ const RfProtocolDef *rf_find_protocol(const String &name) { for (const auto &p : rf_protocols) { if (wanted == p.name) return &p; } - return rf_find_ext_protocol(name); + return rf_find_ext_protocol(wanted); } String rf_flipper_protocol_name(const String &canonical) { diff --git a/src/modules/rf/protocols/rf_registry_ext.cpp b/src/modules/rf/protocols/rf_registry_ext.cpp index b3847ba360..3690fb308a 100644 --- a/src/modules/rf/protocols/rf_registry_ext.cpp +++ b/src/modules/rf/protocols/rf_registry_ext.cpp @@ -9,6 +9,46 @@ #include "decoders/rf_decoder_faac_slh.h" #include "decoders/rf_decoder_power_smart.h" #include "decoders/rf_decoder_intertechno_v3.h" +#include "decoders/rf_decoder_marantec.h" +#include "decoders/rf_decoder_smc5326.h" +#include "decoders/rf_decoder_honeywell_wdb.h" +#include "decoders/rf_decoder_linear_delta3.h" +// Phase 3 — Rolling code decoders +#include "decoders/rf_decoder_secplus_v1.h" +#include "decoders/rf_decoder_secplus_v2.h" +#include "decoders/rf_decoder_somfy_telis.h" +#include "decoders/rf_decoder_somfy_keytis.h" +#include "decoders/rf_decoder_hormann.h" +#include "decoders/rf_decoder_came_twee.h" +#include "decoders/rf_decoder_came_atomo.h" +#include "decoders/rf_decoder_nice_flor_s.h" +#include "decoders/rf_decoder_ido.h" +#include "decoders/rf_decoder_nero_radio.h" +#include "decoders/rf_decoder_nero_sketch.h" +// Phase 2 — Additional simple OOK decoders +#include "decoders/rf_decoder_bett.h" +#include "decoders/rf_decoder_feron.h" +#include "decoders/rf_decoder_roger.h" +#include "decoders/rf_decoder_elplast.h" +// Phase 4 — TPMS decoders +#include "decoders/rf_decoder_schrader_tpms.h" +#include "decoders/rf_decoder_ford_tpms.h" +#include "decoders/rf_decoder_renault_tpms.h" +#include "decoders/rf_decoder_citroen_tpms.h" +#include "decoders/rf_decoder_toyota_tpms.h" +// Phase 5 — Flipper ported OOK decoders +#include "decoders/rf_decoder_doitrand.h" +#include "decoders/rf_decoder_magellan.h" +#include "decoders/rf_decoder_dooya.h" +#include "decoders/rf_decoder_legrand.h" +#include "decoders/rf_decoder_chamb_code.h" +#include "decoders/rf_decoder_marantec24.h" +#include "decoders/rf_decoder_hollarm.h" +#include "decoders/rf_decoder_hay21.h" +#include "decoders/rf_decoder_gangqi.h" +#include "decoders/rf_decoder_dickert_mahs.h" +#include "decoders/rf_decoder_revers_rb2.h" +#include "decoders/rf_decoder_megacode.h" #define DECODE RF_PF_HAS_DECODER @@ -20,6 +60,82 @@ static const RfProtocolDef rf_ext_protocols[] = { rf_decode_power_smart, rf_encode_power_smart}, {"Intertechno_V3", 275, {0, 0}, {0, 0}, {0, 0}, 32, false, DECODE, rf_decode_intertechno_v3, rf_encode_intertechno_v3}, + {"Marantec", 1000, {0, 0}, {0, 0}, {0, 0}, 49, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_marantec, rf_encode_marantec}, + {"SMC5326", 300, {0, 0}, {0, 0}, {0, 0}, 25, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_smc5326, rf_encode_smc5326}, + {"Honeywell_WDB", 160, {0, 0}, {0, 0}, {0, 0}, 48, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_honeywell_wdb, rf_encode_honeywell_wdb}, + {"Linear_Delta3", 500, {0, 0}, {0, 0}, {0, 0}, 8, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_linear_delta3, rf_encode_linear_delta3}, + // Phase 3 — Rolling code decoders + {"SecPlus_v1", 500, {0, 0}, {0, 0}, {0, 0}, 42, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_secplus_v1, rf_encode_secplus_v1}, + {"SecPlus_v2", 250, {0, 0}, {0, 0}, {0, 0}, 62, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_secplus_v2, rf_encode_secplus_v2}, + {"Somfy_Telis", 640, {0, 0}, {0, 0}, {0, 0}, 56, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_somfy_telis, rf_encode_somfy_telis}, + {"Somfy_Keytis", 640, {0, 0}, {0, 0}, {0, 0}, 80, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_somfy_keytis, rf_encode_somfy_keytis}, + {"Hormann_HSM", 500, {0, 0}, {0, 0}, {0, 0}, 44, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_hormann, rf_encode_hormann}, + {"CAME_Twee", 500, {0, 0}, {0, 0}, {0, 0}, 54, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_came_twee, rf_encode_came_twee}, + {"CAME_Atomo", 600, {0, 0}, {0, 0}, {0, 0}, 62, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_came_atomo, rf_encode_came_atomo}, + {"Nice_Flor_S", 500, {0, 0}, {0, 0}, {0, 0}, 52, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_nice_flor_s, rf_encode_nice_flor_s}, + {"IDO", 450, {0, 0}, {0, 0}, {0, 0}, 48, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_ido, rf_encode_ido}, + {"Nero_Radio", 200, {0, 0}, {0, 0}, {0, 0}, 56, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_nero_radio, rf_encode_nero_radio}, + {"Nero_Sketch", 330, {0, 0}, {0, 0}, {0, 0}, 40, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_nero_sketch, rf_encode_nero_sketch}, + // Phase 2 — Additional simple OOK decoders + {"Bett", 340, {0, 0}, {0, 0}, {0, 0}, 18, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_bett, rf_encode_bett}, + {"Feron", 350, {0, 0}, {0, 0}, {0, 0}, 32, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_feron, rf_encode_feron}, + {"Roger", 500, {0, 0}, {0, 0}, {0, 0}, 28, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_roger, rf_encode_roger}, + {"Elplast", 230, {0, 0}, {0, 0}, {0, 0}, 18, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_elplast, rf_encode_elplast}, + // Phase 4 — TPMS decoders + {"Schrader_TPMS", 120, {0, 0}, {0, 0}, {0, 0}, 64, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_schrader_tpms, rf_encode_schrader_tpms}, + {"Ford_TPMS", 52, {0, 0}, {0, 0}, {0, 0}, 64, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_ford_tpms, rf_encode_ford_tpms}, + {"Renault_TPMS", 48, {0, 0}, {0, 0}, {0, 0}, 72, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_renault_tpms, rf_encode_renault_tpms}, + {"Citroen_TPMS", 52, {0, 0}, {0, 0}, {0, 0}, 80, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_citroen_tpms, rf_encode_citroen_tpms}, + {"Toyota_TPMS", 48, {0, 0}, {0, 0}, {0, 0}, 72, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_toyota_tpms, rf_encode_toyota_tpms}, + // Phase 5 — Flipper ported OOK decoders + {"Doitrand", 400, {0, 0}, {0, 0}, {0, 0}, 37, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_doitrand, rf_encode_doitrand}, + {"Magellan", 200, {0, 0}, {0, 0}, {0, 0}, 32, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_magellan, rf_encode_magellan}, + {"Dooya", 366, {0, 0}, {0, 0}, {0, 0}, 40, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_dooya, rf_encode_dooya}, + {"Legrand", 375, {0, 0}, {0, 0}, {0, 0}, 18, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_legrand, nullptr}, + {"Chamb_Code", 1000, {0, 0}, {0, 0}, {0, 0}, 10, false, DECODE, + rf_decode_chamb_code, nullptr}, + {"Marantec24", 800, {0, 0}, {0, 0}, {0, 0}, 24, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_marantec24, rf_encode_marantec24}, + {"Hollarm", 200, {0, 0}, {0, 0}, {0, 0}, 42, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_hollarm, rf_encode_hollarm}, + {"Hay21", 300, {0, 0}, {0, 0}, {0, 0}, 21, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_hay21, nullptr}, + {"GangQi", 500, {0, 0}, {0, 0}, {0, 0}, 34, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_gangqi, rf_encode_gangqi}, + {"Dickert_MAHS", 400, {0, 0}, {0, 0}, {0, 0}, 36, false, DECODE, + rf_decode_dickert_mahs, rf_encode_dickert_mahs}, + {"Revers_RB2", 250, {0, 0}, {0, 0}, {0, 0}, 64, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_revers_rb2, nullptr}, + {"MegaCode", 1000, {0, 0}, {0, 0}, {0, 0}, 24, false, DECODE | RF_PF_FIXED_LEN, + rf_decode_megacode, rf_encode_megacode}, }; #undef DECODE diff --git a/src/modules/rf/rf_scan.cpp b/src/modules/rf/rf_scan.cpp index d363018f32..549b55975e 100644 --- a/src/modules/rf/rf_scan.cpp +++ b/src/modules/rf/rf_scan.cpp @@ -694,10 +694,11 @@ bool rfSaveSignal(float frequency, RfCodes codes, bool raw, char *key, bool auto "Protocol: " + (codes.protocol == "" ? String("RcSwitch") : rf_flipper_protocol_name(codes.protocol)) + "\n"; subfile_out += "Bit: " + String(codes.Bit) + "\n"; - // The Key (64-bit) is always written so the signal can be replayed; for - // KeeLoq the rolling-code fields are added as informative extras. + // The Key (64-bit) is always written so the signal can be replayed; when + // KeeLoq or a rolling-code protocol is detected the extra fields are also + // included so the encoder can reconstruct the frame on re-transmit. subfile_out += "Key: " + String(key) + "\n"; - if (codes.hop != 0) { + if (codes.hop != 0 || codes.serial != 0 || codes.cnt != 0) { char hexString[64] = {0}; decimalToHexString(codes.serial, hexString); if (codes.seed != 0) { @@ -875,12 +876,13 @@ String rfReceiveSignal(float frequency, int max_loops, bool raw, bool headless) if (!outRaw) { subfile_out += "Preset: " + String(received.preset) + "\n"; subfile_out += - "Protocol: " + String(received.protocol == "" ? "RcSwitch" : received.protocol) + "\n"; + "Protocol: " + + (received.protocol == "" ? String("RcSwitch") : rf_flipper_protocol_name(received.protocol)) + "\n"; subfile_out += "Bit: " + String(received.Bit) + "\n"; subfile_out += "Key: " + String(hexString) + "\n"; - if (received.fix != 0) { // KeeLoq: include the resolved rolling-code fields + if (received.hop != 0 || received.serial != 0 || received.cnt != 0) { char tmp[32] = {0}; - subfile_out += "Manufacture: " + received.mf_name + "\n"; + if (received.fix != 0) subfile_out += "Manufacture: " + received.mf_name + "\n"; decimalToHexString(received.serial, tmp); subfile_out += "Serial: " + String(tmp) + "\n"; subfile_out += "Button: " + String(received.btn) + "\n";