Files
unleashed-firmware/lib/subghz/protocols/chamberlain_code.c
T
Mykhailo ShevchukandClaude Opus 5 5629fc5e17 SubGHz + Storage: share more duplicated bodies (-4,892 B of flash) (#1116)
* SubGhz: share the remaining duplicated serialize handlers

Follow-up to af2370573. Two families of decoder serialize handlers were still
copied per protocol: the one writing generic.data_2 as "Data" (beninca_arc,
jarolift, kinggates_stylo_4k) and the one writing TE (holtek_th12x, legrand,
smc5326). Both now live in common.c and are pointed at straight from the
vtable, so the per-protocol functions and their declarations are gone.

The data_2 block also existed three more times in the encoder create_data
paths of those same files; they share the helper too. princeton is that TE
handler plus a Guard_time write, so it calls the helper and adds its own
field. honeywell had generic and decoder in the reverse order, which was the
only reason af2370573 could not fold six of its handlers - swapping the two
members folds them.

The helpers log through generic.protocol_name instead of each file's TAG, so
they match the vtable slot signature and need no per-protocol wrapper. Two
diagnostics-only consequences: converted paths log under the protocol's
user-facing name rather than TAG, and the encoder create_data failure message
merges with the serialize one. Nothing in the tree filters on log tags.

SUBGHZ_ASSERT_*_LAYOUT pins the punned prefixes at compile time. Pinning the
tail offset alone would not have caught honeywell's swap: decoder and generic
are both multiples of 8, so te lands at the same offset either way. Verified
by restoring the old order and watching the build fail.

.text 696,740 -> 695,828 (-912 B), .rodata unchanged, api_symbols.csv untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SubGhz: share the protocol alloc bodies

48 decoder and 48 encoder alloc functions differed only in the struct type,
the protocol descriptor and, for encoders, the repeat and upload-buffer
constants. They now call subghz_protocol_{decoder,encoder}_common_alloc and
keep a thunk each - the vtable slot passes only SubGhzEnvironment*, so the
per-protocol constants have to come from somewhere.

Keeping the thunk rather than making alloc data-driven is deliberate. Hanging
instance_size/repeat/size_upload off SubGhzProtocol and letting alloc be NULL
would remove all 96 thunks, but it moves the same constants into .rodata at
about 12 B per protocol across the whole registry, adds a dispatch branch, and
changes a struct that is FAP ABI (raw.h is an exported SDK header and includes
base.h). Net saving is roughly zero.

SubGhzProtocolEncoderCommon stays {base, encoder} so protocols that only use
free/stop/yield are not forced to carry a generic; the alloc helper puns
through a nested SubGhzProtocolEncoderCommonGeneric instead, mirroring how
SubGhzProtocolDecoderCommonTe extends the decoder type.

SUBGHZ_ASSERT_*_LAYOUT now covers all 114 structs any shared helper puns
through, not just the ones converted here - 14 of those were already being
punned by free/stop/yield/reset/serialize with no guard at all. The asserts
cost no flash.

23 allocs keep their own body for doing real extra work (keystore setup,
rainbow-table names, extra buffers). Most could call the helper and then add
their own lines the way princeton does for serialize, worth roughly 440 B;
left for a follow-up so this commit stays mechanical.

.text 695,828 -> 693,396 (-2,432 B), .rodata unchanged, api_symbols.csv untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SubGhz: share the deserialize bodies that only differ by a callback

Two more shared helpers in common.c:
- subghz_protocol_encoder_common_deserialize(ctx, ff, min_count_bit, get_upload)
  covers 11 encoders whose bodies were identical apart from the bit count and
  their get_upload
- subghz_protocol_decoder_common_deserialize_te(ctx, ff, min_count_bit) covers
  the three decoders that read TE back, mirroring the serialize_te helper

The per-protocol get_upload functions now take void* instead of being cast to
the callback type. Calling through an incompatible function pointer type is
formally undefined even though it is harmless on AAPCS, and enabling LTO is on
the roadmap for this same effort - not a combination worth keeping.

Deliberately not converted:

The 35 decoders whose deserialize is just a tail call to
subghz_block_generic_deserialize_check_count_bit compile to 8 bytes each. A
decoder thunk is 6 bytes, so folding them would gain about 70 B - real, but a
poor return for touching 35 files and adding a second hop to a call that is
already a tail call into shared code.

The six encoders that call a per-protocol check_remote_controller before
get_upload would need a fifth argument, which spills to the stack on AAPCS and
costs more per thunk than it saves. They can instead be folded into the helper
above by giving their get_upload a bool return and moving the check inside it,
which also reaches allstar_firefly/elplast and, once the shared stop resets
encoder.front, came_twee/marantec/power_smart/hormann/magellan - about 390 B
across 13 protocols with no new helper. Left as a follow-up because it changes
those functions rather than just moving them.

.text 693,396 -> 692,900 (-496 B), .rodata unchanged, api_symbols.csv untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Storage: share the command-dispatch bodies

Twelve public storage calls had the same S_API macro body and differed only in
the StorageCommand they sent. Four static helpers now carry it:

  storage_file_command_bool    file_truncate, file_sync, file_eof, dir_rewind
  storage_file_command_uint64  file_tell, file_size
  storage_sd_command           sd_format, sd_unmount, sd_mount, sd_status
  storage_path_command         common_remove, common_mkdir

Split by return macro on purpose - S_RETURN_BOOL, S_RETURN_UINT64 and
S_RETURN_ERROR read different members of the SAReturn union, so one helper for
all twelve would read the wrong member. Each pairing was checked against what
storage_processing.c actually writes.

The public symbols stay as one-line thunks: all twelve are exported in
api_symbols.csv, so the bodies could move but the symbols could not.

file_close and dir_close share the shape but do work between the epilogue and
the return, so they keep their own bodies.

.text 692,900 -> 692,296 (-604 B), .rodata unchanged, api_symbols.csv untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SubGhz: fold the allocs and deserializes the earlier reviews left behind

Follow-up to 84aca75eb and 5f4f4cc2b, both of which deliberately stopped at the
mechanical cases and listed what they skipped.

15 more allocs now call the shared helpers and keep only their own extra work -
keystore lookups, rainbow-table names, extra buffers: alutech_at_4n, beninca_arc,
bin_raw, dickert_mahs, faac_slh, jarolift, keeloq, kinggates_stylo_4k,
nice_flor_s, telcoma_edge.

8 more encoders join subghz_protocol_encoder_common_deserialize. Their
get_upload had no failure path and returned void, so it becomes
bool/void* and returns true; five of them also move their
check_remote_controller call from deserialize into the top of get_upload,
which needs a forward declaration since get_upload is defined above it.

min_count_bit widens from uint8_t to uint16_t in both deserialize helpers.
subghz_block_generic_deserialize_check_count_bit compares it against a
uint16_t, so the narrower parameter would have silently truncated any protocol
above 255 bits. None exists today; the tree's largest is 128.

The get_upload doc says "true Always; this encoder has no failure path" rather
than "on success". These eight have no bound check, unlike the eleven converted
in 5f4f4cc2b, and claiming success would imply a validation none of them do.
They cannot overrun today - deserialize_check_count_bit pins data_count_bit by
equality before get_upload runs - but keyfinder has only 4 spare entries in a
60-entry buffer, so adding the guards is worth its own commit.

Not done here, deliberately: the five moved check_remote_controller calls write
only serial/btn/cnt on the encoder instance, which nothing reads, so they could
be deleted outright rather than moved. Deleting a store is a behaviour change
and belongs with the encoder.front reset in its own commit.

.text 692,296 -> 691,848 (-448 B), .rodata unchanged, api_symbols.csv untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* upd changelog

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 17:31:16 +03:00

441 lines
17 KiB
C

#include "chamberlain_code.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "common.h"
#define TAG "SubGhzProtocolChambCode"
#define CHAMBERLAIN_CODE_BIT_STOP 0b0001
#define CHAMBERLAIN_CODE_BIT_1 0b0011
#define CHAMBERLAIN_CODE_BIT_0 0b0111
#define CHAMBERLAIN_7_CODE_MASK 0xF000000FF0F
#define CHAMBERLAIN_8_CODE_MASK 0xF00000F00F
#define CHAMBERLAIN_9_CODE_MASK 0xF000000000F
#define CHAMBERLAIN_7_CODE_MASK_CHECK 0x10000001101
#define CHAMBERLAIN_8_CODE_MASK_CHECK 0x1000001001
#define CHAMBERLAIN_9_CODE_MASK_CHECK 0x10000000001
#define CHAMBERLAIN_7_CODE_DIP_PATTERN "%c%c%c%c%c%c%c"
#define CHAMBERLAIN_7_CODE_DATA_TO_DIP(dip) \
(dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \
(dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \
(dip & 0x0001 ? '1' : '0')
#define CHAMBERLAIN_8_CODE_DIP_PATTERN "%c%c%c%c%cx%c%c"
#define CHAMBERLAIN_8_CODE_DATA_TO_DIP(dip) \
(dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), \
(dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \
(dip & 0x0002 ? '1' : '0')
#define CHAMBERLAIN_9_CODE_DIP_PATTERN "%c%c%c%c%c%c%c%c%c"
#define CHAMBERLAIN_9_CODE_DATA_TO_DIP(dip) \
(dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \
(dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), \
(dip & 0x0001 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), (dip & 0x0004 ? '1' : '0')
static const SubGhzBlockConst subghz_protocol_chamb_code_const = {
.te_short = 1000,
.te_long = 3000,
.te_delta = 200,
.min_count_bit_for_found = 10,
};
struct SubGhzProtocolDecoderChamb_Code {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
SUBGHZ_ASSERT_DECODER_COMMON_LAYOUT(SubGhzProtocolDecoderChamb_Code);
struct SubGhzProtocolEncoderChamb_Code {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
SUBGHZ_ASSERT_ENCODER_GENERIC_LAYOUT(SubGhzProtocolEncoderChamb_Code);
typedef enum {
Chamb_CodeDecoderStepReset = 0,
Chamb_CodeDecoderStepFoundStartBit,
Chamb_CodeDecoderStepSaveDuration,
Chamb_CodeDecoderStepCheckDuration,
} Chamb_CodeDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder = {
.alloc = subghz_protocol_decoder_chamb_code_alloc,
.free = subghz_protocol_decoder_common_free,
.feed = subghz_protocol_decoder_chamb_code_feed,
.reset = subghz_protocol_decoder_common_reset,
.get_hash_data = subghz_protocol_decoder_common_get_hash_data,
.serialize = subghz_protocol_decoder_common_serialize,
.deserialize = subghz_protocol_decoder_chamb_code_deserialize,
.get_string = subghz_protocol_decoder_chamb_code_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder = {
.alloc = subghz_protocol_encoder_chamb_code_alloc,
.free = subghz_protocol_encoder_common_free,
.deserialize = subghz_protocol_encoder_chamb_code_deserialize,
.stop = subghz_protocol_encoder_common_stop,
.yield = subghz_protocol_encoder_common_yield,
};
const SubGhzProtocol subghz_protocol_chamb_code = {
.name = SUBGHZ_PROTOCOL_CHAMB_CODE_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_chamb_code_decoder,
.encoder = &subghz_protocol_chamb_code_encoder,
};
void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
return subghz_protocol_encoder_common_alloc(
sizeof(SubGhzProtocolEncoderChamb_Code), &subghz_protocol_chamb_code, 3, 24);
}
static uint64_t subghz_protocol_chamb_bit_to_code(uint64_t data, uint8_t size) {
uint64_t data_res = 0;
for(uint8_t i = 0; i < size; i++) {
if(!(bit_read(data, size - i - 1))) {
data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_0;
} else {
data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_1;
}
}
return data_res;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderChamb_Code instance
* @return true On success
*/
static bool
subghz_protocol_encoder_chamb_code_get_upload(SubGhzProtocolEncoderChamb_Code* instance) {
furi_assert(instance);
uint64_t data = subghz_protocol_chamb_bit_to_code(
instance->generic.data, instance->generic.data_count_bit);
switch(instance->generic.data_count_bit) {
case 7:
data = ((data >> 4) << 16) | (data & 0xF) << 4 | CHAMBERLAIN_7_CODE_MASK_CHECK;
break;
case 8:
data = ((data >> 12) << 16) | (data & 0xFF) << 4 | CHAMBERLAIN_8_CODE_MASK_CHECK;
break;
case 9:
data = (data << 4) | CHAMBERLAIN_9_CODE_MASK_CHECK;
break;
default:
FURI_LOG_E(TAG, "Invalid bits count");
return false;
break;
}
#define UPLOAD_HEX_DATA_SIZE 10
uint8_t upload_hex_data[UPLOAD_HEX_DATA_SIZE] = {0};
size_t upload_hex_count_bit = 0;
//insert guard time
for(uint8_t i = 0; i < 36; i++) {
subghz_protocol_blocks_set_bit_array(
0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
}
//insert data
switch(instance->generic.data_count_bit) {
case 7:
case 9:
for(uint8_t i = 44; i > 0; i--) {
if(!bit_read(data, i - 1)) {
subghz_protocol_blocks_set_bit_array(
0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
} else {
subghz_protocol_blocks_set_bit_array(
1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
}
}
break;
case 8:
for(uint8_t i = 40; i > 0; i--) {
if(!bit_read(data, i - 1)) {
subghz_protocol_blocks_set_bit_array(
0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
} else {
subghz_protocol_blocks_set_bit_array(
1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
}
}
break;
}
instance->encoder.size_upload = subghz_protocol_blocks_get_upload_from_bit_array(
upload_hex_data,
upload_hex_count_bit,
instance->encoder.upload,
instance->encoder.size_upload,
subghz_protocol_chamb_code_const.te_short,
SubGhzProtocolBlockAlignBitLeft);
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderChamb_Code* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit >
subghz_protocol_chamb_code_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
return subghz_protocol_decoder_common_alloc(
sizeof(SubGhzProtocolDecoderChamb_Code), &subghz_protocol_chamb_code);
}
static bool subghz_protocol_chamb_code_to_bit(uint64_t* data, uint8_t size) {
uint64_t data_tmp = data[0];
uint64_t data_res = 0;
for(uint8_t i = 0; i < size; i++) {
if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_0) {
bit_write(data_res, i, 0);
} else if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_1) {
bit_write(data_res, i, 1);
} else {
return false;
}
data_tmp >>= 4;
}
data[0] = data_res;
return true;
}
static bool subghz_protocol_decoder_chamb_code_check_mask_and_parse(
SubGhzProtocolDecoderChamb_Code* instance) {
furi_assert(instance);
if(instance->decoder.decode_count_bit >
subghz_protocol_chamb_code_const.min_count_bit_for_found + 1)
return false;
if((instance->decoder.decode_data & CHAMBERLAIN_7_CODE_MASK) ==
CHAMBERLAIN_7_CODE_MASK_CHECK) {
instance->decoder.decode_count_bit = 7;
instance->decoder.decode_data &= ~CHAMBERLAIN_7_CODE_MASK;
instance->decoder.decode_data = (instance->decoder.decode_data >> 12) |
((instance->decoder.decode_data >> 4) & 0xF);
} else if(
(instance->decoder.decode_data & CHAMBERLAIN_8_CODE_MASK) ==
CHAMBERLAIN_8_CODE_MASK_CHECK) {
instance->decoder.decode_count_bit = 8;
instance->decoder.decode_data &= ~CHAMBERLAIN_8_CODE_MASK;
instance->decoder.decode_data = instance->decoder.decode_data >> 4 |
CHAMBERLAIN_CODE_BIT_0 << 8; //DIP 6 no use
} else if(
(instance->decoder.decode_data & CHAMBERLAIN_9_CODE_MASK) ==
CHAMBERLAIN_9_CODE_MASK_CHECK) {
instance->decoder.decode_count_bit = 9;
instance->decoder.decode_data &= ~CHAMBERLAIN_9_CODE_MASK;
instance->decoder.decode_data >>= 4;
} else {
return false;
}
return subghz_protocol_chamb_code_to_bit(
&instance->decoder.decode_data, instance->decoder.decode_count_bit);
}
void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
switch(instance->decoder.parser_step) {
case Chamb_CodeDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 39) <
subghz_protocol_chamb_code_const.te_delta * 20)) {
//Found header Chamb_Code
instance->decoder.parser_step = Chamb_CodeDecoderStepFoundStartBit;
}
break;
case Chamb_CodeDecoderStepFoundStartBit:
if((level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) <
subghz_protocol_chamb_code_const.te_delta)) {
//Found start bit Chamb_Code
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_STOP;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
break;
case Chamb_CodeDecoderStepSaveDuration:
if(!level) { //save interval
if(duration > subghz_protocol_chamb_code_const.te_short * 5) {
if(instance->decoder.decode_count_bit >=
subghz_protocol_chamb_code_const.min_count_bit_for_found) {
instance->generic.serial = 0x0;
instance->generic.btn = 0x0;
if(subghz_protocol_decoder_chamb_code_check_mask_and_parse(instance)) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Chamb_CodeDecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
break;
case Chamb_CodeDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF( //Found stop bit Chamb_Code
instance->decoder.te_last,
subghz_protocol_chamb_code_const.te_short * 3) <
subghz_protocol_chamb_code_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) <
subghz_protocol_chamb_code_const.te_delta)) {
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_STOP;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short * 2) <
subghz_protocol_chamb_code_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 2) <
subghz_protocol_chamb_code_const.te_delta)) {
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_1;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short) <
subghz_protocol_chamb_code_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 3) <
subghz_protocol_chamb_code_const.te_delta)) {
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_0;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
break;
}
}
SubGhzProtocolStatus
subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit >
subghz_protocol_chamb_code_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
} while(false);
return ret;
}
void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff;
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key:0x%03lX\r\n"
"Yek:0x%03lX\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_lo,
code_found_reverse_lo);
switch(instance->generic.data_count_bit) {
case 7:
furi_string_cat_printf(
output,
"DIP:" CHAMBERLAIN_7_CODE_DIP_PATTERN "\r\n",
CHAMBERLAIN_7_CODE_DATA_TO_DIP(code_found_lo));
break;
case 8:
furi_string_cat_printf(
output,
"DIP:" CHAMBERLAIN_8_CODE_DIP_PATTERN "\r\n",
CHAMBERLAIN_8_CODE_DATA_TO_DIP(code_found_lo));
break;
case 9:
furi_string_cat_printf(
output,
"DIP:" CHAMBERLAIN_9_CODE_DIP_PATTERN "\r\n",
CHAMBERLAIN_9_CODE_DATA_TO_DIP(code_found_lo));
break;
default:
break;
}
}