mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-05-13 23:13:05 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c32ee61a4f | |||
| 0995609391 | |||
| 29fef56be1 | |||
| 6a348dd304 | |||
| 32a96e580d | |||
| 54f03a39c2 | |||
| a55189e2a4 | |||
| 14d10c0794 | |||
| 27818ccb1f | |||
| 0ebf26eff4 |
+9
-1
@@ -81,4 +81,12 @@ node_modules/
|
|||||||
|
|
||||||
#companion app
|
#companion app
|
||||||
/companion
|
/companion
|
||||||
/Flipper-Android-App
|
/Flipper-Android-App
|
||||||
|
|
||||||
|
#WIP not ready to push protocols
|
||||||
|
|
||||||
|
lib/subghz/protocols/subghz_protocol_honda_pandora.c
|
||||||
|
lib/subghz/protocols/honda_rolling.c
|
||||||
|
lib/subghz/protocols/honda_rolling.h
|
||||||
|
lib/subghz/protocols/honda_pandora.c
|
||||||
|
lib/subghz/protocols/honda_pandora.h
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
|||||||
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
|
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
|
||||||
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
||||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||||
|
| Ford | Ford V1 | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
|
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
|
||||||
| Renault (old models) | Marelli | 433 MHz | AM | No | Yes | No|
|
| Renault (old models) | Marelli | 433 MHz | AM | No | Yes | No|
|
||||||
@@ -58,6 +59,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
|||||||
| Kia/Hyundai | KIA/HYU V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
| Kia/Hyundai | KIA/HYU V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||||
| Kia/Hyundai | KIA/HYU V5 | 433 MHz | FM | Yes | Yes | Yes |
|
| Kia/Hyundai | KIA/HYU V5 | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Kia/Hyundai | KIA/HYU V6 | 433 MHz | FM | Yes | Yes | Yes |
|
| Kia/Hyundai | KIA/HYU V6 | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
|
| Kia/Hyundai | KIA V7 | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||||
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
|
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
|
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
|
||||||
@@ -67,6 +69,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
|||||||
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
|
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
|
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
|
||||||
| Chrysler/Dodge/Jeep | FOBIK GQ43VT | 315/433 MHz | AM | Yes | Yes | No |
|
| Chrysler/Dodge/Jeep | FOBIK GQ43VT | 315/433 MHz | AM | Yes | Yes | No |
|
||||||
|
| Honda | Honda Static | 433 MHz | AM | Yes | Yes | No |
|
||||||
|
|
||||||
### Gate / Access Protocols
|
### Gate / Access Protocols
|
||||||
|
|
||||||
@@ -345,3 +348,18 @@ THIS SOFTWARE IS PROVIDED **"AS IS,"** WITHOUT ANY WARRANTIES OF ANY KIND, EXPRE
|
|||||||
IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
||||||
|
|
||||||
**ALL RISKS FROM THE USE OR PERFORMANCE OF THIS SOFTWARE REMAIN WITH THE USER.**
|
**ALL RISKS FROM THE USE OR PERFORMANCE OF THIS SOFTWARE REMAIN WITH THE USER.**
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Special thanks to everyone who contributes to this project (in alphabetical order):
|
||||||
|
|
||||||
|
- 47LeCoste
|
||||||
|
- D4c1
|
||||||
|
- D4rks1d3
|
||||||
|
- LTX74
|
||||||
|
- Leeroy
|
||||||
|
- lupettohf
|
||||||
|
- MMX
|
||||||
|
- RalphWiggum
|
||||||
|
- zero-mega
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/types.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#define FORD_PROTOCOL_V1_NAME "Ford V1"
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderFordV1 SubGhzProtocolDecoderFordV1;
|
||||||
|
|
||||||
|
extern const SubGhzProtocol ford_protocol_v1;
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_decoder_ford_v1_free(void* context);
|
||||||
|
void subghz_protocol_decoder_ford_v1_reset(void* context);
|
||||||
|
void subghz_protocol_decoder_ford_v1_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t subghz_protocol_decoder_ford_v1_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_ford_v1_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_encoder_ford_v1_free(void* context);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_encoder_ford_v1_stop(void* context);
|
||||||
|
LevelDuration subghz_protocol_encoder_ford_v1_yield(void* context);
|
||||||
|
extern const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder;
|
||||||
@@ -1,855 +0,0 @@
|
|||||||
#include "honda.h"
|
|
||||||
|
|
||||||
#include "../blocks/const.h"
|
|
||||||
#include "../blocks/decoder.h"
|
|
||||||
#include "../blocks/encoder.h"
|
|
||||||
#include "../blocks/generic.h"
|
|
||||||
#include "../blocks/math.h"
|
|
||||||
#include "../blocks/custom_btn_i.h"
|
|
||||||
|
|
||||||
#define TAG "SubGhzProtocolHonda"
|
|
||||||
|
|
||||||
static const SubGhzBlockConst subghz_protocol_honda_const = {
|
|
||||||
.te_short = HONDA_TE_SHORT,
|
|
||||||
.te_long = HONDA_TE_LONG,
|
|
||||||
.te_delta = HONDA_TE_DELTA,
|
|
||||||
.min_count_bit_for_found = HONDA_MIN_BITS,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Pandora rolling-code tables (extracted from firmware @ 0xEFDC)
|
|
||||||
* Five 16×16 nibble-substitution tables.
|
|
||||||
* ==========================================================================*/
|
|
||||||
static const uint8_t honda_table_a[16][16] = { HONDA_TABLE_A };
|
|
||||||
static const uint8_t honda_table_b[16][16] = { HONDA_TABLE_B };
|
|
||||||
static const uint8_t honda_table_c[16][16] = { HONDA_TABLE_C };
|
|
||||||
static const uint8_t honda_table_d[16][16] = { HONDA_TABLE_D };
|
|
||||||
static const uint8_t honda_table_e[16][16] __attribute__((unused)) = { HONDA_TABLE_E };
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Bit-reverse helpers (mirrors Crypto.Util.Bit_Reverse_Byte @ 0x11AD4)
|
|
||||||
* ==========================================================================*/
|
|
||||||
static inline uint8_t _bit_rev8(uint8_t v) {
|
|
||||||
v = (uint8_t)(((v & 0xF0u) >> 4) | ((v & 0x0Fu) << 4));
|
|
||||||
v = (uint8_t)(((v & 0xCCu) >> 2) | ((v & 0x33u) << 2));
|
|
||||||
v = (uint8_t)(((v & 0xAAu) >> 1) | ((v & 0x55u) << 1));
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
static inline uint8_t _bit_rev4(uint8_t v) {
|
|
||||||
/* bit-reverse the low 4 bits only */
|
|
||||||
return (uint8_t)(_bit_rev8(v & 0x0Fu) >> 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* The 10-byte frame buffer has this layout (matching Pandora RAM):
|
|
||||||
* buf[0] — header byte (type_b_header<<4 | button) or (button<<4 | serial_hi)
|
|
||||||
* buf[1] — serial[23:16]
|
|
||||||
* buf[2] — serial[15:8]
|
|
||||||
* buf[3] — serial[7:0] / counter cascade
|
|
||||||
* buf[4] — {serial_low_nibble, counter[23:20]} (type-B layout)
|
|
||||||
* buf[5] — counter[19:12]
|
|
||||||
* buf[6] — counter[11:4]
|
|
||||||
* buf[7] — {mode_nibble, counter[3:0]} mode: 0x2 or 0xC
|
|
||||||
* buf[8] — checksum (nibble-substituted via tables on each TX)
|
|
||||||
* buf[9] — extra / padding
|
|
||||||
*
|
|
||||||
* counter bytes are buf[5], buf[6], buf[7] (with low nibble of buf[7] being
|
|
||||||
* the LSN of the counter and the high nibble being the mode indicator).
|
|
||||||
*
|
|
||||||
* The increment algorithm:
|
|
||||||
* 1. bit_rev-increment the low nibble of buf[3] (= counter LSN in Pandora)
|
|
||||||
* 2. On overflow, cascade to high nibble of buf[3] then to buf[2]
|
|
||||||
* 3. Dispatch on mode nibble (buf[7]>>4):
|
|
||||||
* 0x2 → use TABLE_A/TABLE_D for checksum nibble substitution
|
|
||||||
* 0xC → use TABLE_B/TABLE_D for checksum nibble substitution, flip mode→0x2
|
|
||||||
* (other branches flip mode and recurse similarly)
|
|
||||||
*
|
|
||||||
* For the Flipper port:
|
|
||||||
* plain +1 on the 24-bit counter with nibble-reversed carry, then re-compute
|
|
||||||
* the checksum using the appropriate table pair based on the mode nibble.
|
|
||||||
* ==========================================================================*/
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool type_b;
|
|
||||||
uint8_t type_b_header;
|
|
||||||
uint8_t button;
|
|
||||||
uint32_t serial;
|
|
||||||
uint32_t counter;
|
|
||||||
uint8_t checksum;
|
|
||||||
uint8_t mode; /* high nibble of buf[7]: 0x2 or 0xC */
|
|
||||||
} HondaFrameData;
|
|
||||||
|
|
||||||
/* Build the 10-byte Pandora buffer from a HondaFrameData */
|
|
||||||
static void _honda_to_buf(const HondaFrameData* f, uint8_t buf[10]) {
|
|
||||||
buf[9] = 0x00;
|
|
||||||
if(!f->type_b) {
|
|
||||||
buf[0] = (uint8_t)((f->button << 4) | ((f->serial >> 24) & 0x0Fu));
|
|
||||||
buf[1] = (uint8_t)((f->serial >> 16) & 0xFFu);
|
|
||||||
buf[2] = (uint8_t)((f->serial >> 8) & 0xFFu);
|
|
||||||
buf[3] = (uint8_t)( f->serial & 0xFFu);
|
|
||||||
buf[4] = (uint8_t)((f->counter >> 16) & 0xFFu);
|
|
||||||
buf[5] = (uint8_t)((f->counter >> 8) & 0xFFu);
|
|
||||||
buf[6] = (uint8_t)( f->counter & 0xFFu);
|
|
||||||
/* buf[7]: mode nibble high | counter LSN low — for Type-A counter is in buf[4..6] */
|
|
||||||
buf[7] = (uint8_t)((f->mode & 0x0Fu) << 4);
|
|
||||||
buf[8] = f->checksum;
|
|
||||||
} else {
|
|
||||||
buf[0] = (uint8_t)((f->type_b_header << 4) | (f->button & 0x0Fu));
|
|
||||||
buf[1] = (uint8_t)((f->serial >> 20) & 0xFFu);
|
|
||||||
buf[2] = (uint8_t)((f->serial >> 12) & 0xFFu);
|
|
||||||
buf[3] = (uint8_t)((f->serial >> 4) & 0xFFu);
|
|
||||||
buf[4] = (uint8_t)(((f->serial & 0x0Fu) << 4) | ((f->counter >> 20) & 0x0Fu));
|
|
||||||
buf[5] = (uint8_t)((f->counter >> 12) & 0xFFu);
|
|
||||||
buf[6] = (uint8_t)((f->counter >> 4) & 0xFFu);
|
|
||||||
buf[7] = (uint8_t)(((f->mode & 0x0Fu) << 4) | (f->counter & 0x0Fu));
|
|
||||||
buf[8] = f->checksum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Uses TABLE_A (or B) for the low nibble and TABLE_D (or A/B high) for the
|
|
||||||
* high nibble of buf[8], indexed by bit-reversed nibbles of buf[3] (counter
|
|
||||||
* cascade byte) as per the decompilation. */
|
|
||||||
static uint8_t _honda_rolling_checksum(const uint8_t buf[10], bool mode_is_c) {
|
|
||||||
uint8_t cnt_byte = buf[3]; /* the cascade/index byte Pandora uses */
|
|
||||||
uint8_t prev_csum = buf[8];
|
|
||||||
|
|
||||||
/* Choose table pair based on mode (mirrors Pandora's dispatch on buf[7]>>4) */
|
|
||||||
const uint8_t (*tbl_lo)[16] = mode_is_c ? honda_table_b : honda_table_a;
|
|
||||||
const uint8_t (*tbl_hi)[16] = honda_table_d;
|
|
||||||
const uint8_t (*tbl_perm)[16] = honda_table_c;
|
|
||||||
|
|
||||||
uint8_t new_lo = prev_csum & 0x0Fu;
|
|
||||||
uint8_t new_hi = (prev_csum >> 4) & 0x0Fu;
|
|
||||||
|
|
||||||
uint8_t idx = _bit_rev8(cnt_byte) & 0x0Fu;
|
|
||||||
|
|
||||||
/* Low nibble substitution (mirrors inner loop in Pandora decompile) */
|
|
||||||
for(uint8_t row = 0; row < 16; row++) {
|
|
||||||
if(tbl_lo[row][idx] == (prev_csum & 0x0Fu)) {
|
|
||||||
new_lo = tbl_perm[row][idx];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* High nibble substitution */
|
|
||||||
uint8_t idx_hi = _bit_rev8(cnt_byte >> 4) & 0x0Fu;
|
|
||||||
for(uint8_t row = 0; row < 16; row++) {
|
|
||||||
if(tbl_hi[row][idx_hi] == ((prev_csum >> 4) & 0x0Fu)) {
|
|
||||||
new_hi = tbl_perm[row][idx_hi];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (uint8_t)((new_hi << 4) | new_lo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Advance counter by 1 using Pandora's bit-reversed nibble arithmetic.
|
|
||||||
* counter increment section @ 0xEFF0-0xF090 */
|
|
||||||
static void _honda_counter_increment(HondaFrameData* f) {
|
|
||||||
uint8_t buf[10];
|
|
||||||
_honda_to_buf(f, buf);
|
|
||||||
|
|
||||||
/* Pandora increments buf[3] low nibble with bit-reverse carry */
|
|
||||||
uint8_t lo = _bit_rev4(buf[3] & 0x0Fu);
|
|
||||||
lo = (lo + 1) & 0x0Fu;
|
|
||||||
buf[3] = (buf[3] & 0xF0u) | _bit_rev4(lo);
|
|
||||||
|
|
||||||
/* Carry to high nibble of buf[3] when low overflows (was 0xF) */
|
|
||||||
if((f->counter & 0x0Fu) == 0x0Fu) {
|
|
||||||
uint8_t hi = _bit_rev4((buf[3] >> 4) & 0x0Fu);
|
|
||||||
hi = (hi + 1) & 0x0Fu;
|
|
||||||
buf[3] = (buf[3] & 0x0Fu) | (uint8_t)(_bit_rev4(hi) << 4);
|
|
||||||
|
|
||||||
/* Carry to buf[2] */
|
|
||||||
if(((f->counter >> 4) & 0x0Fu) == 0x0Fu) {
|
|
||||||
uint8_t b2lo = _bit_rev4(buf[2] & 0x0Fu);
|
|
||||||
b2lo = (b2lo + 1) & 0x0Fu;
|
|
||||||
buf[2] = (buf[2] & 0xF0u) | _bit_rev4(b2lo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Plain counter +1 */
|
|
||||||
f->counter = (f->counter + 1u) & 0x00FFFFFFu;
|
|
||||||
|
|
||||||
/* Mode flip: 0x2 ↔ 0xC (Pandora flips mode nibble each TX cycle) */
|
|
||||||
bool mode_was_c = (f->mode == 0xCu);
|
|
||||||
f->mode = mode_was_c ? 0x2u : 0xCu;
|
|
||||||
|
|
||||||
/* Recompute checksum using Pandora's table lookup */
|
|
||||||
_honda_to_buf(f, buf);
|
|
||||||
f->checksum = _honda_rolling_checksum(buf, !mode_was_c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Simple XOR checksum (Type-A static, used for initial decode validation)
|
|
||||||
* ==========================================================================*/
|
|
||||||
static uint8_t _honda_xor_checksum(const uint8_t* data) {
|
|
||||||
uint8_t c = 0;
|
|
||||||
for(uint8_t i = 0; i < 7; i++) c ^= data[i];
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Bit helpers
|
|
||||||
* ==========================================================================*/
|
|
||||||
static uint32_t _bits_get(const uint8_t* data, uint8_t start, uint8_t len) {
|
|
||||||
uint32_t val = 0;
|
|
||||||
for(uint8_t i = 0; i < len; i++) {
|
|
||||||
uint8_t byte_idx = (uint8_t)((start + i) / 8u);
|
|
||||||
uint8_t bit_idx = (uint8_t)(7u - ((start + i) % 8u));
|
|
||||||
val = (val << 1) | ((data[byte_idx] >> bit_idx) & 1u);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _bits_set(uint8_t* data, uint8_t start, uint8_t len, uint32_t val) {
|
|
||||||
if(!len) return;
|
|
||||||
for(int8_t i = (int8_t)len - 1; i >= 0; i--) {
|
|
||||||
uint8_t pos = (uint8_t)(start + (uint8_t)i);
|
|
||||||
uint8_t byte_idx = (uint8_t)(pos / 8u);
|
|
||||||
uint8_t bit_idx = (uint8_t)(7u - (pos % 8u));
|
|
||||||
if(val & 1u)
|
|
||||||
data[byte_idx] |= (uint8_t)(1u << bit_idx);
|
|
||||||
else
|
|
||||||
data[byte_idx] &= (uint8_t)(~(1u << bit_idx));
|
|
||||||
val >>= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Pack / unpack
|
|
||||||
* ==========================================================================*/
|
|
||||||
static uint64_t _honda_pack(const HondaFrameData* f) {
|
|
||||||
uint8_t key[8] = {0};
|
|
||||||
key[0] = (uint8_t)(((f->type_b ? 1u : 0u) << 7) |
|
|
||||||
((f->type_b_header & 0x07u) << 4) | (f->button & 0x0Fu));
|
|
||||||
key[1] = (uint8_t)((f->serial >> 20) & 0xFFu);
|
|
||||||
key[2] = (uint8_t)((f->serial >> 12) & 0xFFu);
|
|
||||||
key[3] = (uint8_t)((f->serial >> 4) & 0xFFu);
|
|
||||||
key[4] = (uint8_t)((f->serial & 0x0Fu) << 4);
|
|
||||||
key[5] = (uint8_t)((f->counter >> 16) & 0xFFu);
|
|
||||||
key[6] = (uint8_t)((f->counter >> 8) & 0xFFu);
|
|
||||||
key[7] = (uint8_t)( f->counter & 0xFFu);
|
|
||||||
|
|
||||||
uint64_t out = 0;
|
|
||||||
for(int i = 0; i < 8; i++) out = (out << 8) | key[i];
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _honda_unpack(uint64_t raw, HondaFrameData* f) {
|
|
||||||
uint8_t key[8];
|
|
||||||
for(int i = 7; i >= 0; i--) {
|
|
||||||
key[i] = (uint8_t)(raw & 0xFFu);
|
|
||||||
raw >>= 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
f->type_b = (key[0] >> 7) & 0x01u;
|
|
||||||
f->type_b_header = (key[0] >> 4) & 0x07u;
|
|
||||||
f->button = key[0] & 0x0Fu;
|
|
||||||
f->serial = ((uint32_t)key[1] << 20) | ((uint32_t)key[2] << 12) |
|
|
||||||
((uint32_t)key[3] << 4) | ((uint32_t)(key[4] >> 4) & 0x0Fu);
|
|
||||||
f->counter = ((uint32_t)key[5] << 16) | ((uint32_t)key[6] << 8) | (uint32_t)key[7];
|
|
||||||
f->mode = 0x2u; /* default mode; will be set properly on decode */
|
|
||||||
|
|
||||||
/* Recompute XOR checksum */
|
|
||||||
uint8_t fb[8] = {0};
|
|
||||||
if(!f->type_b) {
|
|
||||||
fb[0] = (uint8_t)((f->button << 4) | ((f->serial >> 24) & 0x0Fu));
|
|
||||||
fb[1] = (uint8_t)((f->serial >> 16) & 0xFFu);
|
|
||||||
fb[2] = (uint8_t)((f->serial >> 8) & 0xFFu);
|
|
||||||
fb[3] = (uint8_t)( f->serial & 0xFFu);
|
|
||||||
fb[4] = (uint8_t)((f->counter >> 16) & 0xFFu);
|
|
||||||
fb[5] = (uint8_t)((f->counter >> 8) & 0xFFu);
|
|
||||||
fb[6] = (uint8_t)( f->counter & 0xFFu);
|
|
||||||
} else {
|
|
||||||
fb[0] = (uint8_t)((f->type_b_header << 4) | (f->button & 0x0Fu));
|
|
||||||
fb[1] = (uint8_t)((f->serial >> 20) & 0xFFu);
|
|
||||||
fb[2] = (uint8_t)((f->serial >> 12) & 0xFFu);
|
|
||||||
fb[3] = (uint8_t)((f->serial >> 4) & 0xFFu);
|
|
||||||
fb[4] = (uint8_t)(((f->serial & 0x0Fu) << 4) | ((f->counter >> 20) & 0x0Fu));
|
|
||||||
fb[5] = (uint8_t)((f->counter >> 12) & 0xFFu);
|
|
||||||
fb[6] = (uint8_t)((f->counter >> 4) & 0xFFu);
|
|
||||||
}
|
|
||||||
f->checksum = _honda_xor_checksum(fb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Decoder state
|
|
||||||
* ==========================================================================*/
|
|
||||||
#define HONDA_HALF_BIT_BUF 512u
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HondaDecoderStepReset = 0,
|
|
||||||
HondaDecoderStepAccumulate,
|
|
||||||
} HondaDecoderStep;
|
|
||||||
|
|
||||||
typedef struct SubGhzProtocolDecoderHonda {
|
|
||||||
SubGhzProtocolDecoderBase base;
|
|
||||||
SubGhzBlockDecoder decoder;
|
|
||||||
SubGhzBlockGeneric generic;
|
|
||||||
|
|
||||||
uint8_t half_bits[HONDA_HALF_BIT_BUF];
|
|
||||||
uint16_t hb_count;
|
|
||||||
uint16_t consecutive_clean;
|
|
||||||
|
|
||||||
HondaFrameData frame;
|
|
||||||
bool frame_valid;
|
|
||||||
} SubGhzProtocolDecoderHonda;
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Encoder state
|
|
||||||
* ==========================================================================*/
|
|
||||||
#define HONDA_ENC_BUF_SIZE 512u
|
|
||||||
|
|
||||||
typedef struct SubGhzProtocolEncoderHonda {
|
|
||||||
SubGhzProtocolEncoderBase base;
|
|
||||||
SubGhzProtocolBlockEncoder encoder;
|
|
||||||
SubGhzBlockGeneric generic;
|
|
||||||
|
|
||||||
HondaFrameData frame;
|
|
||||||
uint8_t active_button;
|
|
||||||
} SubGhzProtocolEncoderHonda;
|
|
||||||
|
|
||||||
const SubGhzProtocolDecoder subghz_protocol_honda_decoder;
|
|
||||||
const SubGhzProtocolEncoder subghz_protocol_honda_encoder;
|
|
||||||
const SubGhzProtocol subghz_protocol_honda;
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Duration classifier
|
|
||||||
* Pandora uses TE_SHORT=250us, TE_LONG=480us from Brand_Auto_Honda_TX
|
|
||||||
* ==========================================================================*/
|
|
||||||
static uint8_t _classify_duration(uint32_t abs_dur) {
|
|
||||||
if(abs_dur >= (HONDA_TE_SHORT - HONDA_TE_DELTA) &&
|
|
||||||
abs_dur <= (HONDA_TE_SHORT + HONDA_TE_DELTA)) return 1;
|
|
||||||
if(abs_dur >= (HONDA_TE_LONG - HONDA_TE_DELTA) &&
|
|
||||||
abs_dur <= (HONDA_TE_LONG + HONDA_TE_DELTA)) return 2;
|
|
||||||
if(abs_dur >= (HONDA_TE_SHORT - HONDA_TE_DELTA - 30u) &&
|
|
||||||
abs_dur <= (HONDA_TE_SHORT + HONDA_TE_DELTA + 30u)) return 1;
|
|
||||||
if(abs_dur >= (HONDA_TE_LONG - HONDA_TE_DELTA - 30u) &&
|
|
||||||
abs_dur <= (HONDA_TE_LONG + HONDA_TE_DELTA + 30u)) return 2;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Manchester decoder
|
|
||||||
* ==========================================================================*/
|
|
||||||
static bool _honda_try_decode_polarity(SubGhzProtocolDecoderHonda* inst, bool invert) {
|
|
||||||
uint8_t* hb = inst->half_bits;
|
|
||||||
uint16_t cnt = inst->hb_count;
|
|
||||||
|
|
||||||
int16_t best_preamble_end = -1;
|
|
||||||
uint16_t preamble_count = 0;
|
|
||||||
|
|
||||||
for(uint16_t i = 1; i < cnt; i++) {
|
|
||||||
if(hb[i] != hb[i - 1]) {
|
|
||||||
preamble_count++;
|
|
||||||
} else {
|
|
||||||
if(preamble_count >= HONDA_MIN_PREAMBLE_COUNT) {
|
|
||||||
best_preamble_end = (int16_t)i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
preamble_count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(best_preamble_end < 0 && preamble_count >= HONDA_MIN_PREAMBLE_COUNT)
|
|
||||||
return false; /* preamble only */
|
|
||||||
if(best_preamble_end < 0)
|
|
||||||
best_preamble_end = 0;
|
|
||||||
|
|
||||||
/* Skip same-level at sync */
|
|
||||||
uint16_t i = (uint16_t)best_preamble_end;
|
|
||||||
while(i + 1 < cnt && hb[i] == hb[i + 1]) i++;
|
|
||||||
|
|
||||||
/* Manchester decode */
|
|
||||||
uint8_t decoded[16] = {0};
|
|
||||||
uint8_t bit_count = 0;
|
|
||||||
|
|
||||||
while(i + 1 < cnt && bit_count < 128u) {
|
|
||||||
uint8_t h0 = hb[i];
|
|
||||||
uint8_t h1 = hb[i + 1];
|
|
||||||
if(h0 != h1) {
|
|
||||||
uint8_t bit_val;
|
|
||||||
if(!invert)
|
|
||||||
bit_val = (h0 == 1 && h1 == 0) ? 1u : 0u;
|
|
||||||
else
|
|
||||||
bit_val = (h0 == 0 && h1 == 1) ? 1u : 0u;
|
|
||||||
uint8_t byte_idx = bit_count / 8u;
|
|
||||||
uint8_t bit_idx = 7u - (bit_count % 8u);
|
|
||||||
if(bit_val)
|
|
||||||
decoded[byte_idx] |= (uint8_t)(1u << bit_idx);
|
|
||||||
else
|
|
||||||
decoded[byte_idx] &= (uint8_t)(~(1u << bit_idx));
|
|
||||||
bit_count++;
|
|
||||||
i += 2;
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(bit_count < HONDA_MIN_BITS) return false;
|
|
||||||
|
|
||||||
FURI_LOG_D(
|
|
||||||
TAG, "pol=%s bits=%u: %02X %02X %02X %02X %02X %02X %02X %02X",
|
|
||||||
invert ? "INV" : "NOR", bit_count,
|
|
||||||
decoded[0], decoded[1], decoded[2], decoded[3],
|
|
||||||
decoded[4], decoded[5], decoded[6], decoded[7]);
|
|
||||||
|
|
||||||
/* --- Type-A: [4b btn][28b serial][24b counter][8b csum] = 64 bits --- */
|
|
||||||
if(bit_count >= 64u) {
|
|
||||||
uint8_t btn = (uint8_t)_bits_get(decoded, 0, 4);
|
|
||||||
uint32_t serial = _bits_get(decoded, 4, 28);
|
|
||||||
uint32_t counter = _bits_get(decoded, 32, 24);
|
|
||||||
uint8_t csum = (uint8_t)_bits_get(decoded, 56, 8);
|
|
||||||
|
|
||||||
uint8_t xor_check = 0;
|
|
||||||
for(uint8_t b = 0; b < 7; b++) xor_check ^= decoded[b];
|
|
||||||
|
|
||||||
if(xor_check == csum ||
|
|
||||||
(btn <= HONDA_BTN_LOCK2PRESS && btn > 0 &&
|
|
||||||
serial != 0 && serial != 0xFFFFFFFu &&
|
|
||||||
__builtin_popcount(xor_check ^ csum) <= 4)) {
|
|
||||||
inst->frame.type_b = false;
|
|
||||||
inst->frame.type_b_header = 0;
|
|
||||||
inst->frame.button = btn;
|
|
||||||
inst->frame.serial = serial;
|
|
||||||
inst->frame.counter = counter;
|
|
||||||
inst->frame.checksum = csum;
|
|
||||||
inst->frame.mode = 0x2u;
|
|
||||||
inst->frame_valid = true;
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG, "DECODED TypeA pol=%s btn=%u ser=%07lX cnt=%06lX",
|
|
||||||
invert ? "INV" : "NOR",
|
|
||||||
btn, (unsigned long)serial, (unsigned long)counter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Type-B: [4b hdr][4b btn][28b serial][24b counter][8b csum] = 68 bits --- */
|
|
||||||
if(bit_count >= 68u) {
|
|
||||||
uint8_t hdr = (uint8_t)_bits_get(decoded, 0, 4);
|
|
||||||
uint8_t btn = (uint8_t)_bits_get(decoded, 4, 4);
|
|
||||||
uint32_t serial = _bits_get(decoded, 8, 28);
|
|
||||||
uint32_t counter = _bits_get(decoded, 36, 24);
|
|
||||||
uint8_t csum = (uint8_t)_bits_get(decoded, 60, 8);
|
|
||||||
|
|
||||||
uint8_t calc_csum_b = 0;
|
|
||||||
{
|
|
||||||
uint8_t fb[7] = {0};
|
|
||||||
fb[0] = (uint8_t)((hdr << 4) | (btn & 0x0Fu));
|
|
||||||
fb[1] = (uint8_t)((serial >> 20) & 0xFFu);
|
|
||||||
fb[2] = (uint8_t)((serial >> 12) & 0xFFu);
|
|
||||||
fb[3] = (uint8_t)((serial >> 4) & 0xFFu);
|
|
||||||
fb[4] = (uint8_t)(((serial & 0x0Fu) << 4) | ((counter >> 20) & 0x0Fu));
|
|
||||||
fb[5] = (uint8_t)((counter >> 12) & 0xFFu);
|
|
||||||
fb[6] = (uint8_t)((counter >> 4) & 0xFFu);
|
|
||||||
for(uint8_t _i = 0; _i < 7; _i++) calc_csum_b ^= fb[_i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(btn <= HONDA_BTN_LOCK2PRESS &&
|
|
||||||
(calc_csum_b == csum || __builtin_popcount(calc_csum_b ^ csum) <= 1)) {
|
|
||||||
inst->frame.type_b = true;
|
|
||||||
inst->frame.type_b_header = hdr;
|
|
||||||
inst->frame.button = btn;
|
|
||||||
inst->frame.serial = serial;
|
|
||||||
inst->frame.counter = counter;
|
|
||||||
inst->frame.checksum = csum;
|
|
||||||
inst->frame.mode = (uint8_t)((decoded[7] >> 4) & 0x0Fu);
|
|
||||||
inst->frame_valid = true;
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG, "DECODED TypeB pol=%s hdr=%u btn=%u ser=%07lX cnt=%06lX",
|
|
||||||
invert ? "INV" : "NOR",
|
|
||||||
hdr, btn, (unsigned long)serial, (unsigned long)counter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _honda_try_decode(SubGhzProtocolDecoderHonda* inst) {
|
|
||||||
if(inst->hb_count < 40u) return false;
|
|
||||||
if(_honda_try_decode_polarity(inst, true)) return true;
|
|
||||||
if(_honda_try_decode_polarity(inst, false)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Encoder — build Manchester upload buffer
|
|
||||||
* Uses Pandora timing: preamble 312 cycles × 250us, data bits 480/250us
|
|
||||||
* ==========================================================================*/
|
|
||||||
static void _honda_build_upload(SubGhzProtocolEncoderHonda* inst) {
|
|
||||||
LevelDuration* buf = inst->encoder.upload;
|
|
||||||
size_t idx = 0;
|
|
||||||
|
|
||||||
buf[idx++] = level_duration_make(false, HONDA_GUARD_TIME_US);
|
|
||||||
|
|
||||||
for(uint16_t p = 0; p < (uint16_t)(HONDA_MIN_PREAMBLE_COUNT * 2u); p++) {
|
|
||||||
buf[idx++] = level_duration_make((p & 1u) != 0u, HONDA_TE_SHORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t frame[9] = {0};
|
|
||||||
uint8_t btn = inst->active_button & 0x0Fu;
|
|
||||||
|
|
||||||
if(!inst->frame.type_b) {
|
|
||||||
_bits_set(frame, 0, 4, btn);
|
|
||||||
_bits_set(frame, 4, 28, inst->frame.serial);
|
|
||||||
_bits_set(frame, 32, 24, inst->frame.counter);
|
|
||||||
_bits_set(frame, 56, 8, _honda_xor_checksum(frame));
|
|
||||||
} else {
|
|
||||||
_bits_set(frame, 0, 4, inst->frame.type_b_header);
|
|
||||||
_bits_set(frame, 4, 4, btn);
|
|
||||||
_bits_set(frame, 8, 28, inst->frame.serial);
|
|
||||||
_bits_set(frame, 36, 24, inst->frame.counter);
|
|
||||||
|
|
||||||
uint8_t cs = 0;
|
|
||||||
for(uint8_t i = 0; i < 7; i++) cs ^= frame[i];
|
|
||||||
_bits_set(frame, 60, 8, cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t total_bits = inst->frame.type_b ?
|
|
||||||
(uint8_t)HONDA_FRAME_BITS_B : (uint8_t)HONDA_FRAME_BITS;
|
|
||||||
|
|
||||||
/* Manchester encode inverted: bit-1 = LOW/HIGH, bit-0 = HIGH/LOW, all at TE_SHORT */
|
|
||||||
for(uint8_t b = 0; b < total_bits; b++) {
|
|
||||||
uint8_t byte_idx = b / 8u;
|
|
||||||
uint8_t bit_idx = 7u - (b % 8u);
|
|
||||||
uint8_t bit = (frame[byte_idx] >> bit_idx) & 1u;
|
|
||||||
if(bit) {
|
|
||||||
/* bit 1: LOW then HIGH */
|
|
||||||
buf[idx++] = level_duration_make(false, HONDA_TE_SHORT);
|
|
||||||
buf[idx++] = level_duration_make(true, HONDA_TE_SHORT);
|
|
||||||
} else {
|
|
||||||
/* bit 0: HIGH then LOW */
|
|
||||||
buf[idx++] = level_duration_make(true, HONDA_TE_SHORT);
|
|
||||||
buf[idx++] = level_duration_make(false, HONDA_TE_SHORT);
|
|
||||||
}
|
|
||||||
furi_check(idx < HONDA_ENC_BUF_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[idx++] = level_duration_make(false, HONDA_GUARD_TIME_US);
|
|
||||||
|
|
||||||
inst->encoder.size_upload = idx;
|
|
||||||
inst->encoder.front = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Protocol tables
|
|
||||||
* ==========================================================================*/
|
|
||||||
const SubGhzProtocolDecoder subghz_protocol_honda_decoder = {
|
|
||||||
.alloc = subghz_protocol_decoder_honda_alloc,
|
|
||||||
.free = subghz_protocol_decoder_honda_free,
|
|
||||||
.feed = subghz_protocol_decoder_honda_feed,
|
|
||||||
.reset = subghz_protocol_decoder_honda_reset,
|
|
||||||
.get_hash_data = subghz_protocol_decoder_honda_get_hash_data,
|
|
||||||
.serialize = subghz_protocol_decoder_honda_serialize,
|
|
||||||
.deserialize = subghz_protocol_decoder_honda_deserialize,
|
|
||||||
.get_string = subghz_protocol_decoder_honda_get_string,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SubGhzProtocolEncoder subghz_protocol_honda_encoder = {
|
|
||||||
.alloc = subghz_protocol_encoder_honda_alloc,
|
|
||||||
.free = subghz_protocol_encoder_honda_free,
|
|
||||||
.deserialize = subghz_protocol_encoder_honda_deserialize,
|
|
||||||
.stop = subghz_protocol_encoder_honda_stop,
|
|
||||||
.yield = subghz_protocol_encoder_honda_yield,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SubGhzProtocol subghz_protocol_honda = {
|
|
||||||
.name = SUBGHZ_PROTOCOL_HONDA_NAME,
|
|
||||||
.type = SubGhzProtocolTypeDynamic,
|
|
||||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 |
|
|
||||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
|
||||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
|
||||||
.decoder = &subghz_protocol_honda_decoder,
|
|
||||||
.encoder = &subghz_protocol_honda_encoder,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Custom button helpers
|
|
||||||
* 1 → Lock (0x01)
|
|
||||||
* 2 → Unlock (0x02)
|
|
||||||
* 3 → Trunk (0x04)
|
|
||||||
* 4 → Panic (0x08)
|
|
||||||
* 5 → RStart (0x05)
|
|
||||||
* ==========================================================================*/
|
|
||||||
uint8_t subghz_protocol_honda_btn_to_custom(uint8_t btn) {
|
|
||||||
switch(btn) {
|
|
||||||
case HONDA_BTN_LOCK: return 1;
|
|
||||||
case HONDA_BTN_UNLOCK: return 2;
|
|
||||||
case HONDA_BTN_TRUNK: return 3;
|
|
||||||
case HONDA_BTN_PANIC: return 4;
|
|
||||||
case HONDA_BTN_RSTART: return 5;
|
|
||||||
default: return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t subghz_protocol_honda_custom_to_btn(uint8_t custom) {
|
|
||||||
switch(custom) {
|
|
||||||
case 1: return HONDA_BTN_LOCK;
|
|
||||||
case 2: return HONDA_BTN_UNLOCK;
|
|
||||||
case 3: return HONDA_BTN_TRUNK;
|
|
||||||
case 4: return HONDA_BTN_PANIC;
|
|
||||||
case 5: return HONDA_BTN_RSTART;
|
|
||||||
default: return HONDA_BTN_LOCK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Decoder
|
|
||||||
* ==========================================================================*/
|
|
||||||
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment) {
|
|
||||||
UNUSED(environment);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = malloc(sizeof(SubGhzProtocolDecoderHonda));
|
|
||||||
furi_check(inst);
|
|
||||||
memset(inst, 0, sizeof(SubGhzProtocolDecoderHonda));
|
|
||||||
inst->base.protocol = &subghz_protocol_honda;
|
|
||||||
inst->generic.protocol_name = inst->base.protocol->name;
|
|
||||||
inst->frame_valid = false;
|
|
||||||
FURI_LOG_I(TAG, "decoder allocated");
|
|
||||||
return inst;
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_decoder_honda_free(void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
free(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_decoder_honda_reset(void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = context;
|
|
||||||
inst->decoder.parser_step = HondaDecoderStepReset;
|
|
||||||
inst->decoder.te_last = 0;
|
|
||||||
inst->hb_count = 0;
|
|
||||||
inst->consecutive_clean = 0;
|
|
||||||
/* DO NOT clear frame/frame_valid — get_string needs them after reset */
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = context;
|
|
||||||
uint8_t lvl = level ? 1u : 0u;
|
|
||||||
uint8_t dur_class = _classify_duration(duration);
|
|
||||||
|
|
||||||
if(dur_class > 0) {
|
|
||||||
inst->consecutive_clean++;
|
|
||||||
if(dur_class == 1) {
|
|
||||||
if(inst->hb_count < HONDA_HALF_BIT_BUF)
|
|
||||||
inst->half_bits[inst->hb_count++] = lvl;
|
|
||||||
} else {
|
|
||||||
if(inst->hb_count + 2u <= HONDA_HALF_BIT_BUF) {
|
|
||||||
inst->half_bits[inst->hb_count++] = lvl;
|
|
||||||
inst->half_bits[inst->hb_count++] = lvl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(inst->hb_count >= (HONDA_MIN_PREAMBLE_COUNT + 16u)) {
|
|
||||||
if(_honda_try_decode(inst)) {
|
|
||||||
inst->generic.data = _honda_pack(&inst->frame);
|
|
||||||
inst->generic.data_count_bit = inst->frame.type_b ?
|
|
||||||
(uint8_t)HONDA_FRAME_BITS_B : (uint8_t)HONDA_FRAME_BITS;
|
|
||||||
inst->generic.serial = inst->frame.serial;
|
|
||||||
inst->generic.btn = inst->frame.button;
|
|
||||||
inst->generic.cnt = inst->frame.counter;
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG, "FRAME btn=%u ser=%07lX cnt=%06lX",
|
|
||||||
inst->frame.button,
|
|
||||||
(unsigned long)inst->frame.serial,
|
|
||||||
(unsigned long)inst->frame.counter);
|
|
||||||
|
|
||||||
uint8_t custom = subghz_protocol_honda_btn_to_custom(inst->frame.button);
|
|
||||||
if(subghz_custom_btn_get_original() == 0)
|
|
||||||
subghz_custom_btn_set_original(custom);
|
|
||||||
subghz_custom_btn_set_max(HONDA_CUSTOM_BTN_MAX);
|
|
||||||
|
|
||||||
if(inst->base.callback)
|
|
||||||
inst->base.callback(&inst->base, inst->base.context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inst->hb_count = 0;
|
|
||||||
inst->consecutive_clean = 0;
|
|
||||||
}
|
|
||||||
inst->decoder.te_last = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = context;
|
|
||||||
return (uint8_t)(inst->generic.data ^
|
|
||||||
(inst->generic.data >> 8) ^
|
|
||||||
(inst->generic.data >> 16) ^
|
|
||||||
(inst->generic.data >> 24) ^
|
|
||||||
(inst->generic.data >> 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
|
|
||||||
void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = context;
|
|
||||||
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
|
|
||||||
}
|
|
||||||
|
|
||||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
|
|
||||||
void* context, FlipperFormat* flipper_format) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = context;
|
|
||||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
|
||||||
&inst->generic, flipper_format,
|
|
||||||
subghz_protocol_honda_const.min_count_bit_for_found);
|
|
||||||
if(ret == SubGhzProtocolStatusOk) {
|
|
||||||
_honda_unpack(inst->generic.data, &inst->frame);
|
|
||||||
inst->frame_valid = true;
|
|
||||||
inst->generic.serial = inst->frame.serial;
|
|
||||||
inst->generic.btn = inst->frame.button;
|
|
||||||
inst->generic.cnt = inst->frame.counter;
|
|
||||||
|
|
||||||
uint8_t custom = subghz_protocol_honda_btn_to_custom(inst->frame.button);
|
|
||||||
if(subghz_custom_btn_get_original() == 0)
|
|
||||||
subghz_custom_btn_set_original(custom);
|
|
||||||
subghz_custom_btn_set_max(HONDA_CUSTOM_BTN_MAX);
|
|
||||||
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG, "deserialize: btn=%u ser=%07lX cnt=%06lX",
|
|
||||||
inst->frame.button,
|
|
||||||
(unsigned long)inst->frame.serial,
|
|
||||||
(unsigned long)inst->frame.counter);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolDecoderHonda* inst = context;
|
|
||||||
|
|
||||||
if(!inst->frame_valid && inst->generic.data != 0) {
|
|
||||||
_honda_unpack(inst->generic.data, &inst->frame);
|
|
||||||
inst->frame_valid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* btn_name;
|
|
||||||
switch(inst->frame.button) {
|
|
||||||
case HONDA_BTN_LOCK: btn_name = "Lock"; break;
|
|
||||||
case HONDA_BTN_UNLOCK: btn_name = "Unlock"; break;
|
|
||||||
case HONDA_BTN_TRUNK: btn_name = "Trunk/Hatch"; break;
|
|
||||||
case HONDA_BTN_PANIC: btn_name = "Panic"; break;
|
|
||||||
case HONDA_BTN_RSTART: btn_name = "Remote Start"; break;
|
|
||||||
case HONDA_BTN_LOCK2PRESS: btn_name = "Lock x2"; break;
|
|
||||||
default: btn_name = "Unknown"; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
furi_string_cat_printf(
|
|
||||||
output,
|
|
||||||
"%s %s %ubit\r\n"
|
|
||||||
"Btn:%s (0x%X)\r\n"
|
|
||||||
"Ser:%07lX\r\n"
|
|
||||||
"Cnt:%06lX Chk:%02X Mode:%X\r\n",
|
|
||||||
inst->generic.protocol_name,
|
|
||||||
inst->frame.type_b ? "TB" : "TA",
|
|
||||||
inst->generic.data_count_bit,
|
|
||||||
btn_name,
|
|
||||||
inst->frame.button,
|
|
||||||
(unsigned long)inst->frame.serial,
|
|
||||||
(unsigned long)inst->frame.counter,
|
|
||||||
inst->frame.checksum,
|
|
||||||
inst->frame.mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
* Encoder
|
|
||||||
* ==========================================================================*/
|
|
||||||
void* subghz_protocol_encoder_honda_alloc(SubGhzEnvironment* environment) {
|
|
||||||
UNUSED(environment);
|
|
||||||
SubGhzProtocolEncoderHonda* inst = malloc(sizeof(SubGhzProtocolEncoderHonda));
|
|
||||||
furi_check(inst);
|
|
||||||
memset(inst, 0, sizeof(SubGhzProtocolEncoderHonda));
|
|
||||||
inst->base.protocol = &subghz_protocol_honda;
|
|
||||||
inst->generic.protocol_name = inst->base.protocol->name;
|
|
||||||
inst->encoder.repeat = 3;
|
|
||||||
inst->encoder.size_upload = 0;
|
|
||||||
inst->encoder.upload = malloc(HONDA_ENC_BUF_SIZE * sizeof(LevelDuration));
|
|
||||||
furi_check(inst->encoder.upload);
|
|
||||||
inst->encoder.is_running = false;
|
|
||||||
inst->encoder.front = 0;
|
|
||||||
return inst;
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_encoder_honda_free(void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolEncoderHonda* inst = context;
|
|
||||||
free(inst->encoder.upload);
|
|
||||||
free(inst);
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_encoder_honda_stop(void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolEncoderHonda* inst = context;
|
|
||||||
inst->encoder.is_running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LevelDuration subghz_protocol_encoder_honda_yield(void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolEncoderHonda* inst = context;
|
|
||||||
if(inst->encoder.repeat == 0 || !inst->encoder.is_running) {
|
|
||||||
inst->encoder.is_running = false;
|
|
||||||
return level_duration_reset();
|
|
||||||
}
|
|
||||||
LevelDuration ret = inst->encoder.upload[inst->encoder.front];
|
|
||||||
if(++inst->encoder.front >= inst->encoder.size_upload) {
|
|
||||||
inst->encoder.repeat--;
|
|
||||||
inst->encoder.front = 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
SubGhzProtocolStatus subghz_protocol_encoder_honda_deserialize(
|
|
||||||
void* context, FlipperFormat* flipper_format) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolEncoderHonda* inst = context;
|
|
||||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&inst->generic, flipper_format);
|
|
||||||
if(ret != SubGhzProtocolStatusOk) return ret;
|
|
||||||
|
|
||||||
_honda_unpack(inst->generic.data, &inst->frame);
|
|
||||||
|
|
||||||
uint8_t custom = subghz_protocol_honda_btn_to_custom(inst->frame.button);
|
|
||||||
if(subghz_custom_btn_get_original() == 0)
|
|
||||||
subghz_custom_btn_set_original(custom);
|
|
||||||
subghz_custom_btn_set_max(HONDA_CUSTOM_BTN_MAX);
|
|
||||||
|
|
||||||
uint8_t active_custom = subghz_custom_btn_get();
|
|
||||||
inst->active_button = (active_custom == SUBGHZ_CUSTOM_BTN_OK)
|
|
||||||
? subghz_protocol_honda_custom_to_btn(subghz_custom_btn_get_original())
|
|
||||||
: subghz_protocol_honda_custom_to_btn(active_custom);
|
|
||||||
|
|
||||||
inst->frame.counter = (inst->frame.counter +
|
|
||||||
furi_hal_subghz_get_rolling_counter_mult()) & 0x00FFFFFFu;
|
|
||||||
_honda_counter_increment(&inst->frame);
|
|
||||||
|
|
||||||
inst->frame.button = inst->active_button;
|
|
||||||
|
|
||||||
inst->generic.data = _honda_pack(&inst->frame);
|
|
||||||
inst->generic.cnt = inst->frame.counter;
|
|
||||||
inst->generic.btn = inst->active_button;
|
|
||||||
|
|
||||||
flipper_format_rewind(flipper_format);
|
|
||||||
uint8_t key_data[8];
|
|
||||||
for(int i = 0; i < 8; i++)
|
|
||||||
key_data[i] = (uint8_t)(inst->generic.data >> (56 - i * 8));
|
|
||||||
flipper_format_update_hex(flipper_format, "Key", key_data, 8);
|
|
||||||
|
|
||||||
_honda_build_upload(inst);
|
|
||||||
inst->encoder.is_running = true;
|
|
||||||
return SubGhzProtocolStatusOk;
|
|
||||||
}
|
|
||||||
|
|
||||||
void subghz_protocol_encoder_honda_set_button(void* context, uint8_t btn) {
|
|
||||||
furi_assert(context);
|
|
||||||
SubGhzProtocolEncoderHonda* inst = context;
|
|
||||||
inst->active_button = btn & 0x0Fu;
|
|
||||||
inst->encoder.is_running = false;
|
|
||||||
_honda_counter_increment(&inst->frame);
|
|
||||||
inst->generic.data = _honda_pack(&inst->frame);
|
|
||||||
inst->generic.cnt = inst->frame.counter;
|
|
||||||
_honda_build_upload(inst);
|
|
||||||
inst->encoder.repeat = 3;
|
|
||||||
inst->encoder.is_running = true;
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <lib/subghz/protocols/base.h>
|
|
||||||
#include <lib/subghz/blocks/const.h>
|
|
||||||
#include <lib/subghz/blocks/decoder.h>
|
|
||||||
#include <lib/subghz/blocks/encoder.h>
|
|
||||||
#include <lib/subghz/blocks/generic.h>
|
|
||||||
#include <lib/subghz/blocks/math.h>
|
|
||||||
|
|
||||||
#define SUBGHZ_PROTOCOL_HONDA_NAME "Honda"
|
|
||||||
|
|
||||||
#define HONDA_TE_SHORT 63u
|
|
||||||
#define HONDA_TE_LONG 126u
|
|
||||||
#define HONDA_TE_DELTA 35u
|
|
||||||
#define HONDA_GUARD_TIME_US 700u
|
|
||||||
|
|
||||||
#define HONDA_MIN_PREAMBLE_COUNT 20u
|
|
||||||
#define HONDA_PREAMBLE_CYCLES 312u
|
|
||||||
|
|
||||||
#define HONDA_FRAME_BITS 64u
|
|
||||||
#define HONDA_FRAME_BITS_B 68u
|
|
||||||
#define HONDA_MIN_BITS HONDA_FRAME_BITS
|
|
||||||
|
|
||||||
#define HONDA_BTN_LOCK 0x01u
|
|
||||||
#define HONDA_BTN_UNLOCK 0x02u
|
|
||||||
#define HONDA_BTN_TRUNK 0x04u
|
|
||||||
#define HONDA_BTN_PANIC 0x08u
|
|
||||||
#define HONDA_BTN_RSTART 0x05u
|
|
||||||
#define HONDA_BTN_LOCK2PRESS 0x09u
|
|
||||||
|
|
||||||
#define HONDA_TABLE_A \
|
|
||||||
{0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D,0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09}, \
|
|
||||||
{0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07,0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03}, \
|
|
||||||
{0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00,0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04}, \
|
|
||||||
{0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A,0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E}, \
|
|
||||||
{0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B,0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F}, \
|
|
||||||
{0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01,0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05}, \
|
|
||||||
{0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06,0x0D,0x09,0x0F,0x0B,0x04,0x00,0x06,0x02}, \
|
|
||||||
{0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C,0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08}, \
|
|
||||||
{0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E,0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A}, \
|
|
||||||
{0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04,0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00}, \
|
|
||||||
{0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03,0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07}, \
|
|
||||||
{0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09,0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D}, \
|
|
||||||
{0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08,0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C}, \
|
|
||||||
{0x0D,0x09,0x0F,0x0B,0x09,0x00,0x06,0x02,0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06}, \
|
|
||||||
{0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05,0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01}, \
|
|
||||||
{0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F,0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B}
|
|
||||||
|
|
||||||
#define HONDA_TABLE_B \
|
|
||||||
{0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03,0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07}, \
|
|
||||||
{0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09,0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D}, \
|
|
||||||
{0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E,0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A}, \
|
|
||||||
{0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04,0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00}, \
|
|
||||||
{0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05,0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01}, \
|
|
||||||
{0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F,0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B}, \
|
|
||||||
{0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08,0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C}, \
|
|
||||||
{0x0D,0x09,0x0F,0x0B,0x09,0x00,0x06,0x02,0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06}, \
|
|
||||||
{0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00,0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04}, \
|
|
||||||
{0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A,0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E}, \
|
|
||||||
{0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D,0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09}, \
|
|
||||||
{0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07,0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03}, \
|
|
||||||
{0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06,0x0D,0x09,0x0F,0x0B,0x04,0x00,0x06,0x02}, \
|
|
||||||
{0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C,0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08}, \
|
|
||||||
{0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B,0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F}, \
|
|
||||||
{0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01,0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05}
|
|
||||||
|
|
||||||
#define HONDA_TABLE_C \
|
|
||||||
{0x02,0x08,0x0F,0x05,0x04,0x0E,0x09,0x03,0x01,0x0B,0x0C,0x06,0x07,0x0D,0x0A,0x00}, \
|
|
||||||
{0x0B,0x01,0x06,0x0C,0x0D,0x07,0x00,0x0A,0x08,0x02,0x05,0x0F,0x0E,0x04,0x03,0x09}, \
|
|
||||||
{0x06,0x0C,0x0B,0x01,0x00,0x0A,0x0D,0x07,0x05,0x0F,0x08,0x02,0x03,0x09,0x0E,0x04}, \
|
|
||||||
{0x0F,0x05,0x02,0x08,0x09,0x03,0x04,0x0E,0x0C,0x06,0x01,0x0B,0x0A,0x00,0x07,0x0D}, \
|
|
||||||
{0x08,0x02,0x05,0x0F,0x0E,0x04,0x03,0x09,0x0B,0x01,0x06,0x0C,0x0D,0x07,0x00,0x0A}, \
|
|
||||||
{0x01,0x0B,0x0C,0x06,0x07,0x0D,0x0A,0x00,0x02,0x08,0x0F,0x05,0x04,0x0E,0x09,0x03}, \
|
|
||||||
{0x0C,0x06,0x01,0x0B,0x0A,0x00,0x07,0x0D,0x0F,0x05,0x02,0x08,0x09,0x03,0x04,0x0E}, \
|
|
||||||
{0x05,0x0F,0x08,0x02,0x03,0x09,0x0E,0x04,0x06,0x0C,0x0B,0x01,0x00,0x0A,0x0D,0x07}, \
|
|
||||||
{0x09,0x03,0x04,0x0E,0x0F,0x05,0x02,0x08,0x0A,0x00,0x07,0x0D,0x0C,0x06,0x01,0x0B}, \
|
|
||||||
{0x00,0x0A,0x0D,0x07,0x06,0x0C,0x0B,0x01,0x03,0x09,0x0E,0x04,0x05,0x0F,0x08,0x02}, \
|
|
||||||
{0x0D,0x07,0x00,0x0A,0x0B,0x01,0x06,0x0C,0x0E,0x04,0x03,0x09,0x08,0x02,0x05,0x0F}, \
|
|
||||||
{0x04,0x0E,0x09,0x03,0x02,0x08,0x0F,0x05,0x07,0x0D,0x0A,0x00,0x01,0x0B,0x0C,0x06}, \
|
|
||||||
{0x03,0x09,0x0E,0x04,0x05,0x0F,0x08,0x02,0x00,0x0A,0x0D,0x07,0x06,0x0C,0x0B,0x01}, \
|
|
||||||
{0x0A,0x00,0x07,0x0D,0x0C,0x06,0x01,0x0B,0x09,0x03,0x04,0x0E,0x0F,0x05,0x02,0x08}, \
|
|
||||||
{0x07,0x0D,0x0A,0x00,0x01,0x0B,0x0C,0x06,0x04,0x0E,0x09,0x03,0x02,0x08,0x0F,0x05}, \
|
|
||||||
{0x0E,0x04,0x03,0x09,0x08,0x02,0x05,0x0F,0x0D,0x07,0x00,0x0A,0x0B,0x01,0x06,0x0C}
|
|
||||||
|
|
||||||
#define HONDA_TABLE_D \
|
|
||||||
{0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F,0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04}, \
|
|
||||||
{0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E,0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05}, \
|
|
||||||
{0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B,0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00}, \
|
|
||||||
{0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A,0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01}, \
|
|
||||||
{0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05,0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E}, \
|
|
||||||
{0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04,0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F}, \
|
|
||||||
{0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01,0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A}, \
|
|
||||||
{0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00,0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B}, \
|
|
||||||
{0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A,0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01}, \
|
|
||||||
{0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B,0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00}, \
|
|
||||||
{0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E,0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05}, \
|
|
||||||
{0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F,0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04}, \
|
|
||||||
{0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00,0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B}, \
|
|
||||||
{0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01,0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A}, \
|
|
||||||
{0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04,0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F}, \
|
|
||||||
{0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05,0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E}
|
|
||||||
|
|
||||||
#define HONDA_TABLE_E \
|
|
||||||
{0x01,0x00,0x05,0x04,0x0B,0x0A,0x0F,0x0E,0x04,0x05,0x00,0x01,0x0E,0x0F,0x0A,0x0B}, \
|
|
||||||
{0x0F,0x0E,0x0B,0x0A,0x05,0x04,0x01,0x00,0x0A,0x0B,0x0E,0x0F,0x00,0x01,0x04,0x05}, \
|
|
||||||
{0x0E,0x0F,0x0A,0x0B,0x04,0x05,0x00,0x01,0x0B,0x0A,0x0F,0x0E,0x01,0x00,0x05,0x04}, \
|
|
||||||
{0x00,0x01,0x04,0x05,0x0A,0x0B,0x0E,0x0F,0x05,0x04,0x01,0x00,0x0F,0x0E,0x0B,0x0A}, \
|
|
||||||
{0x02,0x03,0x06,0x07,0x08,0x09,0x0C,0x0D,0x07,0x06,0x03,0x02,0x0D,0x0C,0x09,0x08}, \
|
|
||||||
{0x0C,0x0D,0x08,0x09,0x06,0x07,0x02,0x03,0x09,0x08,0x0D,0x0C,0x03,0x02,0x07,0x06}, \
|
|
||||||
{0x0D,0x0C,0x09,0x08,0x07,0x06,0x03,0x02,0x08,0x09,0x0C,0x0D,0x02,0x03,0x06,0x07}, \
|
|
||||||
{0x03,0x02,0x07,0x06,0x09,0x08,0x0D,0x0C,0x06,0x07,0x02,0x03,0x0C,0x0D,0x08,0x09}, \
|
|
||||||
{0x04,0x05,0x00,0x01,0x0E,0x0F,0x0A,0x0B,0x01,0x00,0x05,0x04,0x0B,0x0A,0x0F,0x0E}, \
|
|
||||||
{0x0A,0x0B,0x0E,0x0F,0x00,0x01,0x04,0x05,0x0F,0x0E,0x0B,0x0A,0x05,0x04,0x01,0x00}, \
|
|
||||||
{0x0B,0x0A,0x0F,0x0E,0x01,0x00,0x05,0x04,0x0E,0x0F,0x0A,0x0B,0x04,0x05,0x00,0x01}, \
|
|
||||||
{0x05,0x04,0x01,0x00,0x0F,0x0E,0x0B,0x0A,0x00,0x01,0x04,0x05,0x0A,0x0B,0x0E,0x0F}, \
|
|
||||||
{0x07,0x06,0x03,0x02,0x0D,0x0C,0x09,0x08,0x02,0x03,0x06,0x07,0x08,0x09,0x0C,0x0D}, \
|
|
||||||
{0x09,0x08,0x0D,0x0C,0x03,0x02,0x07,0x06,0x0C,0x0D,0x08,0x09,0x06,0x07,0x02,0x03}, \
|
|
||||||
{0x08,0x09,0x0C,0x0D,0x02,0x0A,0x06,0x07,0x0D,0x0C,0x09,0x08,0x07,0x06,0x03,0x02}, \
|
|
||||||
{0x06,0x07,0x02,0x03,0x0C,0x0D,0x08,0x09,0x03,0x02,0x07,0x06,0x09,0x08,0x0D,0x0C}
|
|
||||||
|
|
||||||
#define HONDA_CC1101_PRESET_DATA \
|
|
||||||
0x02, 0x0D, \
|
|
||||||
0x0B, 0x06, \
|
|
||||||
0x08, 0x32, \
|
|
||||||
0x07, 0x04, \
|
|
||||||
0x14, 0x00, \
|
|
||||||
0x13, 0x02, \
|
|
||||||
0x12, 0x04, \
|
|
||||||
0x11, 0x36, \
|
|
||||||
0x10, 0x69, \
|
|
||||||
0x15, 0x32, \
|
|
||||||
0x18, 0x18, \
|
|
||||||
0x19, 0x16, \
|
|
||||||
0x1D, 0x91, \
|
|
||||||
0x1C, 0x00, \
|
|
||||||
0x1B, 0x07, \
|
|
||||||
0x20, 0xFB, \
|
|
||||||
0x22, 0x10, \
|
|
||||||
0x21, 0x56, \
|
|
||||||
0x00, 0x00, \
|
|
||||||
0xC0, 0x00
|
|
||||||
|
|
||||||
#define HONDA_CUSTOM_BTN_MAX 5
|
|
||||||
|
|
||||||
extern const SubGhzProtocolDecoder subghz_protocol_honda_decoder;
|
|
||||||
extern const SubGhzProtocolEncoder subghz_protocol_honda_encoder;
|
|
||||||
extern const SubGhzProtocol subghz_protocol_honda;
|
|
||||||
|
|
||||||
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment);
|
|
||||||
void subghz_protocol_decoder_honda_free(void* context);
|
|
||||||
void subghz_protocol_decoder_honda_reset(void* context);
|
|
||||||
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration);
|
|
||||||
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context);
|
|
||||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
|
|
||||||
void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset);
|
|
||||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
|
|
||||||
void* context, FlipperFormat* flipper_format);
|
|
||||||
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output);
|
|
||||||
|
|
||||||
void* subghz_protocol_encoder_honda_alloc(SubGhzEnvironment* environment);
|
|
||||||
void subghz_protocol_encoder_honda_free(void* context);
|
|
||||||
void subghz_protocol_encoder_honda_stop(void* context);
|
|
||||||
LevelDuration subghz_protocol_encoder_honda_yield(void* context);
|
|
||||||
SubGhzProtocolStatus subghz_protocol_encoder_honda_deserialize(
|
|
||||||
void* context, FlipperFormat* flipper_format);
|
|
||||||
void subghz_protocol_encoder_honda_set_button(void* context, uint8_t btn);
|
|
||||||
|
|
||||||
uint8_t subghz_protocol_honda_btn_to_custom(uint8_t btn);
|
|
||||||
uint8_t subghz_protocol_honda_custom_to_btn(uint8_t custom);
|
|
||||||
@@ -0,0 +1,796 @@
|
|||||||
|
#include "honda_static.h"
|
||||||
|
|
||||||
|
#define HONDA_STATIC_BIT_COUNT 64
|
||||||
|
#define HONDA_STATIC_MIN_SYMBOLS 36
|
||||||
|
#define HONDA_STATIC_SHORT_BASE_US 28
|
||||||
|
#define HONDA_STATIC_SHORT_SPAN_US 70
|
||||||
|
#define HONDA_STATIC_LONG_BASE_US 61
|
||||||
|
#define HONDA_STATIC_LONG_SPAN_US 130
|
||||||
|
#define HONDA_STATIC_SYNC_TIME_US 700
|
||||||
|
#define HONDA_STATIC_ELEMENT_TIME_US 63
|
||||||
|
#define HONDA_STATIC_UPLOAD_CAPACITY 512
|
||||||
|
#define HONDA_STATIC_SYMBOL_CAPACITY 512
|
||||||
|
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
|
||||||
|
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
|
||||||
|
|
||||||
|
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
|
||||||
|
static const char* const honda_static_button_names[9] = {
|
||||||
|
"LOCK",
|
||||||
|
"UNLOCK",
|
||||||
|
"UNKNOWN",
|
||||||
|
"TRUNK",
|
||||||
|
"REMOTE START",
|
||||||
|
"UNKNOWN",
|
||||||
|
"UNKNOWN",
|
||||||
|
"PANIC",
|
||||||
|
"LOCK x2",
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t button;
|
||||||
|
uint8_t _reserved_01[3];
|
||||||
|
uint32_t serial;
|
||||||
|
uint32_t counter;
|
||||||
|
uint8_t checksum;
|
||||||
|
uint8_t _reserved_0d[3];
|
||||||
|
} HondaStaticFields;
|
||||||
|
|
||||||
|
struct SubGhzProtocolDecoderHondaStatic {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
uint32_t _reserved_0c;
|
||||||
|
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
uint32_t _reserved_20;
|
||||||
|
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
|
||||||
|
uint16_t packet_bit_count;
|
||||||
|
uint8_t _reserved_5a;
|
||||||
|
uint8_t _reserved_5b;
|
||||||
|
|
||||||
|
uint8_t symbols[HONDA_STATIC_SYMBOL_CAPACITY];
|
||||||
|
uint16_t symbols_count;
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
uint8_t decoded_valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubGhzProtocolEncoderHondaStatic {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
uint8_t tx_button;
|
||||||
|
uint8_t _reserved_69[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||||
|
uint64_t value = 0;
|
||||||
|
for(size_t i = 0; i < 8; i++) {
|
||||||
|
value = (value << 8U) | bytes[i];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
|
||||||
|
for(size_t i = 0; i < 8; i++) {
|
||||||
|
bytes[7U - i] = (uint8_t)(value & 0xFFU);
|
||||||
|
value >>= 8U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < count; i++) {
|
||||||
|
const uint8_t bit_index = start + i;
|
||||||
|
const uint8_t byte = data[bit_index >> 3U];
|
||||||
|
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||||
|
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint8_t)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < count; i++) {
|
||||||
|
const uint8_t bit_index = start + i;
|
||||||
|
const uint8_t byte = data[bit_index >> 3U];
|
||||||
|
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||||
|
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
|
||||||
|
for(uint8_t i = 0; i < count; i++) {
|
||||||
|
const uint8_t bit_index = start + i;
|
||||||
|
const uint8_t byte_index = bit_index >> 3U;
|
||||||
|
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
|
||||||
|
const uint8_t mask = (uint8_t)(1U << shift);
|
||||||
|
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
|
||||||
|
|
||||||
|
if(bit) {
|
||||||
|
data[byte_index] |= mask;
|
||||||
|
} else {
|
||||||
|
data[byte_index] &= (uint8_t)~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_level_u8(bool level) {
|
||||||
|
return level ? 1U : 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_sym_u8(uint8_t stored) {
|
||||||
|
return stored ? 1U : 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_reverse_bits8(uint8_t value) {
|
||||||
|
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
|
||||||
|
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
|
||||||
|
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_is_valid_button(uint8_t button) {
|
||||||
|
if(button > 9U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((0x336U >> button) & 1U) != 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_is_valid_serial(uint32_t serial) {
|
||||||
|
return (serial != 0U) && (serial != 0x0FFFFFFFU);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
|
||||||
|
if(button < 2U) {
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
button -= 2U;
|
||||||
|
if(button <= 3U) {
|
||||||
|
return honda_static_encoder_button_map[button];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* honda_static_button_name(uint8_t button) {
|
||||||
|
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
|
||||||
|
return honda_static_button_names[button - 1U];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
|
||||||
|
const uint8_t canonical[7] = {
|
||||||
|
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
|
||||||
|
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
|
||||||
|
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
|
||||||
|
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
|
||||||
|
compact[5],
|
||||||
|
compact[6],
|
||||||
|
compact[7],
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
|
||||||
|
checksum ^= canonical[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
|
||||||
|
uint8_t compact[8];
|
||||||
|
honda_static_u64_to_bytes_be(key, compact);
|
||||||
|
|
||||||
|
memset(fields, 0, sizeof(*fields));
|
||||||
|
fields->button = compact[0] & 0x0FU;
|
||||||
|
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
|
||||||
|
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
|
||||||
|
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
|
||||||
|
(uint32_t)compact[7];
|
||||||
|
fields->checksum = honda_static_compact_bytes_checksum(compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
|
||||||
|
uint8_t compact[8];
|
||||||
|
|
||||||
|
compact[0] = fields->button & 0x0FU;
|
||||||
|
compact[1] = (uint8_t)(fields->serial >> 20U);
|
||||||
|
compact[2] = (uint8_t)(fields->serial >> 12U);
|
||||||
|
compact[3] = (uint8_t)(fields->serial >> 4U);
|
||||||
|
compact[4] = (uint8_t)(fields->serial << 4U);
|
||||||
|
compact[5] = (uint8_t)(fields->counter >> 16U);
|
||||||
|
compact[6] = (uint8_t)(fields->counter >> 8U);
|
||||||
|
compact[7] = (uint8_t)fields->counter;
|
||||||
|
|
||||||
|
return honda_static_bytes_to_u64_be(compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
|
||||||
|
memset(packet, 0, 8);
|
||||||
|
|
||||||
|
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
|
||||||
|
honda_static_set_bits(packet, 4, 28, fields->serial);
|
||||||
|
honda_static_set_bits(packet, 32, 24, fields->counter);
|
||||||
|
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
checksum ^= packet[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
honda_static_set_bits(packet, 56, 8, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||||
|
const uint8_t button = honda_static_get_bits(packet, 0, 4);
|
||||||
|
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
|
||||||
|
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
|
||||||
|
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
|
||||||
|
|
||||||
|
uint8_t checksum_calc = 0U;
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
checksum_calc ^= packet[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(checksum != checksum_calc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!honda_static_is_valid_button(button)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!honda_static_is_valid_serial(serial)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields->button = button;
|
||||||
|
fields->serial = serial;
|
||||||
|
fields->counter = counter;
|
||||||
|
fields->checksum = checksum;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||||
|
uint8_t reversed[9];
|
||||||
|
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
|
||||||
|
reversed[i] = honda_static_reverse_bits8(packet[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
|
||||||
|
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
|
||||||
|
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
|
||||||
|
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
checksum ^= reversed[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!honda_static_is_valid_button(button)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!honda_static_is_valid_serial(serial)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields->button = button;
|
||||||
|
fields->serial = serial;
|
||||||
|
fields->counter = counter;
|
||||||
|
fields->checksum = checksum;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_manchester_pack_64(
|
||||||
|
const uint8_t* symbols,
|
||||||
|
uint16_t count,
|
||||||
|
uint16_t start_pos,
|
||||||
|
bool inverted,
|
||||||
|
uint8_t packet[9],
|
||||||
|
uint16_t* out_bit_count) {
|
||||||
|
memset(packet, 0, 9);
|
||||||
|
|
||||||
|
uint16_t pos = start_pos;
|
||||||
|
uint16_t bit_count = 0U;
|
||||||
|
|
||||||
|
while((uint16_t)(pos + 1U) < count) {
|
||||||
|
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t a = honda_static_sym_u8(symbols[pos]);
|
||||||
|
const uint8_t b = honda_static_sym_u8(symbols[pos + 1U]);
|
||||||
|
|
||||||
|
if(a == b) {
|
||||||
|
pos++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bit = false;
|
||||||
|
if(inverted) {
|
||||||
|
bit = (a == 0U) && (b == 1U);
|
||||||
|
} else {
|
||||||
|
bit = (a == 1U) && (b == 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bit) {
|
||||||
|
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_count++;
|
||||||
|
pos += 2U;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(out_bit_count) {
|
||||||
|
*out_bit_count = bit_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bit_count >= HONDA_STATIC_BIT_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
|
||||||
|
const uint16_t count = instance->symbols_count;
|
||||||
|
const uint8_t* symbols = instance->symbols;
|
||||||
|
|
||||||
|
uint16_t index = 1U;
|
||||||
|
uint16_t transitions = 0U;
|
||||||
|
|
||||||
|
while(index < count) {
|
||||||
|
if(honda_static_sym_u8(symbols[index]) != honda_static_sym_u8(symbols[index - 1U])) {
|
||||||
|
transitions++;
|
||||||
|
} else {
|
||||||
|
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
transitions = 0U;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index >= count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(((uint16_t)(index + 1U) < count) &&
|
||||||
|
(honda_static_sym_u8(symbols[index]) == honda_static_sym_u8(symbols[index + 1U]))) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t data_start = index;
|
||||||
|
|
||||||
|
uint8_t packet[9] = {0};
|
||||||
|
uint16_t bit_count = 0U;
|
||||||
|
|
||||||
|
if(!honda_static_manchester_pack_64(symbols, count, data_start, inverted, packet, &bit_count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(honda_static_validate_forward_packet(packet, &instance->decoded)) {
|
||||||
|
instance->decoded_valid = 1U;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inverted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(honda_static_validate_reverse_packet(packet, &instance->decoded)) {
|
||||||
|
instance->decoded_valid = 1U;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_decoder_commit(SubGhzProtocolDecoderHondaStatic* instance) {
|
||||||
|
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
|
||||||
|
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||||
|
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||||
|
instance->generic.serial = instance->decoded.serial;
|
||||||
|
instance->generic.cnt = instance->decoded.counter;
|
||||||
|
instance->generic.btn = instance->decoded.button;
|
||||||
|
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
|
||||||
|
uint8_t packet[8];
|
||||||
|
honda_static_build_packet_bytes(&instance->decoded, packet);
|
||||||
|
|
||||||
|
size_t index = 0U;
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make((i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
|
||||||
|
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||||
|
|
||||||
|
instance->encoder.front = 0U;
|
||||||
|
instance->encoder.size_upload = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
|
||||||
|
FuriString* tmp = furi_string_alloc();
|
||||||
|
if(!tmp) return false;
|
||||||
|
bool ok = false;
|
||||||
|
do {
|
||||||
|
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
|
||||||
|
|
||||||
|
const char* key_str = furi_string_get_cstr(tmp);
|
||||||
|
uint64_t key = 0;
|
||||||
|
size_t hex_pos = 0;
|
||||||
|
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
|
||||||
|
char c = key_str[i];
|
||||||
|
if(c == ' ') continue;
|
||||||
|
uint8_t nibble;
|
||||||
|
if(c >= '0' && c <= '9')
|
||||||
|
nibble = c - '0';
|
||||||
|
else if(c >= 'A' && c <= 'F')
|
||||||
|
nibble = c - 'A' + 10;
|
||||||
|
else if(c >= 'a' && c <= 'f')
|
||||||
|
nibble = c - 'a' + 10;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
key = (key << 4) | nibble;
|
||||||
|
hex_pos++;
|
||||||
|
}
|
||||||
|
if(hex_pos != 16) break;
|
||||||
|
*out_key = key;
|
||||||
|
ok = true;
|
||||||
|
} while(false);
|
||||||
|
furi_string_free(tmp);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_honda_static_alloc,
|
||||||
|
.free = subghz_protocol_decoder_honda_static_free,
|
||||||
|
.feed = subghz_protocol_decoder_honda_static_feed,
|
||||||
|
.reset = subghz_protocol_decoder_honda_static_reset,
|
||||||
|
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_honda_static_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_honda_static_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
|
||||||
|
.alloc = subghz_protocol_encoder_honda_static_alloc,
|
||||||
|
.free = subghz_protocol_encoder_honda_static_free,
|
||||||
|
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
|
||||||
|
.stop = subghz_protocol_encoder_honda_static_stop,
|
||||||
|
.yield = subghz_protocol_encoder_honda_static_yield,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol honda_static_protocol = {
|
||||||
|
.name = HONDA_STATIC_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
|
||||||
|
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
|
||||||
|
SubGhzProtocolFlag_Send,
|
||||||
|
.decoder = &subghz_protocol_honda_static_decoder,
|
||||||
|
.encoder = &subghz_protocol_honda_static_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
|
||||||
|
furi_check(instance);
|
||||||
|
memset(instance, 0, sizeof(*instance));
|
||||||
|
|
||||||
|
instance->base.protocol = &honda_static_protocol;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.repeat = 3U;
|
||||||
|
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_honda_static_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
|
||||||
|
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
instance->encoder.front = 0U;
|
||||||
|
|
||||||
|
do {
|
||||||
|
FuriString* pstr = furi_string_alloc();
|
||||||
|
if(!pstr) break;
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
|
||||||
|
furi_string_free(pstr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
|
||||||
|
furi_string_free(pstr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
furi_string_free(pstr);
|
||||||
|
|
||||||
|
uint64_t key = 0;
|
||||||
|
if(!honda_static_read_hex_u64(flipper_format, &key)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
honda_static_unpack_compact(key, &instance->decoded);
|
||||||
|
|
||||||
|
uint32_t serial = instance->decoded.serial;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||||
|
instance->decoded.serial = serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t btn_u32 = 0;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||||
|
uint8_t b = (uint8_t)btn_u32;
|
||||||
|
if(honda_static_is_valid_button(b)) {
|
||||||
|
instance->decoded.button = b;
|
||||||
|
} else if(b >= 2U && b <= 5U) {
|
||||||
|
instance->decoded.button = honda_static_encoder_remap_button(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||||
|
instance->decoded.counter = cnt & 0x00FFFFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->generic.serial = instance->decoded.serial;
|
||||||
|
instance->generic.cnt = instance->decoded.counter;
|
||||||
|
instance->generic.btn = instance->decoded.button;
|
||||||
|
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||||
|
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||||
|
|
||||||
|
uint8_t key_data[8];
|
||||||
|
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||||
|
status = SubGhzProtocolStatusErrorParserKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_read_uint32(
|
||||||
|
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||||
|
instance->encoder.repeat = 3U;
|
||||||
|
}
|
||||||
|
|
||||||
|
honda_static_build_upload(instance);
|
||||||
|
instance->encoder.is_running = true;
|
||||||
|
status = SubGhzProtocolStatusOk;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_honda_static_stop(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
return level_duration_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
|
||||||
|
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||||
|
instance->encoder.repeat--;
|
||||||
|
instance->encoder.front = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
|
||||||
|
furi_check(instance);
|
||||||
|
memset(instance, 0, sizeof(*instance));
|
||||||
|
|
||||||
|
instance->base.protocol = &honda_static_protocol;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_reset(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
instance->symbols_count = 0U;
|
||||||
|
instance->decoded_valid = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
|
||||||
|
const uint8_t sym = honda_static_level_u8(level);
|
||||||
|
|
||||||
|
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
|
||||||
|
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
|
||||||
|
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||||
|
instance->symbols[instance->symbols_count++] = sym;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
|
||||||
|
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
|
||||||
|
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||||
|
instance->symbols[instance->symbols_count++] = sym;
|
||||||
|
instance->symbols[instance->symbols_count++] = sym;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t sc = instance->symbols_count;
|
||||||
|
|
||||||
|
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
|
||||||
|
if(honda_static_parse_symbols(instance, true) ||
|
||||||
|
honda_static_parse_symbols(instance, false)) {
|
||||||
|
honda_static_decoder_commit(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->symbols_count = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
const uint64_t data = instance->generic.data;
|
||||||
|
|
||||||
|
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
|
||||||
|
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
if(!instance->decoded_valid && (instance->generic.data != 0ULL)) {
|
||||||
|
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
|
||||||
|
instance->decoded_valid = 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %ubit\r\n"
|
||||||
|
"Key:%016llX\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->packet_bit_count ? instance->packet_bit_count : HONDA_STATIC_BIT_COUNT,
|
||||||
|
(unsigned long long)instance->generic.data);
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"Btn:%s (0x%X)\r\n"
|
||||||
|
"Ser:%07lX\r\n"
|
||||||
|
"Cnt:%06lX Chk:%02X\r\n",
|
||||||
|
honda_static_button_name(instance->decoded.button),
|
||||||
|
instance->decoded.button,
|
||||||
|
(unsigned long)instance->decoded.serial,
|
||||||
|
(unsigned long)instance->decoded.counter,
|
||||||
|
instance->decoded.checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||||
|
|
||||||
|
SubGhzProtocolStatus ret =
|
||||||
|
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
|
||||||
|
if(ret == SubGhzProtocolStatusOk) {
|
||||||
|
flipper_format_write_uint32(flipper_format, "Serial", &instance->decoded.serial, 1);
|
||||||
|
|
||||||
|
uint32_t temp = instance->decoded.button;
|
||||||
|
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
|
||||||
|
|
||||||
|
flipper_format_write_uint32(flipper_format, "Cnt", &instance->decoded.counter, 1);
|
||||||
|
|
||||||
|
temp = instance->decoded.checksum;
|
||||||
|
flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
|
||||||
|
if(status != SubGhzProtocolStatusOk) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
|
||||||
|
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
|
||||||
|
instance->decoded_valid = 1U;
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
uint32_t s = 0, b = 0, c = 0, k = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
|
||||||
|
instance->decoded.serial = s;
|
||||||
|
}
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
|
||||||
|
instance->decoded.button = (uint8_t)b;
|
||||||
|
}
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
|
||||||
|
instance->decoded.counter = c & 0x00FFFFFFU;
|
||||||
|
}
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Checksum", &k, 1)) {
|
||||||
|
instance->decoded.checksum = (uint8_t)k;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->generic.serial = instance->decoded.serial;
|
||||||
|
instance->generic.cnt = instance->decoded.counter;
|
||||||
|
instance->generic.btn = instance->decoded.button;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,811 @@
|
|||||||
|
#include "honda_static.h"
|
||||||
|
|
||||||
|
#define HONDA_STATIC_BIT_COUNT 64
|
||||||
|
#define HONDA_STATIC_MIN_SYMBOLS 36
|
||||||
|
#define HONDA_STATIC_SHORT_BASE_US 28
|
||||||
|
#define HONDA_STATIC_SHORT_SPAN_US 70
|
||||||
|
#define HONDA_STATIC_LONG_BASE_US 61
|
||||||
|
#define HONDA_STATIC_LONG_SPAN_US 130
|
||||||
|
#define HONDA_STATIC_SYNC_TIME_US 700
|
||||||
|
#define HONDA_STATIC_ELEMENT_TIME_US 63
|
||||||
|
#define HONDA_STATIC_UPLOAD_CAPACITY \
|
||||||
|
(1U + HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT + (2U * HONDA_STATIC_BIT_COUNT) + 1U)
|
||||||
|
#define HONDA_STATIC_SYMBOL_CAPACITY 512
|
||||||
|
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
|
||||||
|
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
|
||||||
|
#define HONDA_STATIC_SYMBOL_BYTE_COUNT ((HONDA_STATIC_SYMBOL_CAPACITY + 7U) / 8U)
|
||||||
|
|
||||||
|
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
|
||||||
|
static const char* const honda_static_button_names[9] = {
|
||||||
|
"Lock",
|
||||||
|
"Unlock",
|
||||||
|
"Unknown",
|
||||||
|
"Trunk",
|
||||||
|
"Remote Start",
|
||||||
|
"Unknown",
|
||||||
|
"Unknown",
|
||||||
|
"Panic",
|
||||||
|
"Lock x2",
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t button;
|
||||||
|
uint8_t _reserved_01[3];
|
||||||
|
uint32_t serial;
|
||||||
|
uint32_t counter;
|
||||||
|
uint8_t checksum;
|
||||||
|
uint8_t _reserved_0d[3];
|
||||||
|
} HondaStaticFields;
|
||||||
|
|
||||||
|
struct SubGhzProtocolDecoderHondaStatic {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
|
||||||
|
uint8_t symbols[HONDA_STATIC_SYMBOL_BYTE_COUNT];
|
||||||
|
uint16_t symbols_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubGhzProtocolEncoderHondaStatic {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
uint8_t tx_button;
|
||||||
|
uint8_t _reserved_69[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void honda_static_decoder_commit(
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance,
|
||||||
|
const HondaStaticFields* decoded);
|
||||||
|
|
||||||
|
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||||
|
uint64_t value = 0;
|
||||||
|
for(size_t i = 0; i < 8; i++) {
|
||||||
|
value = (value << 8U) | bytes[i];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
|
||||||
|
for(size_t i = 0; i < 8; i++) {
|
||||||
|
bytes[7U - i] = (uint8_t)(value & 0xFFU);
|
||||||
|
value >>= 8U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < count; i++) {
|
||||||
|
const uint8_t bit_index = start + i;
|
||||||
|
const uint8_t byte = data[bit_index >> 3U];
|
||||||
|
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||||
|
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint8_t)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < count; i++) {
|
||||||
|
const uint8_t bit_index = start + i;
|
||||||
|
const uint8_t byte = data[bit_index >> 3U];
|
||||||
|
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||||
|
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
|
||||||
|
for(uint8_t i = 0; i < count; i++) {
|
||||||
|
const uint8_t bit_index = start + i;
|
||||||
|
const uint8_t byte_index = bit_index >> 3U;
|
||||||
|
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
|
||||||
|
const uint8_t mask = (uint8_t)(1U << shift);
|
||||||
|
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
|
||||||
|
|
||||||
|
if(bit) {
|
||||||
|
data[byte_index] |= mask;
|
||||||
|
} else {
|
||||||
|
data[byte_index] &= (uint8_t)~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_level_u8(bool level) {
|
||||||
|
return level ? 1U : 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_symbol_set(uint8_t* buf, uint16_t index, uint8_t v) {
|
||||||
|
const uint8_t byte_index = (uint8_t)(index >> 3U);
|
||||||
|
const uint8_t shift = (uint8_t)(~index) & 0x07U;
|
||||||
|
const uint8_t mask = (uint8_t)(1U << shift);
|
||||||
|
if(v) {
|
||||||
|
buf[byte_index] |= mask;
|
||||||
|
} else {
|
||||||
|
buf[byte_index] &= (uint8_t)~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_symbol_get(const uint8_t* buf, uint16_t index) {
|
||||||
|
const uint8_t byte_index = (uint8_t)(index >> 3U);
|
||||||
|
const uint8_t shift = (uint8_t)(~index) & 0x07U;
|
||||||
|
return (uint8_t)((buf[byte_index] >> shift) & 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_reverse_bits8(uint8_t value) {
|
||||||
|
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
|
||||||
|
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
|
||||||
|
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_is_valid_button(uint8_t button) {
|
||||||
|
if(button > 9U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((0x336U >> button) & 1U) != 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_is_valid_serial(uint32_t serial) {
|
||||||
|
return (serial != 0U) && (serial != 0x0FFFFFFFU);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
|
||||||
|
if(button < 2U) {
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
button -= 2U;
|
||||||
|
if(button <= 3U) {
|
||||||
|
return honda_static_encoder_button_map[button];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* honda_static_button_name(uint8_t button) {
|
||||||
|
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
|
||||||
|
return honda_static_button_names[button - 1U];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
|
||||||
|
const uint8_t canonical[7] = {
|
||||||
|
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
|
||||||
|
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
|
||||||
|
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
|
||||||
|
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
|
||||||
|
compact[5],
|
||||||
|
compact[6],
|
||||||
|
compact[7],
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
|
||||||
|
checksum ^= canonical[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
|
||||||
|
uint8_t compact[8];
|
||||||
|
honda_static_u64_to_bytes_be(key, compact);
|
||||||
|
|
||||||
|
memset(fields, 0, sizeof(*fields));
|
||||||
|
fields->button = compact[0] & 0x0FU;
|
||||||
|
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
|
||||||
|
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
|
||||||
|
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
|
||||||
|
(uint32_t)compact[7];
|
||||||
|
fields->checksum = honda_static_compact_bytes_checksum(compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
|
||||||
|
uint8_t compact[8];
|
||||||
|
|
||||||
|
compact[0] = fields->button & 0x0FU;
|
||||||
|
compact[1] = (uint8_t)(fields->serial >> 20U);
|
||||||
|
compact[2] = (uint8_t)(fields->serial >> 12U);
|
||||||
|
compact[3] = (uint8_t)(fields->serial >> 4U);
|
||||||
|
compact[4] = (uint8_t)(fields->serial << 4U);
|
||||||
|
compact[5] = (uint8_t)(fields->counter >> 16U);
|
||||||
|
compact[6] = (uint8_t)(fields->counter >> 8U);
|
||||||
|
compact[7] = (uint8_t)fields->counter;
|
||||||
|
|
||||||
|
return honda_static_bytes_to_u64_be(compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
|
||||||
|
memset(packet, 0, 8);
|
||||||
|
|
||||||
|
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
|
||||||
|
honda_static_set_bits(packet, 4, 28, fields->serial);
|
||||||
|
honda_static_set_bits(packet, 32, 24, fields->counter);
|
||||||
|
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
checksum ^= packet[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
honda_static_set_bits(packet, 56, 8, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||||
|
const uint8_t button = honda_static_get_bits(packet, 0, 4);
|
||||||
|
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
|
||||||
|
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
|
||||||
|
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
|
||||||
|
|
||||||
|
uint8_t checksum_calc = 0U;
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
checksum_calc ^= packet[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(checksum != checksum_calc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!honda_static_is_valid_button(button)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!honda_static_is_valid_serial(serial)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields->button = button;
|
||||||
|
fields->serial = serial;
|
||||||
|
fields->counter = counter;
|
||||||
|
fields->checksum = checksum;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||||
|
uint8_t reversed[9];
|
||||||
|
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
|
||||||
|
reversed[i] = honda_static_reverse_bits8(packet[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
|
||||||
|
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
|
||||||
|
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
|
||||||
|
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
checksum ^= reversed[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!honda_static_is_valid_button(button)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!honda_static_is_valid_serial(serial)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields->button = button;
|
||||||
|
fields->serial = serial;
|
||||||
|
fields->counter = counter;
|
||||||
|
fields->checksum = checksum;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_manchester_pack_64(
|
||||||
|
const uint8_t* symbol_bits,
|
||||||
|
uint16_t count,
|
||||||
|
uint16_t start_pos,
|
||||||
|
bool inverted,
|
||||||
|
uint8_t packet[9],
|
||||||
|
uint16_t* out_bit_count) {
|
||||||
|
memset(packet, 0, 9);
|
||||||
|
|
||||||
|
uint16_t pos = start_pos;
|
||||||
|
uint16_t bit_count = 0U;
|
||||||
|
|
||||||
|
while((uint16_t)(pos + 1U) < count) {
|
||||||
|
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t a = honda_static_symbol_get(symbol_bits, pos);
|
||||||
|
const uint8_t b = honda_static_symbol_get(symbol_bits, pos + 1U);
|
||||||
|
|
||||||
|
if(a == b) {
|
||||||
|
pos++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bit = false;
|
||||||
|
if(inverted) {
|
||||||
|
bit = (a == 0U) && (b == 1U);
|
||||||
|
} else {
|
||||||
|
bit = (a == 1U) && (b == 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bit) {
|
||||||
|
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_count++;
|
||||||
|
pos += 2U;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(out_bit_count) {
|
||||||
|
*out_bit_count = bit_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bit_count >= HONDA_STATIC_BIT_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
|
||||||
|
const uint16_t count = instance->symbols_count;
|
||||||
|
const uint8_t* symbol_bits = instance->symbols;
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
|
||||||
|
uint16_t index = 1U;
|
||||||
|
uint16_t transitions = 0U;
|
||||||
|
|
||||||
|
while(index < count) {
|
||||||
|
if(honda_static_symbol_get(symbol_bits, index) !=
|
||||||
|
honda_static_symbol_get(symbol_bits, index - 1U)) {
|
||||||
|
transitions++;
|
||||||
|
} else {
|
||||||
|
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
transitions = 0U;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index >= count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(((uint16_t)(index + 1U) < count) && (honda_static_symbol_get(symbol_bits, index) ==
|
||||||
|
honda_static_symbol_get(symbol_bits, index + 1U))) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t data_start = index;
|
||||||
|
|
||||||
|
uint8_t packet[9] = {0};
|
||||||
|
uint16_t bit_count = 0U;
|
||||||
|
|
||||||
|
if(!honda_static_manchester_pack_64(
|
||||||
|
symbol_bits, count, data_start, inverted, packet, &bit_count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(honda_static_validate_forward_packet(packet, &decoded)) {
|
||||||
|
honda_static_decoder_commit(instance, &decoded);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inverted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(honda_static_validate_reverse_packet(packet, &decoded)) {
|
||||||
|
honda_static_decoder_commit(instance, &decoded);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_decoder_commit(
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance,
|
||||||
|
const HondaStaticFields* decoded) {
|
||||||
|
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||||
|
instance->generic.data = honda_static_pack_compact(decoded);
|
||||||
|
instance->generic.serial = decoded->serial;
|
||||||
|
instance->generic.cnt = decoded->counter;
|
||||||
|
instance->generic.btn = decoded->button;
|
||||||
|
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
|
||||||
|
uint8_t packet[8];
|
||||||
|
honda_static_build_packet_bytes(&instance->decoded, packet);
|
||||||
|
|
||||||
|
size_t index = 0U;
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make((i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
|
||||||
|
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||||
|
|
||||||
|
instance->encoder.front = 0U;
|
||||||
|
instance->encoder.size_upload = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
|
||||||
|
FuriString* tmp = furi_string_alloc();
|
||||||
|
if(!tmp) return false;
|
||||||
|
bool ok = false;
|
||||||
|
do {
|
||||||
|
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
|
||||||
|
|
||||||
|
const char* key_str = furi_string_get_cstr(tmp);
|
||||||
|
uint64_t key = 0;
|
||||||
|
size_t hex_pos = 0;
|
||||||
|
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
|
||||||
|
char c = key_str[i];
|
||||||
|
if(c == ' ') continue;
|
||||||
|
uint8_t nibble;
|
||||||
|
if(c >= '0' && c <= '9')
|
||||||
|
nibble = c - '0';
|
||||||
|
else if(c >= 'A' && c <= 'F')
|
||||||
|
nibble = c - 'A' + 10;
|
||||||
|
else if(c >= 'a' && c <= 'f')
|
||||||
|
nibble = c - 'a' + 10;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
key = (key << 4) | nibble;
|
||||||
|
hex_pos++;
|
||||||
|
}
|
||||||
|
if(hex_pos != 16) break;
|
||||||
|
*out_key = key;
|
||||||
|
ok = true;
|
||||||
|
} while(false);
|
||||||
|
furi_string_free(tmp);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_honda_static_alloc,
|
||||||
|
.free = subghz_protocol_decoder_honda_static_free,
|
||||||
|
.feed = subghz_protocol_decoder_honda_static_feed,
|
||||||
|
.reset = subghz_protocol_decoder_honda_static_reset,
|
||||||
|
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_honda_static_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_honda_static_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
|
||||||
|
.alloc = subghz_protocol_encoder_honda_static_alloc,
|
||||||
|
.free = subghz_protocol_encoder_honda_static_free,
|
||||||
|
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
|
||||||
|
.stop = subghz_protocol_encoder_honda_static_stop,
|
||||||
|
.yield = subghz_protocol_encoder_honda_static_yield,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol honda_static_protocol = {
|
||||||
|
.name = HONDA_STATIC_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
|
||||||
|
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
|
||||||
|
SubGhzProtocolFlag_Send,
|
||||||
|
.decoder = &subghz_protocol_honda_static_decoder,
|
||||||
|
.encoder = &subghz_protocol_honda_static_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
|
||||||
|
furi_check(instance);
|
||||||
|
memset(instance, 0, sizeof(*instance));
|
||||||
|
|
||||||
|
instance->base.protocol = &honda_static_protocol;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.repeat = 3U;
|
||||||
|
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_honda_static_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
|
||||||
|
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
instance->encoder.front = 0U;
|
||||||
|
|
||||||
|
do {
|
||||||
|
FuriString* pstr = furi_string_alloc();
|
||||||
|
if(!pstr) break;
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
|
||||||
|
furi_string_free(pstr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
|
||||||
|
furi_string_free(pstr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
furi_string_free(pstr);
|
||||||
|
|
||||||
|
uint64_t key = 0;
|
||||||
|
if(!honda_static_read_hex_u64(flipper_format, &key)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
honda_static_unpack_compact(key, &instance->decoded);
|
||||||
|
|
||||||
|
uint32_t serial = instance->decoded.serial;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||||
|
instance->decoded.serial = serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t btn_u32 = 0;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||||
|
uint8_t b = (uint8_t)btn_u32;
|
||||||
|
if(honda_static_is_valid_button(b)) {
|
||||||
|
instance->decoded.button = b;
|
||||||
|
} else if(b >= 2U && b <= 5U) {
|
||||||
|
instance->decoded.button = honda_static_encoder_remap_button(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||||
|
instance->decoded.counter = cnt & 0x00FFFFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->generic.serial = instance->decoded.serial;
|
||||||
|
instance->generic.cnt = instance->decoded.counter;
|
||||||
|
instance->generic.btn = instance->decoded.button;
|
||||||
|
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||||
|
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||||
|
|
||||||
|
uint8_t key_data[8];
|
||||||
|
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||||
|
status = SubGhzProtocolStatusErrorParserKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_read_uint32(
|
||||||
|
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||||
|
instance->encoder.repeat = 3U;
|
||||||
|
}
|
||||||
|
|
||||||
|
honda_static_build_upload(instance);
|
||||||
|
instance->encoder.is_running = true;
|
||||||
|
status = SubGhzProtocolStatusOk;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_honda_static_stop(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||||
|
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
return level_duration_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
|
||||||
|
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||||
|
instance->encoder.repeat--;
|
||||||
|
instance->encoder.front = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
|
||||||
|
furi_check(instance);
|
||||||
|
memset(instance, 0, sizeof(*instance));
|
||||||
|
|
||||||
|
instance->base.protocol = &honda_static_protocol;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_reset(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
instance->symbols_count = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
|
||||||
|
const uint8_t sym = honda_static_level_u8(level);
|
||||||
|
|
||||||
|
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
|
||||||
|
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
|
||||||
|
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||||
|
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||||
|
instance->symbols_count++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
|
||||||
|
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
|
||||||
|
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||||
|
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||||
|
instance->symbols_count++;
|
||||||
|
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||||
|
instance->symbols_count++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t sc = instance->symbols_count;
|
||||||
|
|
||||||
|
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
|
||||||
|
if(!honda_static_parse_symbols(instance, true)) {
|
||||||
|
honda_static_parse_symbols(instance, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->symbols_count = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
const uint64_t data = instance->generic.data;
|
||||||
|
|
||||||
|
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
|
||||||
|
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||||
|
|
||||||
|
furi_string_printf(
|
||||||
|
output,
|
||||||
|
"%s\r\n"
|
||||||
|
"Key:%016llX\r\n"
|
||||||
|
"Btn:%s\r\n"
|
||||||
|
"Ser:%07lX Cnt:%06lX",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
(unsigned long long)instance->generic.data,
|
||||||
|
honda_static_button_name(decoded.button),
|
||||||
|
(unsigned long)decoded.serial,
|
||||||
|
(unsigned long)decoded.counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||||
|
|
||||||
|
SubGhzProtocolStatus status =
|
||||||
|
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
if(status != SubGhzProtocolStatusOk) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t temp = decoded.serial;
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Serial", &temp, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = decoded.button;
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = decoded.counter;
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = decoded.checksum;
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||||
|
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
|
||||||
|
if(status != SubGhzProtocolStatusOk) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
HondaStaticFields decoded;
|
||||||
|
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||||
|
uint32_t s = 0;
|
||||||
|
uint32_t b = 0;
|
||||||
|
uint32_t c = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
|
||||||
|
decoded.serial = s;
|
||||||
|
}
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
|
||||||
|
decoded.button = (uint8_t)b;
|
||||||
|
}
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
|
||||||
|
decoded.counter = c & 0x00FFFFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->generic.data = honda_static_pack_compact(&decoded);
|
||||||
|
instance->generic.serial = decoded.serial;
|
||||||
|
instance->generic.cnt = decoded.counter;
|
||||||
|
instance->generic.btn = decoded.button;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/types.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
|
||||||
|
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
|
||||||
|
|
||||||
|
extern const SubGhzProtocol honda_static_protocol;
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_decoder_honda_static_free(void* context);
|
||||||
|
void subghz_protocol_decoder_honda_static_reset(void* context);
|
||||||
|
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_encoder_honda_static_free(void* context);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_encoder_honda_static_stop(void* context);
|
||||||
|
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/types.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
|
||||||
|
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
|
||||||
|
|
||||||
|
extern const SubGhzProtocol honda_static_protocol;
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_decoder_honda_static_free(void* context);
|
||||||
|
void subghz_protocol_decoder_honda_static_reset(void* context);
|
||||||
|
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_encoder_honda_static_free(void* context);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_encoder_honda_static_stop(void* context);
|
||||||
|
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
|
||||||
|
|
||||||
@@ -0,0 +1,711 @@
|
|||||||
|
#include "kia_v7.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define KIA_V7_UPLOAD_CAPACITY 0x3A4
|
||||||
|
#define KIA_V7_PREAMBLE_PAIRS 0x13F
|
||||||
|
#define KIA_V7_PREAMBLE_MIN_PAIRS 16
|
||||||
|
#define KIA_V7_HEADER 0x4C
|
||||||
|
#define KIA_V7_TAIL_GAP_US 0x7D0
|
||||||
|
#define KIA_V7_KEY_BITS 64U
|
||||||
|
#define KIA_V7_DEFAULT_TX_REPEAT 10U
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_kia_v7_const = {
|
||||||
|
.te_short = 250,
|
||||||
|
.te_long = 500,
|
||||||
|
.te_delta = 100,
|
||||||
|
.min_count_bit_for_found = KIA_V7_KEY_BITS,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
KiaV7DecoderStepReset = 0,
|
||||||
|
KiaV7DecoderStepPreamble = 1,
|
||||||
|
KiaV7DecoderStepSyncLow = 2,
|
||||||
|
KiaV7DecoderStepData = 3,
|
||||||
|
} KiaV7DecoderStep;
|
||||||
|
|
||||||
|
struct SubGhzProtocolDecoderKiaV7 {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
|
||||||
|
ManchesterState manchester_state;
|
||||||
|
uint16_t preamble_count;
|
||||||
|
|
||||||
|
uint8_t decoded_button;
|
||||||
|
uint8_t fixed_high_byte;
|
||||||
|
uint8_t crc_calculated;
|
||||||
|
uint8_t crc_raw;
|
||||||
|
bool crc_valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubGhzProtocolEncoderKiaV7 {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
|
||||||
|
uint8_t tx_bit_count;
|
||||||
|
uint8_t decoded_button;
|
||||||
|
uint8_t fixed_high_byte;
|
||||||
|
uint8_t crc_calculated;
|
||||||
|
uint8_t crc_raw;
|
||||||
|
bool crc_valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
|
||||||
|
uint8_t crc = 0x4CU;
|
||||||
|
|
||||||
|
for(size_t index = 0; index < len; index++) {
|
||||||
|
crc ^= data[index];
|
||||||
|
for(uint8_t bit = 0; bit < 8; bit++) {
|
||||||
|
const bool msb = (crc & 0x80U) != 0U;
|
||||||
|
crc <<= 1U;
|
||||||
|
if(msb) {
|
||||||
|
crc ^= 0x7FU;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kia_v7_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||||
|
for(size_t index = 0; index < 8; index++) {
|
||||||
|
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||||
|
uint64_t data = 0;
|
||||||
|
|
||||||
|
for(size_t index = 0; index < 8; index++) {
|
||||||
|
data = (data << 8U) | bytes[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool kia_v7_is_short(uint32_t duration) {
|
||||||
|
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_short) <
|
||||||
|
subghz_protocol_kia_v7_const.te_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool kia_v7_is_long(uint32_t duration) {
|
||||||
|
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_long) <
|
||||||
|
subghz_protocol_kia_v7_const.te_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* kia_v7_get_button_name(uint8_t button) {
|
||||||
|
switch(button) {
|
||||||
|
case 0x01:
|
||||||
|
return "LOCK";
|
||||||
|
case 0x02:
|
||||||
|
return "UNLOCK";
|
||||||
|
case 0x03:
|
||||||
|
case 0x08:
|
||||||
|
return "BOOT";
|
||||||
|
default:
|
||||||
|
return "??";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SubGhzProtocolStatus
|
||||||
|
kia_v7_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button) {
|
||||||
|
SubGhzProtocolStatus status = SubGhzProtocolStatusOk;
|
||||||
|
FuriString* display = furi_string_alloc();
|
||||||
|
|
||||||
|
furi_string_printf(display, "%s - %s", protocol_name, kia_v7_get_button_name(button));
|
||||||
|
|
||||||
|
if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) {
|
||||||
|
status = SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(display);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kia_v7_decode_key_common(
|
||||||
|
SubGhzBlockGeneric* generic,
|
||||||
|
uint8_t* decoded_button,
|
||||||
|
uint8_t* fixed_high_byte,
|
||||||
|
uint8_t* crc_calculated,
|
||||||
|
uint8_t* crc_raw,
|
||||||
|
bool* crc_valid) {
|
||||||
|
uint8_t bytes[8];
|
||||||
|
kia_v7_u64_to_bytes_be(generic->data, bytes);
|
||||||
|
|
||||||
|
const uint32_t serial = (((uint32_t)bytes[3]) << 20U) | (((uint32_t)bytes[4]) << 12U) |
|
||||||
|
(((uint32_t)bytes[5]) << 4U) | (((uint32_t)bytes[6]) >> 4U);
|
||||||
|
const uint16_t counter = ((uint16_t)bytes[1] << 8U) | (uint16_t)bytes[2];
|
||||||
|
const uint8_t button = bytes[6] & 0x0FU;
|
||||||
|
const uint8_t crc_calc = kia_v7_crc8(bytes, 7);
|
||||||
|
const uint8_t crc_pkt = bytes[7];
|
||||||
|
|
||||||
|
generic->serial = serial & 0x0FFFFFFFU;
|
||||||
|
generic->btn = button;
|
||||||
|
generic->cnt = counter;
|
||||||
|
generic->data_count_bit = KIA_V7_KEY_BITS;
|
||||||
|
|
||||||
|
if(decoded_button) {
|
||||||
|
*decoded_button = button;
|
||||||
|
}
|
||||||
|
if(fixed_high_byte) {
|
||||||
|
*fixed_high_byte = bytes[0];
|
||||||
|
}
|
||||||
|
if(crc_calculated) {
|
||||||
|
*crc_calculated = crc_calc;
|
||||||
|
}
|
||||||
|
if(crc_raw) {
|
||||||
|
*crc_raw = crc_pkt;
|
||||||
|
}
|
||||||
|
if(crc_valid) {
|
||||||
|
*crc_valid = (crc_calc == crc_pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kia_v7_decode_key_decoder(SubGhzProtocolDecoderKiaV7* instance) {
|
||||||
|
kia_v7_decode_key_common(
|
||||||
|
&instance->generic,
|
||||||
|
&instance->decoded_button,
|
||||||
|
&instance->fixed_high_byte,
|
||||||
|
&instance->crc_calculated,
|
||||||
|
&instance->crc_raw,
|
||||||
|
&instance->crc_valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t kia_v7_encode_key(
|
||||||
|
uint8_t fixed_high_byte,
|
||||||
|
uint32_t serial,
|
||||||
|
uint8_t button,
|
||||||
|
uint16_t counter,
|
||||||
|
uint8_t* crc_out) {
|
||||||
|
uint8_t bytes[8];
|
||||||
|
|
||||||
|
serial &= 0x0FFFFFFFU;
|
||||||
|
button &= 0x0FU;
|
||||||
|
|
||||||
|
bytes[0] = fixed_high_byte;
|
||||||
|
bytes[1] = (counter >> 8U) & 0xFFU;
|
||||||
|
bytes[2] = counter & 0xFFU;
|
||||||
|
bytes[3] = (serial >> 20U) & 0xFFU;
|
||||||
|
bytes[4] = (serial >> 12U) & 0xFFU;
|
||||||
|
bytes[5] = (serial >> 4U) & 0xFFU;
|
||||||
|
bytes[6] = ((serial & 0x0FU) << 4U) | button;
|
||||||
|
bytes[7] = kia_v7_crc8(bytes, 7);
|
||||||
|
|
||||||
|
if(crc_out) {
|
||||||
|
*crc_out = bytes[7];
|
||||||
|
}
|
||||||
|
|
||||||
|
return kia_v7_bytes_to_u64_be(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
|
||||||
|
kia_v7_decode_key_common(
|
||||||
|
&instance->generic,
|
||||||
|
&instance->decoded_button,
|
||||||
|
&instance->fixed_high_byte,
|
||||||
|
&instance->crc_calculated,
|
||||||
|
&instance->crc_raw,
|
||||||
|
&instance->crc_valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
|
||||||
|
furi_check(instance);
|
||||||
|
|
||||||
|
const LevelDuration high_short =
|
||||||
|
level_duration_make(true, subghz_protocol_kia_v7_const.te_short);
|
||||||
|
const LevelDuration low_short =
|
||||||
|
level_duration_make(false, subghz_protocol_kia_v7_const.te_short);
|
||||||
|
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
|
||||||
|
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
|
||||||
|
|
||||||
|
const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ?
|
||||||
|
instance->tx_bit_count :
|
||||||
|
64U;
|
||||||
|
|
||||||
|
size_t final_size = 0;
|
||||||
|
|
||||||
|
for(uint8_t pass = 0; pass < 2; pass++) {
|
||||||
|
size_t index = pass;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) {
|
||||||
|
if((index + 2U) > max_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.upload[index++] = high_short;
|
||||||
|
instance->encoder.upload[index++] = low_short;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((index + 1U) > max_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
instance->encoder.upload[index++] = high_short;
|
||||||
|
|
||||||
|
for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) {
|
||||||
|
if((index + 2U) > max_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL;
|
||||||
|
instance->encoder.upload[index++] = value ? high_short : low_short;
|
||||||
|
instance->encoder.upload[index++] = value ? low_short : high_short;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((index + 2U) > max_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
instance->encoder.upload[index++] = high_short;
|
||||||
|
instance->encoder.upload[index++] = low_tail;
|
||||||
|
|
||||||
|
final_size = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
instance->encoder.size_upload = final_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder = {
|
||||||
|
.alloc = kia_protocol_decoder_v7_alloc,
|
||||||
|
.free = kia_protocol_decoder_v7_free,
|
||||||
|
.feed = kia_protocol_decoder_v7_feed,
|
||||||
|
.reset = kia_protocol_decoder_v7_reset,
|
||||||
|
.get_hash_data = kia_protocol_decoder_v7_get_hash_data,
|
||||||
|
.serialize = kia_protocol_decoder_v7_serialize,
|
||||||
|
.deserialize = kia_protocol_decoder_v7_deserialize,
|
||||||
|
.get_string = kia_protocol_decoder_v7_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder = {
|
||||||
|
.alloc = kia_protocol_encoder_v7_alloc,
|
||||||
|
.free = kia_protocol_encoder_v7_free,
|
||||||
|
.deserialize = kia_protocol_encoder_v7_deserialize,
|
||||||
|
.stop = kia_protocol_encoder_v7_stop,
|
||||||
|
.yield = kia_protocol_encoder_v7_yield,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_kia_v7 = {
|
||||||
|
.name = KIA_PROTOCOL_V7_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||||
|
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||||
|
.decoder = &subghz_protocol_kia_v7_decoder,
|
||||||
|
.encoder = &subghz_protocol_kia_v7_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
|
||||||
|
furi_check(instance);
|
||||||
|
|
||||||
|
instance->base.protocol = &subghz_protocol_kia_v7;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.repeat = 1;
|
||||||
|
instance->encoder.size_upload = KIA_V7_UPLOAD_CAPACITY;
|
||||||
|
instance->encoder.upload = malloc(KIA_V7_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kia_protocol_encoder_v7_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||||
|
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||||
|
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
instance->encoder.repeat = KIA_V7_DEFAULT_TX_REPEAT;
|
||||||
|
|
||||||
|
do {
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
if(!temp_str) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
|
||||||
|
if(load_st != SubGhzProtocolStatusOk) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->tx_bit_count =
|
||||||
|
(instance->generic.data_count_bit > 0U && instance->generic.data_count_bit <= 64U) ?
|
||||||
|
(uint8_t)instance->generic.data_count_bit :
|
||||||
|
64U;
|
||||||
|
|
||||||
|
kia_v7_decode_key_encoder(instance);
|
||||||
|
|
||||||
|
uint32_t u32 = 0;
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Serial", &u32, 1)) {
|
||||||
|
instance->generic.serial = u32;
|
||||||
|
}
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
|
||||||
|
instance->generic.btn = (uint8_t)u32;
|
||||||
|
}
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
|
||||||
|
instance->generic.cnt = (uint16_t)u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->generic.btn &= 0x0FU;
|
||||||
|
instance->generic.cnt &= 0xFFFFU;
|
||||||
|
instance->generic.serial &= 0x0FFFFFFFU;
|
||||||
|
|
||||||
|
instance->generic.data = kia_v7_encode_key(
|
||||||
|
instance->fixed_high_byte,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn,
|
||||||
|
(uint16_t)instance->generic.cnt,
|
||||||
|
&instance->crc_calculated);
|
||||||
|
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
if(!flipper_format_read_uint32(flipper_format, "Repeat", &u32, 1)) {
|
||||||
|
u32 = KIA_V7_DEFAULT_TX_REPEAT;
|
||||||
|
}
|
||||||
|
instance->encoder.repeat = u32;
|
||||||
|
|
||||||
|
if(!kia_v7_encoder_get_upload(instance)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(instance->encoder.size_upload == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
uint8_t key_data[sizeof(uint64_t)];
|
||||||
|
kia_v7_u64_to_bytes_be(instance->generic.data, key_data);
|
||||||
|
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.is_running = true;
|
||||||
|
ret = SubGhzProtocolStatusOk;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kia_protocol_encoder_v7_stop(void* context) {
|
||||||
|
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration kia_protocol_encoder_v7_yield(void* context) {
|
||||||
|
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||||
|
|
||||||
|
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
return level_duration_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
|
||||||
|
|
||||||
|
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||||
|
instance->encoder.repeat--;
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
|
||||||
|
furi_check(instance);
|
||||||
|
|
||||||
|
instance->base.protocol = &subghz_protocol_kia_v7;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kia_protocol_decoder_v7_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kia_protocol_decoder_v7_reset(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||||
|
instance->decoder.te_last = 0;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
instance->preamble_count = 0;
|
||||||
|
manchester_advance(
|
||||||
|
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
ManchesterEvent event = ManchesterEventReset;
|
||||||
|
bool data = false;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case KiaV7DecoderStepReset:
|
||||||
|
if(level && kia_v7_is_short(duration)) {
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepPreamble;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->preamble_count = 0;
|
||||||
|
manchester_advance(
|
||||||
|
instance->manchester_state,
|
||||||
|
ManchesterEventReset,
|
||||||
|
&instance->manchester_state,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KiaV7DecoderStepPreamble:
|
||||||
|
if(level) {
|
||||||
|
if(kia_v7_is_long(duration) && kia_v7_is_short(instance->decoder.te_last)) {
|
||||||
|
if(instance->preamble_count > (KIA_V7_PREAMBLE_MIN_PAIRS - 1U)) {
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
instance->preamble_count = 0;
|
||||||
|
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0U);
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||||
|
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepSyncLow;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||||
|
}
|
||||||
|
} else if(kia_v7_is_short(duration)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(kia_v7_is_short(duration) && kia_v7_is_short(instance->decoder.te_last)) {
|
||||||
|
instance->preamble_count++;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KiaV7DecoderStepSyncLow:
|
||||||
|
if(!level && kia_v7_is_short(duration) && kia_v7_is_long(instance->decoder.te_last)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepData;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KiaV7DecoderStepData: {
|
||||||
|
if(kia_v7_is_short(duration)) {
|
||||||
|
event = (ManchesterEvent)((uint8_t)(level & 1U) << 1U);
|
||||||
|
} else if(kia_v7_is_long(duration)) {
|
||||||
|
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||||
|
} else {
|
||||||
|
event = ManchesterEventReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(kia_v7_is_short(duration) || kia_v7_is_long(duration)) {
|
||||||
|
if(manchester_advance(
|
||||||
|
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(instance->decoder.decode_count_bit == KIA_V7_KEY_BITS) {
|
||||||
|
const uint64_t candidate = ~instance->decoder.decode_data;
|
||||||
|
const uint8_t hdr = (uint8_t)((candidate >> 56U) & 0xFFU);
|
||||||
|
|
||||||
|
if(hdr == KIA_V7_HEADER) {
|
||||||
|
instance->generic.data = candidate;
|
||||||
|
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
|
||||||
|
kia_v7_decode_key_decoder(instance);
|
||||||
|
|
||||||
|
if(instance->crc_valid) {
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->generic.data = 0;
|
||||||
|
instance->generic.data_count_bit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||||
|
manchester_advance(
|
||||||
|
instance->manchester_state,
|
||||||
|
ManchesterEventReset,
|
||||||
|
&instance->manchester_state,
|
||||||
|
NULL);
|
||||||
|
} else {
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||||
|
manchester_advance(
|
||||||
|
instance->manchester_state,
|
||||||
|
ManchesterEventReset,
|
||||||
|
&instance->manchester_state,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit >> 3U) + 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
kia_v7_decode_key_decoder(instance);
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Key:%016llX\r\n"
|
||||||
|
"Sn:%07lX Cnt:%04lX\r\n"
|
||||||
|
"Btn:%01X [%s] CRC:%02X [%s]",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->generic.data_count_bit,
|
||||||
|
instance->generic.data,
|
||||||
|
instance->generic.serial & 0x0FFFFFFFU,
|
||||||
|
instance->generic.cnt & 0xFFFFU,
|
||||||
|
instance->decoded_button & 0x0FU,
|
||||||
|
kia_v7_get_button_name(instance->decoded_button),
|
||||||
|
instance->crc_calculated,
|
||||||
|
instance->crc_valid ? "OK" : "ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
kia_v7_decode_key_decoder(instance);
|
||||||
|
|
||||||
|
SubGhzProtocolStatus status =
|
||||||
|
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
if(status != SubGhzProtocolStatusOk) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t serial = instance->generic.serial & 0x0FFFFFFFU;
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
uint32_t btn_u32 = (uint32_t)(instance->decoded_button & 0x0FU);
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
uint32_t cnt_u32 = (uint32_t)(instance->generic.cnt & 0xFFFFU);
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_u32, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
uint32_t repeat_u32 = KIA_V7_DEFAULT_TX_REPEAT;
|
||||||
|
if(!flipper_format_write_uint32(flipper_format, "Repeat", &repeat_u32, 1)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kia_v7_write_display(
|
||||||
|
flipper_format, instance->generic.protocol_name, instance->decoded_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||||
|
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
|
||||||
|
|
||||||
|
if(status != SubGhzProtocolStatusOk) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!flipper_format_rewind(flipper_format)) {
|
||||||
|
return SubGhzProtocolStatusErrorParserOthers;
|
||||||
|
}
|
||||||
|
|
||||||
|
kia_v7_decode_key_decoder(instance);
|
||||||
|
|
||||||
|
uint32_t ser_u32 = 0;
|
||||||
|
uint32_t btn_u32 = 0;
|
||||||
|
uint32_t cnt_u32 = 0;
|
||||||
|
bool got_serial = false;
|
||||||
|
bool got_btn = false;
|
||||||
|
bool got_cnt = false;
|
||||||
|
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
got_serial = flipper_format_read_uint32(flipper_format, "Serial", &ser_u32, 1);
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
got_btn = flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
|
||||||
|
flipper_format_rewind(flipper_format);
|
||||||
|
got_cnt = flipper_format_read_uint32(flipper_format, "Cnt", &cnt_u32, 1);
|
||||||
|
|
||||||
|
if(got_serial || got_btn || got_cnt) {
|
||||||
|
if(got_serial) {
|
||||||
|
instance->generic.serial = ser_u32 & 0x0FFFFFFFU;
|
||||||
|
}
|
||||||
|
if(got_btn) {
|
||||||
|
instance->generic.btn = (uint8_t)(btn_u32 & 0x0FU);
|
||||||
|
}
|
||||||
|
if(got_cnt) {
|
||||||
|
instance->generic.cnt = (uint16_t)(cnt_u32 & 0xFFFFU);
|
||||||
|
}
|
||||||
|
instance->generic.data = kia_v7_encode_key(
|
||||||
|
instance->fixed_high_byte,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn & 0x0FU,
|
||||||
|
(uint16_t)(instance->generic.cnt & 0xFFFFU),
|
||||||
|
&instance->crc_calculated);
|
||||||
|
kia_v7_decode_key_decoder(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubGhzProtocolStatusOk;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/types.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
#include <lib/toolbox/level_duration.h>
|
||||||
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
|
|
||||||
|
#define KIA_PROTOCOL_V7_NAME "Kia V7"
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderKiaV7 SubGhzProtocolDecoderKiaV7;
|
||||||
|
typedef struct SubGhzProtocolEncoderKiaV7 SubGhzProtocolEncoderKiaV7;
|
||||||
|
|
||||||
|
extern const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder;
|
||||||
|
extern const SubGhzProtocol subghz_protocol_kia_v7;
|
||||||
|
|
||||||
|
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment);
|
||||||
|
void kia_protocol_decoder_v7_free(void* context);
|
||||||
|
void kia_protocol_decoder_v7_reset(void* context);
|
||||||
|
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
|
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment);
|
||||||
|
void kia_protocol_encoder_v7_free(void* context);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void kia_protocol_encoder_v7_stop(void* context);
|
||||||
|
LevelDuration kia_protocol_encoder_v7_yield(void* context);
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
|||||||
//&subghz_protocol_honeywell,
|
//&subghz_protocol_honeywell,
|
||||||
//&subghz_protocol_legrand,
|
//&subghz_protocol_legrand,
|
||||||
&subghz_protocol_dickert_mahs,
|
&subghz_protocol_dickert_mahs,
|
||||||
//&subghz_protocol_gangqi,
|
&subghz_protocol_gangqi,
|
||||||
&subghz_protocol_marantec24,
|
&subghz_protocol_marantec24,
|
||||||
//&subghz_protocol_hollarm,
|
//&subghz_protocol_hollarm,
|
||||||
&subghz_protocol_hay21,
|
&subghz_protocol_hay21,
|
||||||
@@ -59,6 +59,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
|||||||
&subghz_protocol_vag,
|
&subghz_protocol_vag,
|
||||||
&subghz_protocol_porsche_cayenne,
|
&subghz_protocol_porsche_cayenne,
|
||||||
&subghz_protocol_ford_v0,
|
&subghz_protocol_ford_v0,
|
||||||
|
&ford_protocol_v1,
|
||||||
&subghz_protocol_psa,
|
&subghz_protocol_psa,
|
||||||
&subghz_protocol_fiat_spa,
|
&subghz_protocol_fiat_spa,
|
||||||
&subghz_protocol_fiat_marelli,
|
&subghz_protocol_fiat_marelli,
|
||||||
@@ -71,13 +72,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
|||||||
&subghz_protocol_kia_v3_v4,
|
&subghz_protocol_kia_v3_v4,
|
||||||
&subghz_protocol_kia_v5,
|
&subghz_protocol_kia_v5,
|
||||||
&subghz_protocol_kia_v6,
|
&subghz_protocol_kia_v6,
|
||||||
|
&subghz_protocol_kia_v7,
|
||||||
&subghz_protocol_suzuki,
|
&subghz_protocol_suzuki,
|
||||||
&subghz_protocol_mitsubishi_v0,
|
&subghz_protocol_mitsubishi_v0,
|
||||||
&subghz_protocol_star_line,
|
&subghz_protocol_star_line,
|
||||||
&subghz_protocol_scher_khan,
|
&subghz_protocol_scher_khan,
|
||||||
&subghz_protocol_sheriff_cfm,
|
&subghz_protocol_sheriff_cfm,
|
||||||
&subghz_protocol_honda,
|
|
||||||
&subghz_protocol_chrysler,
|
&subghz_protocol_chrysler,
|
||||||
|
&honda_static_protocol,
|
||||||
|
//&subghz_protocol_honda,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||||
|
|||||||
@@ -72,11 +72,14 @@
|
|||||||
#include "kia_v3_v4.h"
|
#include "kia_v3_v4.h"
|
||||||
#include "kia_v5.h"
|
#include "kia_v5.h"
|
||||||
#include "kia_v6.h"
|
#include "kia_v6.h"
|
||||||
|
#include "kia_v7.h"
|
||||||
#include "suzuki.h"
|
#include "suzuki.h"
|
||||||
#include "mitsubishi_v0.h"
|
#include "mitsubishi_v0.h"
|
||||||
#include "mazda_siemens.h"
|
#include "mazda_siemens.h"
|
||||||
#include "star_line.h"
|
#include "star_line.h"
|
||||||
#include "scher_khan.h"
|
#include "scher_khan.h"
|
||||||
#include "sheriff_cfm.h"
|
#include "sheriff_cfm.h"
|
||||||
#include "honda.h"
|
|
||||||
#include "chrysler.h"
|
#include "chrysler.h"
|
||||||
|
#include "honda_static.h"
|
||||||
|
#include "ford_v1.h"
|
||||||
|
//#include "honda_pandora.h"
|
||||||
|
|||||||
+90
-86
@@ -125,6 +125,10 @@ struct SubGhzProtocolDecoderPSA {
|
|||||||
|
|
||||||
uint32_t last_key1_low;
|
uint32_t last_key1_low;
|
||||||
uint32_t last_key1_high;
|
uint32_t last_key1_high;
|
||||||
|
|
||||||
|
uint32_t te_sum;
|
||||||
|
uint16_t te_count;
|
||||||
|
uint32_t te_detected;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SubGhzProtocolEncoderPSA {
|
struct SubGhzProtocolEncoderPSA {
|
||||||
@@ -670,6 +674,9 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
instance->pattern_counter = 0;
|
instance->pattern_counter = 0;
|
||||||
instance->decode_count_bit = 0;
|
instance->decode_count_bit = 0;
|
||||||
instance->mode_serialize = 0;
|
instance->mode_serialize = 0;
|
||||||
|
instance->te_sum = duration;
|
||||||
|
instance->te_count = 1;
|
||||||
|
instance->te_detected = 0;
|
||||||
instance->prev_duration = duration;
|
instance->prev_duration = duration;
|
||||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||||
&instance->manchester_state, NULL);
|
&instance->manchester_state, NULL);
|
||||||
@@ -935,39 +942,35 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(duration < PSA_TE_SHORT_125) {
|
// Adaptive AM preamble: accept 76-174us, average to detect actual TE
|
||||||
tolerance = PSA_TE_SHORT_125 - duration;
|
if(duration >= 76 && duration <= 174) {
|
||||||
if(tolerance < PSA_TOLERANCE_50) {
|
if(prev_dur >= 76 && prev_dur <= 174) {
|
||||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
instance->pattern_counter++;
|
||||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
instance->te_sum += duration;
|
||||||
instance->pattern_counter++;
|
instance->te_count++;
|
||||||
} else {
|
} else {
|
||||||
instance->pattern_counter = 0;
|
instance->pattern_counter = 0;
|
||||||
}
|
instance->te_sum = duration;
|
||||||
instance->prev_duration = duration;
|
instance->te_count = 1;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
instance->prev_duration = duration;
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
tolerance = duration - PSA_TE_SHORT_125;
|
// Check if this is the preamble-to-data transition (2x detected TE)
|
||||||
if(tolerance < PSA_TOLERANCE_50) {
|
uint32_t te_avg = (instance->te_count > 0) ?
|
||||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
(instance->te_sum / instance->te_count) : PSA_TE_SHORT_125;
|
||||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
uint32_t te_long_expected = te_avg * 2;
|
||||||
instance->pattern_counter++;
|
uint32_t long_diff = psa_abs_diff(duration, te_long_expected);
|
||||||
} else {
|
|
||||||
instance->pattern_counter = 0;
|
if(long_diff <= te_avg && instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||||
}
|
instance->te_detected = te_avg;
|
||||||
instance->prev_duration = duration;
|
new_state = PSADecoderState4;
|
||||||
return;
|
instance->decode_data_low = 0;
|
||||||
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
instance->decode_data_high = 0;
|
||||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
instance->decode_count_bit = 0;
|
||||||
new_state = PSADecoderState4;
|
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||||
instance->decode_data_low = 0;
|
&instance->manchester_state, NULL);
|
||||||
instance->decode_data_high = 0;
|
instance->state = new_state;
|
||||||
instance->decode_count_bit = 0;
|
|
||||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
|
||||||
&instance->manchester_state, NULL);
|
|
||||||
instance->state = new_state;
|
|
||||||
}
|
|
||||||
instance->pattern_counter = 0;
|
instance->pattern_counter = 0;
|
||||||
instance->prev_duration = duration;
|
instance->prev_duration = duration;
|
||||||
return;
|
return;
|
||||||
@@ -975,67 +978,25 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
}
|
}
|
||||||
|
|
||||||
new_state = PSADecoderState0;
|
new_state = PSADecoderState0;
|
||||||
|
instance->pattern_counter = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PSADecoderState4:
|
case PSADecoderState4: {
|
||||||
if(instance->decode_count_bit >= PSA_MAX_BITS) {
|
if(instance->decode_count_bit >= PSA_MAX_BITS) {
|
||||||
new_state = PSADecoderState0;
|
new_state = PSADecoderState0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!level) {
|
uint32_t te_s = instance->te_detected ? instance->te_detected : PSA_TE_SHORT_125;
|
||||||
uint8_t manchester_input;
|
uint32_t te_l = te_s * 2;
|
||||||
bool decoded_bit = false;
|
uint32_t te_tol = te_s / 2;
|
||||||
|
uint32_t midpoint = (te_s + te_l) / 2;
|
||||||
if(duration < PSA_TE_SHORT_125) {
|
|
||||||
tolerance = PSA_TE_SHORT_125 - duration;
|
|
||||||
if(tolerance > PSA_TOLERANCE_49) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
manchester_input = ((level ^ 1) & 0x7f) << 1;
|
|
||||||
} else {
|
|
||||||
tolerance = duration - PSA_TE_SHORT_125;
|
|
||||||
if(tolerance < PSA_TOLERANCE_50) {
|
|
||||||
manchester_input = ((level ^ 1) & 0x7f) << 1;
|
|
||||||
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
|
||||||
if(level == 0) {
|
|
||||||
manchester_input = 6;
|
|
||||||
} else {
|
|
||||||
manchester_input = 4;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(manchester_advance(instance->manchester_state,
|
|
||||||
(ManchesterEvent)manchester_input,
|
|
||||||
&instance->manchester_state,
|
|
||||||
&decoded_bit)) {
|
|
||||||
uint32_t carry = (instance->decode_data_low >> 31) & 1;
|
|
||||||
instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0);
|
|
||||||
instance->decode_data_high = (instance->decode_data_high << 1) | carry;
|
|
||||||
instance->decode_count_bit++;
|
|
||||||
|
|
||||||
if(instance->decode_count_bit == PSA_KEY1_BITS) {
|
|
||||||
instance->key1_low = instance->decode_data_low;
|
|
||||||
instance->key1_high = instance->decode_data_high;
|
|
||||||
instance->decode_data_low = 0;
|
|
||||||
instance->decode_data_high = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(level) {
|
|
||||||
uint32_t end_diff;
|
|
||||||
if(duration < PSA_TE_END_500) {
|
|
||||||
end_diff = PSA_TE_END_500 - duration;
|
|
||||||
} else {
|
|
||||||
end_diff = duration - PSA_TE_END_500;
|
|
||||||
}
|
|
||||||
if(end_diff <= 99) {
|
|
||||||
if(instance->decode_count_bit != PSA_KEY2_BITS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// End marker check: HIGH pulse beyond long range at 80 bits
|
||||||
|
if(level && instance->decode_count_bit == PSA_KEY2_BITS && duration > midpoint) {
|
||||||
|
uint32_t end_expected = te_s * 4;
|
||||||
|
uint32_t end_diff = psa_abs_diff(duration, end_expected);
|
||||||
|
if(end_diff <= te_s * 2) {
|
||||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||||
instance->key2_low = instance->decode_data_low;
|
instance->key2_low = instance->decode_data_low;
|
||||||
instance->key2_high = instance->decode_data_high;
|
instance->key2_high = instance->decode_data_high;
|
||||||
@@ -1052,7 +1013,6 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
instance->mode_serialize = 0x36;
|
instance->mode_serialize = 0x36;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only fire callback if decrypted or validation nibble matches
|
|
||||||
if(instance->decrypted != 0x50 &&
|
if(instance->decrypted != 0x50 &&
|
||||||
(instance->validation_field & 0xf) != 0xa) {
|
(instance->validation_field & 0xf) != 0xa) {
|
||||||
instance->decode_data_low = 0;
|
instance->decode_data_low = 0;
|
||||||
@@ -1076,12 +1036,56 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
new_state = PSADecoderState0;
|
new_state = PSADecoderState0;
|
||||||
instance->state = new_state;
|
instance->state = new_state;
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manchester decode: process BOTH high and low pulses (unlike original AM path)
|
||||||
|
if(duration > te_l + te_tol) {
|
||||||
|
if(duration > 10000) {
|
||||||
|
new_state = PSADecoderState0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t manchester_input;
|
||||||
|
bool decoded_bit = false;
|
||||||
|
|
||||||
|
if(duration <= midpoint) {
|
||||||
|
if(psa_abs_diff(duration, te_s) > te_tol) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
manchester_input = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||||
|
} else {
|
||||||
|
if(psa_abs_diff(duration, te_l) > te_tol) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
manchester_input = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(instance->decode_count_bit < PSA_KEY2_BITS) {
|
||||||
|
if(manchester_advance(instance->manchester_state,
|
||||||
|
(ManchesterEvent)manchester_input,
|
||||||
|
&instance->manchester_state,
|
||||||
|
&decoded_bit)) {
|
||||||
|
uint32_t carry = (instance->decode_data_low >> 31) & 1;
|
||||||
|
// PSA AM uses inverted Manchester convention
|
||||||
|
decoded_bit = !decoded_bit;
|
||||||
|
instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0);
|
||||||
|
instance->decode_data_high = (instance->decode_data_high << 1) | carry;
|
||||||
|
instance->decode_count_bit++;
|
||||||
|
|
||||||
|
if(instance->decode_count_bit == PSA_KEY1_BITS) {
|
||||||
|
instance->key1_low = instance->decode_data_low;
|
||||||
|
instance->key1_high = instance->decode_data_high;
|
||||||
|
instance->decode_data_low = 0;
|
||||||
|
instance->decode_data_high = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
instance->state = new_state;
|
instance->state = new_state;
|
||||||
instance->prev_duration = duration;
|
instance->prev_duration = duration;
|
||||||
|
|||||||
Reference in New Issue
Block a user