mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-05-13 17:43:07 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c32ee61a4f | |||
| 0995609391 | |||
| 29fef56be1 | |||
| 6a348dd304 | |||
| 32a96e580d | |||
| 54f03a39c2 | |||
| a55189e2a4 | |||
| 14d10c0794 | |||
| 27818ccb1f | |||
| 0ebf26eff4 | |||
| ac620e2b0e | |||
| 46115cdf6c | |||
| f465c6edbb | |||
| ad795ae7ef | |||
| efff8d2f2e | |||
| c9c9c74117 | |||
| dc0f30dad9 | |||
| 38f261e23b |
+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,13 +59,17 @@ 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 |
|
||||||
|
| Honda | Honda Type A/B | 433 MHz | FM (custom) | Yes | Yes | No |
|
||||||
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
|
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
|
||||||
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
|
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
|
||||||
| 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 |
|
||||||
|
| Honda | Honda Static | 433 MHz | AM | Yes | Yes | No |
|
||||||
|
|
||||||
### Gate / Access Protocols
|
### Gate / Access Protocols
|
||||||
|
|
||||||
@@ -343,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
|
||||||
@@ -119,3 +119,13 @@ Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 67 11 83 12 04 13 02 15 24 18 18
|
|||||||
Custom_preset_name: FM15k
|
Custom_preset_name: FM15k
|
||||||
Custom_preset_module: CC1101
|
Custom_preset_module: CC1101
|
||||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0
|
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0
|
||||||
|
|
||||||
|
Custom_preset_name: Honda1
|
||||||
|
Custom_preset_module: CC1101
|
||||||
|
# G2 G3 G4 D L0 L1 L2
|
||||||
|
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||||
|
|
||||||
|
Custom_preset_name: Honda2
|
||||||
|
Custom_preset_module: CC1101
|
||||||
|
# G2 G3 G4 D L0 L1 L2
|
||||||
|
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
#include "bmw_cas4.h"
|
||||||
|
|
||||||
|
#include "../blocks/const.h"
|
||||||
|
#include "../blocks/decoder.h"
|
||||||
|
#include "../blocks/encoder.h"
|
||||||
|
#include "../blocks/generic.h"
|
||||||
|
#include "../blocks/math.h"
|
||||||
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
|
|
||||||
|
#define TAG "BmwCas4"
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_bmw_cas4_const = {
|
||||||
|
.te_short = 500,
|
||||||
|
.te_long = 1000,
|
||||||
|
.te_delta = 150,
|
||||||
|
.min_count_bit_for_found = 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define BMW_CAS4_PREAMBLE_PULSE_MIN 300u
|
||||||
|
#define BMW_CAS4_PREAMBLE_PULSE_MAX 700u
|
||||||
|
#define BMW_CAS4_PREAMBLE_MIN 10u
|
||||||
|
#define BMW_CAS4_DATA_BITS 64u
|
||||||
|
#define BMW_CAS4_GAP_MIN 1800u
|
||||||
|
#define BMW_CAS4_BYTE0_MARKER 0x30u
|
||||||
|
#define BMW_CAS4_BYTE6_MARKER 0xC5u
|
||||||
|
|
||||||
|
struct SubGhzProtocolDecoderBmwCas4 {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
ManchesterState manchester_state;
|
||||||
|
uint8_t decoder_state;
|
||||||
|
uint16_t preamble_count;
|
||||||
|
uint8_t raw_data[8];
|
||||||
|
uint8_t bit_count;
|
||||||
|
uint32_t te_last;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubGhzProtocolEncoderBmwCas4 {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BmwCas4DecoderStepReset = 0,
|
||||||
|
BmwCas4DecoderStepPreamble,
|
||||||
|
BmwCas4DecoderStepData,
|
||||||
|
} BmwCas4DecoderStep;
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_bmw_cas4_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_bmw_cas4_alloc,
|
||||||
|
.free = subghz_protocol_decoder_bmw_cas4_free,
|
||||||
|
.feed = subghz_protocol_decoder_bmw_cas4_feed,
|
||||||
|
.reset = subghz_protocol_decoder_bmw_cas4_reset,
|
||||||
|
.get_hash_data = subghz_protocol_decoder_bmw_cas4_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_bmw_cas4_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_bmw_cas4_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_bmw_cas4_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_bmw_cas4_encoder = {
|
||||||
|
.alloc = subghz_protocol_encoder_bmw_cas4_alloc,
|
||||||
|
.free = subghz_protocol_encoder_bmw_cas4_free,
|
||||||
|
.deserialize = subghz_protocol_encoder_bmw_cas4_deserialize,
|
||||||
|
.stop = subghz_protocol_encoder_bmw_cas4_stop,
|
||||||
|
.yield = subghz_protocol_encoder_bmw_cas4_yield,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_bmw_cas4 = {
|
||||||
|
.name = BMW_CAS4_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||||
|
SubGhzProtocolFlag_Decodable |
|
||||||
|
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||||
|
.decoder = &subghz_protocol_bmw_cas4_decoder,
|
||||||
|
.encoder = &subghz_protocol_bmw_cas4_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Encoder stubs
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_bmw_cas4_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolEncoderBmwCas4* instance = calloc(1, sizeof(SubGhzProtocolEncoderBmwCas4));
|
||||||
|
furi_check(instance);
|
||||||
|
instance->base.protocol = &subghz_protocol_bmw_cas4;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
instance->encoder.size_upload = 1;
|
||||||
|
instance->encoder.upload = malloc(sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_bmw_cas4_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderBmwCas4* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(flipper_format);
|
||||||
|
return SubGhzProtocolStatusError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_bmw_cas4_stop(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderBmwCas4* instance = context;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration subghz_protocol_encoder_bmw_cas4_yield(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
return level_duration_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder
|
||||||
|
|
||||||
|
static void bmw_cas4_rebuild_raw_data(SubGhzProtocolDecoderBmwCas4* instance) {
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
uint64_t key = instance->generic.data;
|
||||||
|
for(int i = 0; i < 8; i++) {
|
||||||
|
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||||
|
}
|
||||||
|
instance->bit_count = instance->generic.data_count_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_bmw_cas4_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = calloc(1, sizeof(SubGhzProtocolDecoderBmwCas4));
|
||||||
|
furi_check(instance);
|
||||||
|
instance->base.protocol = &subghz_protocol_bmw_cas4;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_reset(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||||
|
instance->preamble_count = 0;
|
||||||
|
instance->bit_count = 0;
|
||||||
|
instance->te_last = 0;
|
||||||
|
instance->generic.data = 0;
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
instance->manchester_state = ManchesterStateMid1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||||
|
|
||||||
|
uint32_t te_short = subghz_protocol_bmw_cas4_const.te_short;
|
||||||
|
uint32_t te_long = subghz_protocol_bmw_cas4_const.te_long;
|
||||||
|
uint32_t te_delta = subghz_protocol_bmw_cas4_const.te_delta;
|
||||||
|
uint32_t diff;
|
||||||
|
|
||||||
|
switch(instance->decoder_state) {
|
||||||
|
case BmwCas4DecoderStepReset:
|
||||||
|
if(level && duration >= BMW_CAS4_PREAMBLE_PULSE_MIN &&
|
||||||
|
duration <= BMW_CAS4_PREAMBLE_PULSE_MAX) {
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepPreamble;
|
||||||
|
instance->preamble_count = 1;
|
||||||
|
instance->te_last = duration;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BmwCas4DecoderStepPreamble:
|
||||||
|
if(duration >= BMW_CAS4_PREAMBLE_PULSE_MIN &&
|
||||||
|
duration <= BMW_CAS4_PREAMBLE_PULSE_MAX) {
|
||||||
|
instance->preamble_count++;
|
||||||
|
instance->te_last = duration;
|
||||||
|
} else if(!level && duration >= BMW_CAS4_GAP_MIN) {
|
||||||
|
if(instance->preamble_count >= BMW_CAS4_PREAMBLE_MIN) {
|
||||||
|
instance->bit_count = 0;
|
||||||
|
instance->generic.data = 0;
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
manchester_advance(
|
||||||
|
instance->manchester_state,
|
||||||
|
ManchesterEventReset,
|
||||||
|
&instance->manchester_state,
|
||||||
|
NULL);
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepData;
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BmwCas4DecoderStepData: {
|
||||||
|
if(instance->bit_count >= BMW_CAS4_DATA_BITS) {
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ManchesterEvent event = ManchesterEventReset;
|
||||||
|
|
||||||
|
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
||||||
|
if(diff < te_delta) {
|
||||||
|
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||||
|
} else {
|
||||||
|
diff = (duration > te_long) ? (duration - te_long) : (te_long - duration);
|
||||||
|
if(diff < te_delta) {
|
||||||
|
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event != ManchesterEventReset) {
|
||||||
|
bool data_bit;
|
||||||
|
if(manchester_advance(
|
||||||
|
instance->manchester_state,
|
||||||
|
event,
|
||||||
|
&instance->manchester_state,
|
||||||
|
&data_bit)) {
|
||||||
|
uint32_t new_bit = data_bit ? 1 : 0;
|
||||||
|
|
||||||
|
if(instance->bit_count < BMW_CAS4_DATA_BITS) {
|
||||||
|
uint8_t byte_idx = instance->bit_count / 8;
|
||||||
|
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||||
|
if(new_bit) {
|
||||||
|
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||||
|
}
|
||||||
|
instance->generic.data = (instance->generic.data << 1) | new_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->bit_count++;
|
||||||
|
|
||||||
|
if(instance->bit_count == BMW_CAS4_DATA_BITS) {
|
||||||
|
if(instance->raw_data[0] == BMW_CAS4_BYTE0_MARKER &&
|
||||||
|
instance->raw_data[6] == BMW_CAS4_BYTE6_MARKER) {
|
||||||
|
instance->generic.data_count_bit = BMW_CAS4_DATA_BITS;
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->te_last = duration;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_bmw_cas4_get_hash_data(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||||
|
SubGhzBlockDecoder dec = {
|
||||||
|
.decode_data = instance->generic.data,
|
||||||
|
.decode_count_bit = instance->generic.data_count_bit,
|
||||||
|
};
|
||||||
|
return subghz_protocol_blocks_get_hash_data(&dec, (dec.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_bmw_cas4_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||||
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||||
|
SubGhzProtocolStatus ret =
|
||||||
|
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||||
|
if(ret == SubGhzProtocolStatusOk) {
|
||||||
|
bmw_cas4_rebuild_raw_data(instance);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_get_string(void* context, FuriString* output) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Raw:%02X %02X%02X%02X%02X%02X %02X %02X\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
(int)instance->generic.data_count_bit,
|
||||||
|
instance->raw_data[0],
|
||||||
|
instance->raw_data[1], instance->raw_data[2],
|
||||||
|
instance->raw_data[3], instance->raw_data[4], instance->raw_data[5],
|
||||||
|
instance->raw_data[6],
|
||||||
|
instance->raw_data[7]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#define BMW_CAS4_PROTOCOL_NAME "BMW CAS4"
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderBmwCas4 SubGhzProtocolDecoderBmwCas4;
|
||||||
|
typedef struct SubGhzProtocolEncoderBmwCas4 SubGhzProtocolEncoderBmwCas4;
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_bmw_cas4;
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_bmw_cas4_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_free(void* context);
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_reset(void* context);
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t subghz_protocol_decoder_bmw_cas4_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_bmw_cas4_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_decoder_bmw_cas4_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_bmw_cas4_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_encoder_bmw_cas4_free(void* context);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_encoder_bmw_cas4_stop(void* context);
|
||||||
|
LevelDuration subghz_protocol_encoder_bmw_cas4_yield(void* context);
|
||||||
@@ -0,0 +1,643 @@
|
|||||||
|
#include "chrysler.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 "Chrysler"
|
||||||
|
|
||||||
|
// Chrysler keyfob rolling code protocol
|
||||||
|
// Found on: PT Cruiser, Dodge, Jeep (~2004-2010)
|
||||||
|
//
|
||||||
|
// RF: 433.92 MHz, OOK PWM encoding
|
||||||
|
// Bit timing: ~4000us total period
|
||||||
|
// Bit 0: ~300us HIGH + ~3700us LOW
|
||||||
|
// Bit 1: ~600us HIGH + ~3400us LOW
|
||||||
|
// Frame: 24-bit zero preamble + gap ~15600us + 80-bit data
|
||||||
|
// Retransmission: same frame sent twice per press
|
||||||
|
//
|
||||||
|
// 80-bit frame layout (10 bytes):
|
||||||
|
// Byte 0: [counter:4 | device_id:4]
|
||||||
|
// Counter: 4-bit, bit-reversed, decrementing
|
||||||
|
// Device ID: constant per keyfob (e.g. 0xB)
|
||||||
|
// Bytes 1-4: nibble-interleaved rolling code + button
|
||||||
|
// MSB(b0)=0: high nibbles = rolling, low nibbles = button
|
||||||
|
// MSB(b0)=1: low nibbles = rolling, high nibbles = button
|
||||||
|
// Byte 5: check byte (b1 XOR 0xC3 when MSB=0, b1 when MSB=1)
|
||||||
|
// Byte 6: b1 XOR mask (mask depends on MSB and button)
|
||||||
|
// Bytes 7-9: b2-b4 XOR fixed mask (redundancy copy)
|
||||||
|
//
|
||||||
|
// Rolling code: single 8-bit value XOR'd with per-device serial offsets
|
||||||
|
// across all 4 byte positions. The 4 bytes are related by constant XOR
|
||||||
|
// (the serial).
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_chrysler_const = {
|
||||||
|
.te_short = 300,
|
||||||
|
.te_long = 600,
|
||||||
|
.te_delta = 150,
|
||||||
|
.min_count_bit_for_found = 80,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CHRYSLER_BIT_PERIOD 4000u
|
||||||
|
#define CHRYSLER_BIT_TOLERANCE 800u
|
||||||
|
#define CHRYSLER_PREAMBLE_MIN 15u
|
||||||
|
#define CHRYSLER_PREAMBLE_GAP 10000u
|
||||||
|
#define CHRYSLER_DATA_BITS 80u
|
||||||
|
#define CHRYSLER_SHORT_MAX 450u
|
||||||
|
#define CHRYSLER_LONG_MIN 450u
|
||||||
|
#define CHRYSLER_LONG_MAX 800u
|
||||||
|
|
||||||
|
struct SubGhzProtocolDecoderChrysler {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint8_t decoder_state;
|
||||||
|
uint16_t preamble_count;
|
||||||
|
uint8_t raw_data[10];
|
||||||
|
uint8_t bit_count;
|
||||||
|
uint32_t te_last;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubGhzProtocolEncoderChrysler {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint8_t raw_data[10];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ChryslerDecoderStepReset = 0,
|
||||||
|
ChryslerDecoderStepPreamble,
|
||||||
|
ChryslerDecoderStepGap,
|
||||||
|
ChryslerDecoderStepData,
|
||||||
|
} ChryslerDecoderStep;
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_chrysler_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_chrysler_alloc,
|
||||||
|
.free = subghz_protocol_decoder_chrysler_free,
|
||||||
|
.feed = subghz_protocol_decoder_chrysler_feed,
|
||||||
|
.reset = subghz_protocol_decoder_chrysler_reset,
|
||||||
|
.get_hash_data = subghz_protocol_decoder_chrysler_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_chrysler_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_chrysler_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_chrysler_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_chrysler_encoder = {
|
||||||
|
.alloc = subghz_protocol_encoder_chrysler_alloc,
|
||||||
|
.free = subghz_protocol_encoder_chrysler_free,
|
||||||
|
.deserialize = subghz_protocol_encoder_chrysler_deserialize,
|
||||||
|
.stop = subghz_protocol_encoder_chrysler_stop,
|
||||||
|
.yield = subghz_protocol_encoder_chrysler_yield,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_chrysler = {
|
||||||
|
.name = CHRYSLER_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||||
|
SubGhzProtocolFlag_Decodable |
|
||||||
|
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||||
|
.decoder = &subghz_protocol_chrysler_decoder,
|
||||||
|
.encoder = &subghz_protocol_chrysler_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t chrysler_reverse_nibble(uint8_t n) {
|
||||||
|
return (uint8_t)(((n & 1) << 3) | ((n & 2) << 1) | ((n & 4) >> 1) | ((n & 8) >> 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder
|
||||||
|
|
||||||
|
#define CHRYSLER_ENCODER_UPLOAD_MAX 800
|
||||||
|
#define CHRYSLER_ENCODER_REPEAT 3
|
||||||
|
#define CHRYSLER_PREAMBLE_BITS 24
|
||||||
|
#define CHRYSLER_PREAMBLE_GAP_US 15600
|
||||||
|
|
||||||
|
static uint8_t chrysler_custom_to_btn(uint8_t custom) {
|
||||||
|
switch(custom) {
|
||||||
|
case 1:
|
||||||
|
return 0x01; // Lock
|
||||||
|
case 2:
|
||||||
|
return 0x02; // Unlock
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chrysler_advance_rolling(uint8_t* d) {
|
||||||
|
// Advance the counter and rolling code for the next transmission.
|
||||||
|
//
|
||||||
|
// Counter: 4-bit bit-reversed in upper nibble of b0, decrementing.
|
||||||
|
// Rolling code: nibble-interleaved into bytes 1-4, swapped based on MSB(b0).
|
||||||
|
//
|
||||||
|
// Step 1: Extract current rolling nibbles and button nibbles
|
||||||
|
uint8_t msb = (d[0] >> 7) & 1;
|
||||||
|
uint8_t rolling[4], button[4];
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
if(msb == 0) {
|
||||||
|
rolling[i] = (d[1 + i] >> 4) & 0xF;
|
||||||
|
button[i] = d[1 + i] & 0xF;
|
||||||
|
} else {
|
||||||
|
rolling[i] = d[1 + i] & 0xF;
|
||||||
|
button[i] = (d[1 + i] >> 4) & 0xF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Decrement the bit-reversed counter
|
||||||
|
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||||
|
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||||
|
cnt = (cnt - 1) & 0xF;
|
||||||
|
cnt_raw = chrysler_reverse_nibble(cnt);
|
||||||
|
uint8_t new_msb = (cnt_raw >> 3) & 1;
|
||||||
|
|
||||||
|
// Step 3: Reassemble byte 0
|
||||||
|
d[0] = (cnt_raw << 4) | (d[0] & 0x0F);
|
||||||
|
|
||||||
|
// Step 4: Re-interleave nibbles with new MSB
|
||||||
|
// The rolling nibbles stay the same for one step (they change every 2 presses,
|
||||||
|
// i.e. when MSB returns to the same value). The button nibbles may differ
|
||||||
|
// between MSB=0 and MSB=1 states.
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
if(new_msb == 0) {
|
||||||
|
d[1 + i] = (rolling[i] << 4) | (button[i] & 0xF);
|
||||||
|
} else {
|
||||||
|
d[1 + i] = ((button[i] & 0xF) << 4) | rolling[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chrysler_encoder_rebuild(SubGhzProtocolEncoderChrysler* instance) {
|
||||||
|
uint8_t* d = instance->raw_data;
|
||||||
|
uint8_t msb = (d[0] >> 7) & 1;
|
||||||
|
uint8_t btn = instance->generic.btn;
|
||||||
|
|
||||||
|
uint8_t custom = subghz_custom_btn_get();
|
||||||
|
if(custom != 0) {
|
||||||
|
uint8_t new_btn = chrysler_custom_to_btn(custom);
|
||||||
|
if(new_btn != 0) btn = new_btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine b1^b6 mask based on button and MSB
|
||||||
|
uint8_t b1_xor_b6;
|
||||||
|
if(msb == 0) {
|
||||||
|
b1_xor_b6 = (btn == 0x01) ? 0x04 : 0x08;
|
||||||
|
} else {
|
||||||
|
b1_xor_b6 = 0x62;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild byte 5
|
||||||
|
d[5] = (msb == 0) ? (d[1] ^ 0xC3) : d[1];
|
||||||
|
|
||||||
|
// Rebuild byte 6
|
||||||
|
d[6] = d[1] ^ b1_xor_b6;
|
||||||
|
|
||||||
|
// Rebuild bytes 7-9 from bytes 2-4
|
||||||
|
if(msb == 0) {
|
||||||
|
d[7] = d[2] ^ 0x63;
|
||||||
|
d[8] = d[3] ^ 0x59;
|
||||||
|
d[9] = d[4] ^ 0x46;
|
||||||
|
} else {
|
||||||
|
d[7] = d[2] ^ 0x9A;
|
||||||
|
d[8] = d[3] ^ 0xC6;
|
||||||
|
d[9] = d[4] ^ ((btn == 0x01) ? 0x20 : 0x10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool chrysler_encoder_get_upload(SubGhzProtocolEncoderChrysler* instance) {
|
||||||
|
uint32_t te_short = subghz_protocol_chrysler_const.te_short;
|
||||||
|
uint32_t te_bit_period = CHRYSLER_BIT_PERIOD;
|
||||||
|
size_t index = 0;
|
||||||
|
size_t max_upload = CHRYSLER_ENCODER_UPLOAD_MAX;
|
||||||
|
|
||||||
|
// Preamble: 24 zero bits (short HIGH + long LOW each)
|
||||||
|
for(uint8_t i = 0; i < CHRYSLER_PREAMBLE_BITS && (index + 1) < max_upload; i++) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make(false, te_bit_period - te_short);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gap between preamble and data
|
||||||
|
if(index > 0) {
|
||||||
|
instance->encoder.upload[index - 1] =
|
||||||
|
level_duration_make(false, CHRYSLER_PREAMBLE_GAP_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data: 80 bits PWM
|
||||||
|
for(uint8_t bit_i = 0; bit_i < CHRYSLER_DATA_BITS && (index + 1) < max_upload; bit_i++) {
|
||||||
|
uint8_t byte_idx = bit_i / 8;
|
||||||
|
uint8_t bit_pos = 7 - (bit_i % 8);
|
||||||
|
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
|
||||||
|
|
||||||
|
uint32_t high_dur = data_bit ? 600 : te_short;
|
||||||
|
uint32_t low_dur = te_bit_period - high_dur;
|
||||||
|
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, high_dur);
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, low_dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final gap after frame
|
||||||
|
if(index > 0) {
|
||||||
|
instance->encoder.upload[index - 1] =
|
||||||
|
level_duration_make(false, CHRYSLER_PREAMBLE_GAP_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.size_upload = index;
|
||||||
|
return index > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_chrysler_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolEncoderChrysler* instance = calloc(1, sizeof(SubGhzProtocolEncoderChrysler));
|
||||||
|
furi_check(instance);
|
||||||
|
instance->base.protocol = &subghz_protocol_chrysler;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.repeat = CHRYSLER_ENCODER_REPEAT;
|
||||||
|
instance->encoder.size_upload = CHRYSLER_ENCODER_UPLOAD_MAX;
|
||||||
|
instance->encoder.upload = malloc(CHRYSLER_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_chrysler_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderChrysler* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderChrysler* instance = context;
|
||||||
|
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||||
|
|
||||||
|
do {
|
||||||
|
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||||
|
if(ret != SubGhzProtocolStatusOk) break;
|
||||||
|
|
||||||
|
// Rebuild raw_data from generic.data (bytes 0-7)
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
uint64_t key = instance->generic.data;
|
||||||
|
for(int i = 0; i < 8; i++) {
|
||||||
|
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read extra bytes 8-9
|
||||||
|
uint32_t extra = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||||
|
instance->raw_data[8] = (extra >> 8) & 0xFF;
|
||||||
|
instance->raw_data[9] = extra & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance rolling code (decrement counter, swap nibble interleaving)
|
||||||
|
chrysler_advance_rolling(instance->raw_data);
|
||||||
|
|
||||||
|
// Rebuild check bytes with (possibly changed) button
|
||||||
|
chrysler_encoder_rebuild(instance);
|
||||||
|
|
||||||
|
if(!chrysler_encoder_get_upload(instance)) {
|
||||||
|
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.repeat = CHRYSLER_ENCODER_REPEAT;
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
instance->encoder.is_running = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_chrysler_stop(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderChrysler* instance = context;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration subghz_protocol_encoder_chrysler_yield(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderChrysler* instance = context;
|
||||||
|
|
||||||
|
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
return level_duration_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||||
|
|
||||||
|
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||||
|
if(!subghz_block_generic_global.endless_tx) {
|
||||||
|
instance->encoder.repeat--;
|
||||||
|
}
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
chrysler_advance_rolling(instance->raw_data);
|
||||||
|
chrysler_encoder_rebuild(instance);
|
||||||
|
chrysler_encoder_get_upload(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder
|
||||||
|
|
||||||
|
static void chrysler_parse_data(SubGhzProtocolDecoderChrysler* instance) {
|
||||||
|
uint8_t* d = instance->raw_data;
|
||||||
|
|
||||||
|
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||||
|
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||||
|
uint8_t dev_id = d[0] & 0xF;
|
||||||
|
uint8_t msb = (d[0] >> 7) & 1;
|
||||||
|
|
||||||
|
// Determine button from b1^b6 mask
|
||||||
|
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||||
|
uint8_t btn = 0;
|
||||||
|
if(msb == 0) {
|
||||||
|
if(b1_xor_b6 == 0x04)
|
||||||
|
btn = 0x01; // Lock
|
||||||
|
else if(b1_xor_b6 == 0x08)
|
||||||
|
btn = 0x02; // Unlock
|
||||||
|
else
|
||||||
|
btn = 0x00;
|
||||||
|
} else {
|
||||||
|
btn = 0xFF; // Can't distinguish from MSB=1 mask (both = 0x62)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial: XOR offsets between byte positions (constant per device)
|
||||||
|
// We derive it from the relationship between byte positions
|
||||||
|
// serial_bytes[i] = rolling_value XOR bytes[1+i]_rolling_nibble
|
||||||
|
// Since all positions share the same LFSR, XOR between positions is the serial
|
||||||
|
|
||||||
|
instance->generic.serial =
|
||||||
|
((uint32_t)(d[1] ^ d[2]) << 24) |
|
||||||
|
((uint32_t)(d[1] ^ d[3]) << 16) |
|
||||||
|
((uint32_t)(d[1] ^ d[4]) << 8) |
|
||||||
|
((uint32_t)dev_id);
|
||||||
|
|
||||||
|
instance->generic.cnt = cnt;
|
||||||
|
instance->generic.btn = (btn != 0xFF) ? btn : 0;
|
||||||
|
|
||||||
|
// Store full 80-bit data
|
||||||
|
instance->generic.data =
|
||||||
|
((uint64_t)d[0] << 56) | ((uint64_t)d[1] << 48) |
|
||||||
|
((uint64_t)d[2] << 40) | ((uint64_t)d[3] << 32) |
|
||||||
|
((uint64_t)d[4] << 24) | ((uint64_t)d[5] << 16) |
|
||||||
|
((uint64_t)d[6] << 8) | ((uint64_t)d[7]);
|
||||||
|
instance->generic.data_count_bit = CHRYSLER_DATA_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool chrysler_validate(SubGhzProtocolDecoderChrysler* instance) {
|
||||||
|
uint8_t* d = instance->raw_data;
|
||||||
|
uint8_t msb = (d[0] >> 7) & 1;
|
||||||
|
|
||||||
|
// Check byte 5: should be b1 XOR 0xC3 (MSB=0) or b1 (MSB=1)
|
||||||
|
if(msb == 0) {
|
||||||
|
if(d[5] != (d[1] ^ 0xC3)) return false;
|
||||||
|
} else {
|
||||||
|
if(d[5] != d[1]) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bytes 6-9 vs 1-4 XOR mask consistency
|
||||||
|
// b1^b6 should be a known mask
|
||||||
|
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||||
|
if(msb == 0) {
|
||||||
|
if(b1_xor_b6 != 0x04 && b1_xor_b6 != 0x08) return false;
|
||||||
|
} else {
|
||||||
|
if(b1_xor_b6 != 0x62) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bytes 2-4 vs 7-9 XOR mask is consistent
|
||||||
|
// The XOR mask for bytes 2-4 vs 7-9 should be the same across all 3 pairs
|
||||||
|
uint8_t mask2 = d[2] ^ d[7];
|
||||||
|
uint8_t mask3 = d[3] ^ d[8];
|
||||||
|
uint8_t mask4 = d[4] ^ d[9];
|
||||||
|
|
||||||
|
// Masks should be one of the known patterns
|
||||||
|
if(msb == 0) {
|
||||||
|
if(mask2 != 0x63 || mask3 != 0x59 || mask4 != 0x46) return false;
|
||||||
|
} else {
|
||||||
|
// MSB=1 masks: 9A C6 20 or 9A C6 10
|
||||||
|
if(mask2 != 0x9A || mask3 != 0xC6) return false;
|
||||||
|
if(mask4 != 0x20 && mask4 != 0x10) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chrysler_rebuild_raw_data(SubGhzProtocolDecoderChrysler* instance) {
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
uint64_t key = instance->generic.data;
|
||||||
|
for(int i = 0; i < 8; i++) {
|
||||||
|
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||||
|
}
|
||||||
|
instance->bit_count = instance->generic.data_count_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_chrysler_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = calloc(1, sizeof(SubGhzProtocolDecoderChrysler));
|
||||||
|
furi_check(instance);
|
||||||
|
instance->base.protocol = &subghz_protocol_chrysler;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_chrysler_free(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_chrysler_reset(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = context;
|
||||||
|
instance->decoder_state = ChryslerDecoderStepReset;
|
||||||
|
instance->preamble_count = 0;
|
||||||
|
instance->bit_count = 0;
|
||||||
|
instance->te_last = 0;
|
||||||
|
instance->generic.data = 0;
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_chrysler_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder_state) {
|
||||||
|
case ChryslerDecoderStepReset:
|
||||||
|
if(level && duration <= CHRYSLER_SHORT_MAX && duration > 100) {
|
||||||
|
instance->te_last = duration;
|
||||||
|
instance->decoder_state = ChryslerDecoderStepPreamble;
|
||||||
|
instance->preamble_count = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChryslerDecoderStepPreamble:
|
||||||
|
if(!level) {
|
||||||
|
uint32_t total = instance->te_last + duration;
|
||||||
|
if(DURATION_DIFF(total, CHRYSLER_BIT_PERIOD) < CHRYSLER_BIT_TOLERANCE &&
|
||||||
|
instance->te_last <= CHRYSLER_SHORT_MAX) {
|
||||||
|
instance->preamble_count++;
|
||||||
|
} else if(duration > CHRYSLER_PREAMBLE_GAP &&
|
||||||
|
instance->preamble_count >= CHRYSLER_PREAMBLE_MIN) {
|
||||||
|
instance->decoder_state = ChryslerDecoderStepGap;
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = ChryslerDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(duration <= CHRYSLER_SHORT_MAX && duration > 100) {
|
||||||
|
instance->te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = ChryslerDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChryslerDecoderStepGap:
|
||||||
|
if(level) {
|
||||||
|
instance->te_last = duration;
|
||||||
|
instance->bit_count = 0;
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
instance->decoder_state = ChryslerDecoderStepData;
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = ChryslerDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChryslerDecoderStepData:
|
||||||
|
if(level) {
|
||||||
|
instance->te_last = duration;
|
||||||
|
} else {
|
||||||
|
uint32_t total = instance->te_last + duration;
|
||||||
|
if(DURATION_DIFF(total, CHRYSLER_BIT_PERIOD) < CHRYSLER_BIT_TOLERANCE) {
|
||||||
|
bool bit_val = (instance->te_last >= CHRYSLER_LONG_MIN);
|
||||||
|
|
||||||
|
if(instance->bit_count < CHRYSLER_DATA_BITS) {
|
||||||
|
uint8_t byte_idx = instance->bit_count / 8;
|
||||||
|
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||||
|
if(bit_val) {
|
||||||
|
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||||
|
}
|
||||||
|
instance->bit_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(instance->bit_count == CHRYSLER_DATA_BITS) {
|
||||||
|
if(chrysler_validate(instance)) {
|
||||||
|
chrysler_parse_data(instance);
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance->decoder_state = ChryslerDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(instance->bit_count >= CHRYSLER_DATA_BITS) {
|
||||||
|
if(chrysler_validate(instance)) {
|
||||||
|
chrysler_parse_data(instance);
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance->decoder_state = ChryslerDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_chrysler_get_hash_data(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = context;
|
||||||
|
SubGhzBlockDecoder dec = {
|
||||||
|
.decode_data = instance->generic.data,
|
||||||
|
.decode_count_bit = instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
|
||||||
|
};
|
||||||
|
return subghz_protocol_blocks_get_hash_data(&dec, (dec.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = context;
|
||||||
|
|
||||||
|
SubGhzProtocolStatus ret =
|
||||||
|
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
|
||||||
|
if(ret == SubGhzProtocolStatusOk) {
|
||||||
|
uint32_t extra = ((uint32_t)instance->raw_data[8] << 8) | instance->raw_data[9];
|
||||||
|
flipper_format_write_uint32(flipper_format, "Extra", &extra, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = context;
|
||||||
|
|
||||||
|
SubGhzProtocolStatus ret =
|
||||||
|
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||||
|
|
||||||
|
if(ret == SubGhzProtocolStatusOk) {
|
||||||
|
chrysler_rebuild_raw_data(instance);
|
||||||
|
|
||||||
|
uint32_t extra = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||||
|
instance->raw_data[8] = (extra >> 8) & 0xFF;
|
||||||
|
instance->raw_data[9] = extra & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* chrysler_button_name(uint8_t btn) {
|
||||||
|
switch(btn) {
|
||||||
|
case 0x01:
|
||||||
|
return "Lock";
|
||||||
|
case 0x02:
|
||||||
|
return "Unlock";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_chrysler_get_string(void* context, FuriString* output) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolDecoderChrysler* instance = context;
|
||||||
|
|
||||||
|
uint8_t* d = instance->raw_data;
|
||||||
|
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||||
|
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||||
|
uint8_t dev_id = d[0] & 0xF;
|
||||||
|
uint8_t msb = (d[0] >> 7) & 1;
|
||||||
|
|
||||||
|
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||||
|
uint8_t btn = instance->generic.btn;
|
||||||
|
if(msb == 0) {
|
||||||
|
if(b1_xor_b6 == 0x04)
|
||||||
|
btn = 0x01;
|
||||||
|
else if(b1_xor_b6 == 0x08)
|
||||||
|
btn = 0x02;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Raw:%02X%02X%02X%02X%02X %02X%02X%02X%02X%02X\r\n"
|
||||||
|
"Cnt:%X Btn:%s Dev:%X\r\n"
|
||||||
|
"Sn:%08lX\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
(int)instance->generic.data_count_bit,
|
||||||
|
d[0], d[1], d[2], d[3], d[4],
|
||||||
|
d[5], d[6], d[7], d[8], d[9],
|
||||||
|
(unsigned)cnt,
|
||||||
|
chrysler_button_name(btn),
|
||||||
|
(unsigned)dev_id,
|
||||||
|
(unsigned long)instance->generic.serial);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#define CHRYSLER_PROTOCOL_NAME "Chrysler"
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderChrysler SubGhzProtocolDecoderChrysler;
|
||||||
|
typedef struct SubGhzProtocolEncoderChrysler SubGhzProtocolEncoderChrysler;
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_chrysler;
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_chrysler_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_decoder_chrysler_free(void* context);
|
||||||
|
void subghz_protocol_decoder_chrysler_reset(void* context);
|
||||||
|
void subghz_protocol_decoder_chrysler_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t subghz_protocol_decoder_chrysler_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_decoder_chrysler_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
|
void* subghz_protocol_encoder_chrysler_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_encoder_chrysler_free(void* context);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_encoder_chrysler_stop(void* context);
|
||||||
|
LevelDuration subghz_protocol_encoder_chrysler_yield(void* context);
|
||||||
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;
|
||||||
@@ -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,9 +59,11 @@ 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,
|
||||||
|
// &subghz_protocol_bmw_cas4,
|
||||||
&subghz_protocol_subaru,
|
&subghz_protocol_subaru,
|
||||||
&subghz_protocol_mazda_siemens,
|
&subghz_protocol_mazda_siemens,
|
||||||
&subghz_protocol_kia_v0,
|
&subghz_protocol_kia_v0,
|
||||||
@@ -70,11 +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_chrysler,
|
||||||
|
&honda_static_protocol,
|
||||||
|
//&subghz_protocol_honda,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
#include "psa.h"
|
#include "psa.h"
|
||||||
#include "fiat_spa.h"
|
#include "fiat_spa.h"
|
||||||
#include "fiat_marelli.h"
|
#include "fiat_marelli.h"
|
||||||
|
#include "bmw_cas4.h"
|
||||||
#include "subaru.h"
|
#include "subaru.h"
|
||||||
#include "kia_generic.h"
|
#include "kia_generic.h"
|
||||||
#include "kia_v0.h"
|
#include "kia_v0.h"
|
||||||
@@ -71,9 +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 "chrysler.h"
|
||||||
|
#include "honda_static.h"
|
||||||
|
#include "ford_v1.h"
|
||||||
|
//#include "honda_pandora.h"
|
||||||
|
|||||||
+111
-101
@@ -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);
|
||||||
@@ -743,14 +750,6 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
}
|
}
|
||||||
if(end_diff <= 199) {
|
if(end_diff <= 199) {
|
||||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||||
if((instance->validation_field & 0xf) != 0xa) {
|
|
||||||
instance->decode_data_low = 0;
|
|
||||||
instance->decode_data_high = 0;
|
|
||||||
instance->decode_count_bit = 0;
|
|
||||||
new_state = PSADecoderState0;
|
|
||||||
instance->state = new_state;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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;
|
||||||
instance->mode_serialize = 1;
|
instance->mode_serialize = 1;
|
||||||
@@ -766,6 +765,17 @@ 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 &&
|
||||||
|
(instance->validation_field & 0xf) != 0xa) {
|
||||||
|
instance->decode_data_low = 0;
|
||||||
|
instance->decode_data_high = 0;
|
||||||
|
instance->decode_count_bit = 0;
|
||||||
|
new_state = PSADecoderState0;
|
||||||
|
instance->state = new_state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
||||||
instance->generic.data_count_bit = 64;
|
instance->generic.data_count_bit = 64;
|
||||||
instance->decoder.decode_data = instance->generic.data;
|
instance->decoder.decode_data = instance->generic.data;
|
||||||
@@ -932,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;
|
||||||
@@ -972,76 +978,26 @@ 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);
|
||||||
if((instance->validation_field & 0xf) != 0xa) {
|
|
||||||
instance->decode_data_low = 0;
|
|
||||||
instance->decode_data_high = 0;
|
|
||||||
instance->decode_count_bit = 0;
|
|
||||||
new_state = PSADecoderState0;
|
|
||||||
instance->state = new_state;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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;
|
||||||
instance->mode_serialize = 2;
|
instance->mode_serialize = 2;
|
||||||
@@ -1057,6 +1013,16 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
|||||||
instance->mode_serialize = 0x36;
|
instance->mode_serialize = 0x36;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(instance->decrypted != 0x50 &&
|
||||||
|
(instance->validation_field & 0xf) != 0xa) {
|
||||||
|
instance->decode_data_low = 0;
|
||||||
|
instance->decode_data_high = 0;
|
||||||
|
instance->decode_count_bit = 0;
|
||||||
|
new_state = PSADecoderState0;
|
||||||
|
instance->state = new_state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
||||||
instance->generic.data_count_bit = 64;
|
instance->generic.data_count_bit = 64;
|
||||||
instance->decoder.decode_data = instance->generic.data;
|
instance->decoder.decode_data = instance->generic.data;
|
||||||
@@ -1070,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