mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 04:41:26 +04:00
SubGHz: Added 9 new protocols, fixes to existing protocols (#4255)
* Fix Typos * Tune decoders * Better parsing, show more data in existing protocols * Add new protocols * Update keeloqs * Add unit tests & raws * Add honeywell unittest * Comment until better solution is found Adding GAPs to be sent first to make signal better suitable for decoder (decoding from only one signal sample) does nothing, needs something else TODO: Fix encoders? * suppressed missing issue warning * subghz: re-enabled failing encoder tests * Fix two? 3 left * properly do gangqi and marantec for unit test and real use * fix unit tests now * fix possible memory leak * reset decoder step too * subghz: extra encoder safety; report random signal test results on failure * unit_tests: subghz: renamed test file for consistency * subghz: more explicit buffer position resets * Fix gangqi samples --------- Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech at-4n"
|
||||
#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech AT-4N"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderAlutech_at_4n SubGhzProtocolDecoderAlutech_at_4n;
|
||||
typedef struct SubGhzProtocolEncoderAlutech_at_4n SubGhzProtocolEncoderAlutech_at_4n;
|
||||
|
||||
@@ -244,8 +244,11 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat
|
||||
switch(instance->decoder.parser_step) {
|
||||
case CameDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 56) <
|
||||
subghz_protocol_came_const.te_delta * 47)) {
|
||||
subghz_protocol_came_const.te_delta * 63)) {
|
||||
// 17920 us + 7050 us = 24970 us max possible value old one
|
||||
// delta = 150 us x 63 = 9450 us + 17920 us = 27370 us max possible value
|
||||
//Found header CAME
|
||||
// 26700 us or 24000 us max possible values
|
||||
instance->decoder.parser_step = CameDecoderStepFoundStartBit;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -260,6 +260,7 @@ SubGhzProtocolStatus
|
||||
|
||||
subghz_protocol_came_twee_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_came_twee_get_upload(instance);
|
||||
instance->encoder.front = 0; // reset position before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -269,6 +270,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_came_twee_stop(void* context) {
|
||||
SubGhzProtocolEncoderCameTwee* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) {
|
||||
|
||||
@@ -308,7 +308,7 @@ void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t dura
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
/*
|
||||
* serial s/m ch key
|
||||
* long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011
|
||||
@@ -416,7 +416,7 @@ void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output)
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDooya* instance = context;
|
||||
|
||||
subghz_protocol_somfy_telis_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_dooya_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
|
||||
352
lib/subghz/protocols/feron.c
Normal file
352
lib/subghz/protocols/feron.c
Normal file
@@ -0,0 +1,352 @@
|
||||
#include "feron.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolFeron"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_feron_const = {
|
||||
.te_short = 350,
|
||||
.te_long = 750,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 32,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderFeron {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderFeron {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FeronDecoderStepReset = 0,
|
||||
FeronDecoderStepSaveDuration,
|
||||
FeronDecoderStepCheckDuration,
|
||||
} FeronDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_feron_decoder = {
|
||||
.alloc = subghz_protocol_decoder_feron_alloc,
|
||||
.free = subghz_protocol_decoder_feron_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_feron_feed,
|
||||
.reset = subghz_protocol_decoder_feron_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_feron_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_feron_serialize,
|
||||
.deserialize = subghz_protocol_decoder_feron_deserialize,
|
||||
.get_string = subghz_protocol_decoder_feron_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_feron_encoder = {
|
||||
.alloc = subghz_protocol_encoder_feron_alloc,
|
||||
.free = subghz_protocol_encoder_feron_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_feron_deserialize,
|
||||
.stop = subghz_protocol_encoder_feron_stop,
|
||||
.yield = subghz_protocol_encoder_feron_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_feron = {
|
||||
.name = SUBGHZ_PROTOCOL_FERON_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_feron_decoder,
|
||||
.encoder = &subghz_protocol_feron_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFeron* instance = malloc(sizeof(SubGhzProtocolEncoderFeron));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_feron;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_feron_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_feron_get_upload(SubGhzProtocolEncoderFeron* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send 500/500 and gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
true, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
// Gap
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send 500/500 and gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
true, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
// Gap
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_feron_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->serial = instance->data >> 16;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_feron_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_feron_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_feron_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_feron_stop(void* context) {
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_feron_yield(void* context) {
|
||||
SubGhzProtocolEncoderFeron* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFeron* instance = malloc(sizeof(SubGhzProtocolDecoderFeron));
|
||||
instance->base.protocol = &subghz_protocol_feron;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
|
||||
// Feron Decoder
|
||||
// 2025.04 - @xMasterX (MMX)
|
||||
|
||||
// Key samples
|
||||
/*
|
||||
0110001100111000 1000010101111010 - ON
|
||||
0110001100111000 1000010001111011 - OFF
|
||||
|
||||
0110001100111000 1000011001111001 - brightness up
|
||||
0110001100111000 1000011101111000 - brightness down
|
||||
|
||||
0110001100111000 1000001001111101 - scroll mode command
|
||||
|
||||
------------------------------------------
|
||||
0110001100111000 0111000010001111 - R
|
||||
0110001100111000 0001101011100101 - B
|
||||
0110001100111000 0100000010111111 - G
|
||||
*/
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FeronDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long * 6) <
|
||||
subghz_protocol_feron_const.te_delta * 4)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case FeronDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = FeronDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case FeronDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short and long timing = 350us HIGH (te_last) and 750us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) <
|
||||
subghz_protocol_feron_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_feron_const.te_long) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
|
||||
// Bit 1 is long and short timing = 750us HIGH (te_last) and 350us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) <
|
||||
subghz_protocol_feron_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_feron_const.te_short) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key 500Low(we are here)/500High us
|
||||
DURATION_DIFF(
|
||||
duration, (uint16_t)(subghz_protocol_feron_const.te_short + (uint16_t)150)) <
|
||||
subghz_protocol_feron_const.te_delta) {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 32 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_feron_const.min_count_bit_for_found) {
|
||||
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.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_feron_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
|
||||
subghz_protocol_feron_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%08lX\r\n"
|
||||
"Serial: 0x%04lX\r\n"
|
||||
"Command: 0x%04lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
(uint32_t)(instance->generic.data & 0xFFFF));
|
||||
}
|
||||
109
lib/subghz/protocols/feron.h
Normal file
109
lib/subghz/protocols/feron.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_FERON_NAME "Feron"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFeron SubGhzProtocolDecoderFeron;
|
||||
typedef struct SubGhzProtocolEncoderFeron SubGhzProtocolEncoderFeron;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_feron_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_feron_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_feron;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderFeron.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderFeron* pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_encoder_feron_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_encoder_feron_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_feron_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderFeron.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderFeron* pointer to a SubGhzProtocolDecoderFeron instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output);
|
||||
407
lib/subghz/protocols/gangqi.c
Normal file
407
lib/subghz/protocols/gangqi.c
Normal file
@@ -0,0 +1,407 @@
|
||||
#include "gangqi.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolGangQi"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_gangqi_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1200,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 34,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderGangQi {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderGangQi {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GangQiDecoderStepReset = 0,
|
||||
GangQiDecoderStepSaveDuration,
|
||||
GangQiDecoderStepCheckDuration,
|
||||
} GangQiDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder = {
|
||||
.alloc = subghz_protocol_decoder_gangqi_alloc,
|
||||
.free = subghz_protocol_decoder_gangqi_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_gangqi_feed,
|
||||
.reset = subghz_protocol_decoder_gangqi_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_gangqi_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_gangqi_serialize,
|
||||
.deserialize = subghz_protocol_decoder_gangqi_deserialize,
|
||||
.get_string = subghz_protocol_decoder_gangqi_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder = {
|
||||
.alloc = subghz_protocol_encoder_gangqi_alloc,
|
||||
.free = subghz_protocol_encoder_gangqi_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_gangqi_deserialize,
|
||||
.stop = subghz_protocol_encoder_gangqi_stop,
|
||||
.yield = subghz_protocol_encoder_gangqi_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_gangqi = {
|
||||
.name = SUBGHZ_PROTOCOL_GANGQI_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_gangqi_decoder,
|
||||
.encoder = &subghz_protocol_gangqi_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderGangQi* instance = malloc(sizeof(SubGhzProtocolEncoderGangQi));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_gangqi;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 1024;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_gangqi_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQi* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
// Generate new key
|
||||
uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF);
|
||||
uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn);
|
||||
uint8_t serial_high = (uint8_t)(serial >> 8);
|
||||
uint8_t serial_low = (uint8_t)(serial & 0xFF);
|
||||
uint8_t bytesum = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button);
|
||||
|
||||
instance->generic.data = (instance->generic.data >> 14) << 14 | (instance->generic.btn << 10) |
|
||||
(bytesum << 2);
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long * 2);
|
||||
|
||||
for(size_t r = 0; r < 5; r++) {
|
||||
// Send key and GAP between parcels
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
|
||||
subghz_protocol_gangqi_const.te_delta);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_gangqi_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
|
||||
subghz_protocol_gangqi_const.te_delta);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data and parsing serial number
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_gangqi_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->btn = (instance->data >> 10) & 0xF;
|
||||
instance->serial = (instance->data & 0xFFFFF0000) >> 16;
|
||||
|
||||
// GangQi Decoder
|
||||
// 09.2024 - @xMasterX (MMX) (last update - bytesum calculation at 02.2025)
|
||||
// Thanks @Skorpionm for support!
|
||||
// Thanks @Drone1950 and @mishamyte (who spent 2 weeks on this) for making this work properly
|
||||
|
||||
// Example of correct bytesum calculation
|
||||
// 0xC8 - serial_high - serial_low - constant_and_button
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_gangqi_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_gangqi_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_gangqi_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Key");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_gangqi_stop(void* context) {
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) {
|
||||
SubGhzProtocolEncoderGangQi* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderGangQi* instance = malloc(sizeof(SubGhzProtocolDecoderGangQi));
|
||||
instance->base.protocol = &subghz_protocol_gangqi;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case GangQiDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
|
||||
subghz_protocol_gangqi_const.te_delta * 5)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case GangQiDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = GangQiDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case GangQiDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short and long timing
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
|
||||
subghz_protocol_gangqi_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
|
||||
// Bit 1 is long and short timing
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
|
||||
subghz_protocol_gangqi_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
|
||||
subghz_protocol_gangqi_const.te_delta * 5) {
|
||||
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 34 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_gangqi_const.min_count_bit_for_found) {
|
||||
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.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button name.
|
||||
* @param btn Button number, 4 bit
|
||||
*/
|
||||
static const char* subghz_protocol_gangqi_get_button_name(uint8_t btn) {
|
||||
const char* name_btn[16] = {
|
||||
"Unknown",
|
||||
"Exit settings",
|
||||
"Volume setting",
|
||||
"0x3",
|
||||
"Vibro sens. setting",
|
||||
"Settings mode",
|
||||
"Ringtone setting",
|
||||
"Ring", // D
|
||||
"0x8",
|
||||
"0x9",
|
||||
"0xA",
|
||||
"Alarm", // C
|
||||
"0xC",
|
||||
"Arm", // A
|
||||
"Disarm", // B
|
||||
"0xF"};
|
||||
return btn <= 0xf ? name_btn[btn] : name_btn[0];
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_gangqi_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
|
||||
// Parse serial
|
||||
subghz_protocol_gangqi_remote_controller(&instance->generic);
|
||||
|
||||
// Get byte sum
|
||||
uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF);
|
||||
uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn);
|
||||
uint8_t serial_high = (uint8_t)(serial >> 8);
|
||||
uint8_t serial_low = (uint8_t)(serial & 0xFF);
|
||||
// Type 1 is what original remotes use, type 2 is "backdoor" sum that receiver accepts too
|
||||
uint8_t sum_type1 = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button);
|
||||
uint8_t sum_type2 = (uint8_t)(0x02 + serial_high + serial_low + const_and_button);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%X%08lX\r\n"
|
||||
"Serial: 0x%05lX\r\n"
|
||||
"Sum: 0x%02X Sum2: 0x%02X\r\n"
|
||||
"Btn: 0x%01X - %s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint8_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
sum_type1,
|
||||
sum_type2,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_gangqi_get_button_name(instance->generic.btn));
|
||||
}
|
||||
109
lib/subghz/protocols/gangqi.h
Normal file
109
lib/subghz/protocols/gangqi.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_GANGQI_NAME "GangQi"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderGangQi SubGhzProtocolDecoderGangQi;
|
||||
typedef struct SubGhzProtocolEncoderGangQi SubGhzProtocolEncoderGangQi;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_gangqi;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderGangQi.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderGangQi* pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_encoder_gangqi_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_encoder_gangqi_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderGangQi.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderGangQi* pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output);
|
||||
269
lib/subghz/protocols/hay21.c
Normal file
269
lib/subghz/protocols/hay21.c
Normal file
@@ -0,0 +1,269 @@
|
||||
#include "hay21.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolHay21"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_hay21_const = {
|
||||
.te_short = 300,
|
||||
.te_long = 700,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 21,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderHay21 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHay21 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Hay21DecoderStepReset = 0,
|
||||
Hay21DecoderStepSaveDuration,
|
||||
Hay21DecoderStepCheckDuration,
|
||||
} Hay21DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_hay21_decoder = {
|
||||
.alloc = subghz_protocol_decoder_hay21_alloc,
|
||||
.free = subghz_protocol_decoder_hay21_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_hay21_feed,
|
||||
.reset = subghz_protocol_decoder_hay21_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_hay21_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_hay21_serialize,
|
||||
.deserialize = subghz_protocol_decoder_hay21_deserialize,
|
||||
.get_string = subghz_protocol_decoder_hay21_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_hay21_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_hay21 = {
|
||||
.name = SUBGHZ_PROTOCOL_HAY21_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_hay21_decoder,
|
||||
.encoder = &subghz_protocol_hay21_encoder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Analysis of received data and parsing serial number
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_hay21_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->btn = (instance->data >> 13) & 0xFF;
|
||||
instance->serial = (instance->data >> 5) & 0xFF;
|
||||
instance->cnt = (instance->data >> 1) & 0xF;
|
||||
|
||||
// Hay21 Decoder
|
||||
// 09.2024 - @xMasterX (MMX)
|
||||
|
||||
// Key samples (inverted)
|
||||
// button serial CNT (goes lower since 0/1 are inverted)
|
||||
//14A84A = 000 10100101 01000010 0101 0 (cnt 5)
|
||||
//14A848 = 000 10100101 01000010 0100 0 (cnt 4)
|
||||
//14A846 = 000 10100101 01000010 0011 0 (cnt 3)
|
||||
//14A844 = 000 10100101 01000010 0010 0 (cnt 2)
|
||||
//14A842 = 000 10100101 01000010 0001 0 (cnt 1)
|
||||
//14A840 = 000 10100101 01000010 0000 0 (cnt 0)
|
||||
//14A85E = 000 10100101 01000010 1111 0 (cnt F)
|
||||
//14A85C = 000 10100101 01000010 1110 0 (cnt E)
|
||||
//14A85A = 000 10100101 01000010 1101 0 (cnt D)
|
||||
//14A858 = 000 10100101 01000010 1100 0 (cnt C)
|
||||
//14A856 = 000 10100101 01000010 1011 0 (cnt B)
|
||||
// 0xA5 (Labeled as On/Off on the remote board)
|
||||
// 0x3C (Labeled as Mode on the remote board)
|
||||
// 0x42 (Serial)
|
||||
// BTN Serial CNT
|
||||
//078854 = 000 00111100 01000010 1010 0 (cnt A)
|
||||
//078852 = 000 00111100 01000010 1001 0 (cnt 9)
|
||||
//078850 = 000 00111100 01000010 1000 0 (cnt 8)
|
||||
//07884E = 000 00111100 01000010 0111 0 (cnt 7)
|
||||
// Inverted back
|
||||
//1877B9 = 000 11000011 10111101 1100 1
|
||||
//1877BB = 000 11000011 10111101 1101 1
|
||||
//1877BD = 000 11000011 10111101 1110 1
|
||||
//0B57BF = 000 01011010 10111101 1111 1
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderHay21* instance = malloc(sizeof(SubGhzProtocolDecoderHay21));
|
||||
instance->base.protocol = &subghz_protocol_hay21;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Hay21DecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
|
||||
subghz_protocol_hay21_const.te_delta * 3)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case Hay21DecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Hay21DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case Hay21DecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 1 is long + short timing
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
|
||||
subghz_protocol_hay21_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_short) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
|
||||
// Bit 0 is short + long timing
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
|
||||
subghz_protocol_hay21_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
|
||||
subghz_protocol_hay21_const.te_delta * 2) {
|
||||
//Found next GAP and add bit 0 or 1
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
// If got 21 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_hay21_const.min_count_bit_for_found) {
|
||||
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.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button name.
|
||||
* @param btn Button number, 4 bit
|
||||
*/
|
||||
static const char* subghz_protocol_hay21_get_button_name(uint8_t btn) {
|
||||
const char* btn_name;
|
||||
switch(btn) {
|
||||
case 0x5A:
|
||||
btn_name = "On/Off";
|
||||
break;
|
||||
case 0xC3:
|
||||
btn_name = "Mode";
|
||||
break;
|
||||
case 0x88:
|
||||
btn_name = "Hold";
|
||||
break;
|
||||
default:
|
||||
btn_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
return btn_name;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_hay21_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
|
||||
// Parse serial, button, counter
|
||||
subghz_protocol_hay21_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s - %dbit\r\n"
|
||||
"Key: 0x%06lX\r\n"
|
||||
"Serial: 0x%02X\r\n"
|
||||
"Btn: 0x%01X - %s\r\n"
|
||||
"Cnt: 0x%01X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
(uint8_t)(instance->generic.serial & 0xFF),
|
||||
instance->generic.btn,
|
||||
subghz_protocol_hay21_get_button_name(instance->generic.btn),
|
||||
(uint8_t)(instance->generic.cnt & 0xF));
|
||||
}
|
||||
74
lib/subghz/protocols/hay21.h
Normal file
74
lib/subghz/protocols/hay21.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_HAY21_NAME "Hay21"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHay21 SubGhzProtocolDecoderHay21;
|
||||
typedef struct SubGhzProtocolEncoderHay21 SubGhzProtocolEncoderHay21;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_hay21_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_hay21_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_hay21;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderHay21.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderHay21* pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output);
|
||||
408
lib/subghz/protocols/hollarm.c
Normal file
408
lib/subghz/protocols/hollarm.c
Normal file
@@ -0,0 +1,408 @@
|
||||
#include "hollarm.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolHollarm"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_hollarm_const = {
|
||||
.te_short = 200,
|
||||
.te_long = 1000,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 42,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderHollarm {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHollarm {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
HollarmDecoderStepReset = 0,
|
||||
HollarmDecoderStepSaveDuration,
|
||||
HollarmDecoderStepCheckDuration,
|
||||
} HollarmDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder = {
|
||||
.alloc = subghz_protocol_decoder_hollarm_alloc,
|
||||
.free = subghz_protocol_decoder_hollarm_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_hollarm_feed,
|
||||
.reset = subghz_protocol_decoder_hollarm_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_hollarm_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_hollarm_serialize,
|
||||
.deserialize = subghz_protocol_decoder_hollarm_deserialize,
|
||||
.get_string = subghz_protocol_decoder_hollarm_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder = {
|
||||
.alloc = subghz_protocol_encoder_hollarm_alloc,
|
||||
.free = subghz_protocol_encoder_hollarm_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_hollarm_deserialize,
|
||||
.stop = subghz_protocol_encoder_hollarm_stop,
|
||||
.yield = subghz_protocol_encoder_hollarm_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_hollarm = {
|
||||
.name = SUBGHZ_PROTOCOL_HOLLARM_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_hollarm_decoder,
|
||||
.encoder = &subghz_protocol_hollarm_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderHollarm* instance = malloc(sizeof(SubGhzProtocolEncoderHollarm));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_hollarm;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_hollarm_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_hollarm_get_upload(SubGhzProtocolEncoderHollarm* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
// Generate new key
|
||||
uint64_t new_key = (instance->generic.data >> 12) << 12 | (instance->generic.btn << 8);
|
||||
|
||||
uint8_t bytesum = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) +
|
||||
((new_key >> 16) & 0xFF) + ((new_key >> 8) & 0xFF);
|
||||
|
||||
instance->generic.data = (new_key | bytesum);
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP between parcels
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
// Read and prepare levels with 2 bit (was saved for better parsing) to the left offset to fit with the original remote transmission
|
||||
if(bit_read((instance->generic.data << 2), i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 8);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_hollarm_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data and parsing serial number
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_hollarm_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->btn = (instance->data >> 8) & 0xF;
|
||||
instance->serial = (instance->data & 0xFFFFFFF0000) >> 16;
|
||||
|
||||
// Hollarm Decoder
|
||||
// 09.2024 - @xMasterX (MMX)
|
||||
// Thanks @Skorpionm for support!
|
||||
|
||||
// F0B93422FF = FF 8bit Sum
|
||||
// F0B93421FE = FE 8bit Sum
|
||||
// F0B9342401 = 01 8bit Sum
|
||||
// F0B9342805 = 05 8bit Sum
|
||||
|
||||
// Serial (moved 2bit to right) | Btn | 8b previous 4 bytes sum
|
||||
// 00001111000010111001001101000010 0010 11111111 btn = (0x2)
|
||||
// 00001111000010111001001101000010 0001 11111110 btn = (0x1)
|
||||
// 00001111000010111001001101000010 0100 00000001 btn = (0x4)
|
||||
// 00001111000010111001001101000010 1000 00000101 btn = (0x8)
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_hollarm_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_hollarm_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_hollarm_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Key");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_hollarm_stop(void* context) {
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) {
|
||||
SubGhzProtocolEncoderHollarm* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderHollarm* instance = malloc(sizeof(SubGhzProtocolDecoderHollarm));
|
||||
instance->base.protocol = &subghz_protocol_hollarm;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case HollarmDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) <
|
||||
subghz_protocol_hollarm_const.te_delta * 2)) {
|
||||
//Found GAP between parcels
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case HollarmDecoderStepSaveDuration:
|
||||
// Save HIGH level timing for next step
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = HollarmDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case HollarmDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short 200us HIGH + long 1000us LOW timing
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) <
|
||||
subghz_protocol_hollarm_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_long) <
|
||||
subghz_protocol_hollarm_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
|
||||
// Bit 1 is short 200us HIGH + short x8 = 1600us LOW timing
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) <
|
||||
subghz_protocol_hollarm_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 8) <
|
||||
subghz_protocol_hollarm_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) <
|
||||
subghz_protocol_hollarm_const.te_delta) {
|
||||
// When next GAP is found add bit 0 and do check for read finish
|
||||
// (we have 42 high level pulses, last or first one may be a stop/start bit but we will parse it as zero)
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
|
||||
// If got 42 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_hollarm_const.min_count_bit_for_found) {
|
||||
// Saving with 2bit to the right offset for proper parsing
|
||||
instance->generic.data = (instance->decoder.decode_data >> 2);
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
uint8_t bytesum = ((instance->generic.data >> 32) & 0xFF) +
|
||||
((instance->generic.data >> 24) & 0xFF) +
|
||||
((instance->generic.data >> 16) & 0xFF) +
|
||||
((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
if(bytesum != (instance->generic.data & 0xFF)) {
|
||||
// Check if the key is valid by verifying the sum
|
||||
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 = HollarmDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button name.
|
||||
* @param btn Button number, 4 bit
|
||||
*/
|
||||
static const char* subghz_protocol_hollarm_get_button_name(uint8_t btn) {
|
||||
const char* name_btn[16] = {
|
||||
"Unknown",
|
||||
"Disarm", // B (2)
|
||||
"Arm", // A (1)
|
||||
"0x3",
|
||||
"Ringtone/Alarm", // C (3)
|
||||
"0x5",
|
||||
"0x6",
|
||||
"0x7",
|
||||
"Ring", // D (4)
|
||||
"Settings mode",
|
||||
"Exit settings",
|
||||
"Vibro sens. setting",
|
||||
"Not used\n(in settings)",
|
||||
"Volume setting",
|
||||
"0xE",
|
||||
"0xF"};
|
||||
return btn <= 0xf ? name_btn[btn] : name_btn[0];
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_hollarm_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
|
||||
// Parse serial
|
||||
subghz_protocol_hollarm_remote_controller(&instance->generic);
|
||||
// Get byte sum
|
||||
uint8_t bytesum =
|
||||
((instance->generic.data >> 32) & 0xFF) + ((instance->generic.data >> 24) & 0xFF) +
|
||||
((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%02lX%08lX\r\n"
|
||||
"Serial: 0x%06lX Sum: %02X\r\n"
|
||||
"Btn: 0x%01X - %s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)instance->generic.data,
|
||||
instance->generic.serial,
|
||||
bytesum,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_hollarm_get_button_name(instance->generic.btn));
|
||||
}
|
||||
109
lib/subghz/protocols/hollarm.h
Normal file
109
lib/subghz/protocols/hollarm.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_HOLLARM_NAME "Hollarm"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHollarm SubGhzProtocolDecoderHollarm;
|
||||
typedef struct SubGhzProtocolEncoderHollarm SubGhzProtocolEncoderHollarm;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_hollarm;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderHollarm.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderHollarm* pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_encoder_hollarm_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_encoder_hollarm_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderHollarm.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderHollarm* pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output);
|
||||
@@ -234,8 +234,10 @@ void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Holtek_HT12XDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 36) <
|
||||
subghz_protocol_holtek_th12x_const.te_delta * 36)) {
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 28) <
|
||||
subghz_protocol_holtek_th12x_const.te_delta * 20)) {
|
||||
// 18720 us old max value
|
||||
// 12960 us corrected max value
|
||||
//Found Preambula
|
||||
instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit;
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ SubGhzProtocolStatus
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_hormann_get_upload(instance)) {
|
||||
instance->encoder.front = 0; // reset position before start
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
@@ -170,6 +171,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_hormann_stop(void* context) {
|
||||
SubGhzProtocolEncoderHormann* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_hormann_yield(void* context) {
|
||||
|
||||
@@ -193,6 +193,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize(
|
||||
void subghz_protocol_encoder_intertechno_v3_stop(void* context) {
|
||||
SubGhzProtocolEncoderIntertechno_V3* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) {
|
||||
|
||||
@@ -299,7 +299,7 @@ SubGhzProtocolStatus
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -309,6 +309,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_keeloq_stop(void* context) {
|
||||
SubGhzProtocolEncoderKeeloq* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) {
|
||||
|
||||
398
lib/subghz/protocols/legrand.c
Normal file
398
lib/subghz/protocols/legrand.c
Normal file
@@ -0,0 +1,398 @@
|
||||
#include "legrand.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolLegrand"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_legrand_const = {
|
||||
.te_short = 375,
|
||||
.te_long = 1125,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 18,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderLegrand {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t te;
|
||||
uint32_t last_data;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderLegrand {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t te;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LegrandDecoderStepReset = 0,
|
||||
LegrandDecoderStepFirstBit,
|
||||
LegrandDecoderStepSaveDuration,
|
||||
LegrandDecoderStepCheckDuration,
|
||||
} LegrandDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_legrand_decoder = {
|
||||
.alloc = subghz_protocol_decoder_legrand_alloc,
|
||||
.free = subghz_protocol_decoder_legrand_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_legrand_feed,
|
||||
.reset = subghz_protocol_decoder_legrand_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_legrand_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_legrand_serialize,
|
||||
.deserialize = subghz_protocol_decoder_legrand_deserialize,
|
||||
.get_string = subghz_protocol_decoder_legrand_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_legrand_encoder = {
|
||||
.alloc = subghz_protocol_encoder_legrand_alloc,
|
||||
.free = subghz_protocol_encoder_legrand_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_legrand_deserialize,
|
||||
.stop = subghz_protocol_encoder_legrand_stop,
|
||||
.yield = subghz_protocol_encoder_legrand_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_legrand = {
|
||||
.name = SUBGHZ_PROTOCOL_LEGRAND_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_legrand_decoder,
|
||||
.encoder = &subghz_protocol_legrand_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderLegrand* instance = malloc(sizeof(SubGhzProtocolEncoderLegrand));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_legrand;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload =
|
||||
(subghz_protocol_legrand_const.min_count_bit_for_found * 6) * 2 + 2;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_legrand_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
* @return true On success
|
||||
*/
|
||||
static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
//size_t size_upload = (instance->generic.data_count_bit * 2) + 1;
|
||||
//if(size_upload != instance->encoder.size_upload) {
|
||||
// FURI_LOG_E(TAG, "Invalid data bit count");
|
||||
// return false;
|
||||
//}
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for(size_t r = 0; r < 5; r++) {
|
||||
// Send sync
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)instance->te * 16); // 5728
|
||||
// Send key data
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// send bit 1
|
||||
if(i == instance->generic.data_count_bit) {
|
||||
//Send first bit
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te * 3);
|
||||
} else {
|
||||
// send bit 1 regular
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)instance->te);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te * 3);
|
||||
}
|
||||
} else {
|
||||
// send bit 0
|
||||
if(i == instance->generic.data_count_bit) {
|
||||
//Send first bit
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te);
|
||||
} else {
|
||||
// send bit 0 regular
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)instance->te * 3);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->encoder.size_upload = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_legrand_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing TE");
|
||||
ret = SubGhzProtocolStatusErrorParserTe;
|
||||
break;
|
||||
}
|
||||
// optional parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_legrand_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_legrand_stop(void* context) {
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_legrand_yield(void* context) {
|
||||
SubGhzProtocolEncoderLegrand* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderLegrand* instance = malloc(sizeof(SubGhzProtocolDecoderLegrand));
|
||||
instance->base.protocol = &subghz_protocol_legrand;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
instance->last_data = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case LegrandDecoderStepReset:
|
||||
if(!level && DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) <
|
||||
subghz_protocol_legrand_const.te_delta * 8) { // 6000 +- 1200
|
||||
instance->decoder.parser_step = LegrandDecoderStepFirstBit;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->te = 0;
|
||||
}
|
||||
break;
|
||||
case LegrandDecoderStepFirstBit:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) <
|
||||
subghz_protocol_legrand_const.te_delta) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->te += duration * 4; // long low that is part of sync, then short high
|
||||
}
|
||||
|
||||
if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) <
|
||||
subghz_protocol_legrand_const.te_delta * 3) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->te += duration / 3 * 4; // short low that is part of sync, then long high
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit > 0) {
|
||||
// advance to the next step if either short or long is found
|
||||
instance->decoder.parser_step = LegrandDecoderStepSaveDuration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
break;
|
||||
case LegrandDecoderStepSaveDuration:
|
||||
if(!level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->te += duration;
|
||||
instance->decoder.parser_step = LegrandDecoderStepCheckDuration;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
break;
|
||||
case LegrandDecoderStepCheckDuration:
|
||||
if(level) {
|
||||
uint8_t found = 0;
|
||||
|
||||
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_long) <
|
||||
subghz_protocol_legrand_const.te_delta * 3 &&
|
||||
DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) <
|
||||
subghz_protocol_legrand_const.te_delta) {
|
||||
found = 1;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
|
||||
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_short) <
|
||||
subghz_protocol_legrand_const.te_delta &&
|
||||
DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) <
|
||||
subghz_protocol_legrand_const.te_delta * 3) {
|
||||
found = 1;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
|
||||
if(found) {
|
||||
instance->te += duration;
|
||||
|
||||
if(instance->decoder.decode_count_bit <
|
||||
subghz_protocol_legrand_const.min_count_bit_for_found) {
|
||||
instance->decoder.parser_step = LegrandDecoderStepSaveDuration;
|
||||
break;
|
||||
}
|
||||
|
||||
// enough bits for a packet found, save it only if there was a previous packet
|
||||
// with the same data
|
||||
if(instance->last_data && (instance->last_data == instance->decoder.decode_data)) {
|
||||
instance->te /= instance->decoder.decode_count_bit * 4;
|
||||
|
||||
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->last_data = instance->decoder.decode_data;
|
||||
// fallthrough to reset, the next bit is expected to be a sync
|
||||
// it also takes care of resetting the decoder state
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if((ret == SubGhzProtocolStatusOk) &&
|
||||
!flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add TE");
|
||||
ret = SubGhzProtocolStatusErrorParserTe;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_legrand_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing TE");
|
||||
ret = SubGhzProtocolStatusErrorParserTe;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%05lX\r\n"
|
||||
"Te:%luus\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFF),
|
||||
instance->te);
|
||||
}
|
||||
117
lib/subghz/protocols/legrand.h
Normal file
117
lib/subghz/protocols/legrand.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_LEGRAND_NAME "Legrand"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SubGhzProtocolDecoderLegrand SubGhzProtocolDecoderLegrand;
|
||||
typedef struct SubGhzProtocolEncoderLegrand SubGhzProtocolEncoderLegrand;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_legrand_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_legrand_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_legrand;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderLegrand.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderLegrand* pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_encoder_legrand_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_encoder_legrand_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_legrand_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderLegrand.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderLegrand* pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -168,6 +168,7 @@ SubGhzProtocolStatus
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_magellan_get_upload(instance)) {
|
||||
instance->encoder.front = 0; // reset before start
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
@@ -180,6 +181,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_magellan_stop(void* context) {
|
||||
SubGhzProtocolEncoderMagellan* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_magellan_yield(void* context) {
|
||||
@@ -359,15 +361,38 @@ static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric*
|
||||
*
|
||||
* 0x1275EC => 0x12-event codes, 0x75EC-serial (dec 117236)
|
||||
*
|
||||
* event codes
|
||||
* bit_0: 1-Open/Motion, 0-close/ok
|
||||
* bit_1: 1-Tamper On (alarm), 0-Tamper Off (ok)
|
||||
* bit_2: ?
|
||||
* bit_3: 1-power on
|
||||
* bit_4: model type - wireless reed
|
||||
* bit_5: model type - motion sensor
|
||||
* bit_6: ?
|
||||
* bit_7: ?
|
||||
* Event codes consist of two parts:
|
||||
* - The upper nibble (bits 7-4) represents the event type:
|
||||
* - 0x00: Nothing
|
||||
* - 0x01: Door
|
||||
* - 0x02: Motion
|
||||
* - 0x03: Smoke Alarm
|
||||
* - 0x04: REM1
|
||||
* - 0x05: REM1 with subtype Off1
|
||||
* - 0x06: REM2
|
||||
* - 0x07: REM2 with subtype Off1
|
||||
* - Others: Unknown
|
||||
* - The lower nibble (bits 3-0) represents the event subtype, which varies based on the model type:
|
||||
* - If the model type is greater than 0x03 (e.g., REM1 or REM2):
|
||||
* - 0x00: Arm1
|
||||
* - 0x01: Btn1
|
||||
* - 0x02: Btn2
|
||||
* - 0x03: Btn3
|
||||
* - 0x08: Reset
|
||||
* - 0x09: LowBatt
|
||||
* - 0x0A: BattOk
|
||||
* - 0x0B: Learn
|
||||
* - Others: Unknown
|
||||
* - Otherwise:
|
||||
* - 0x00: Sealed
|
||||
* - 0x01: Alarm
|
||||
* - 0x02: Tamper
|
||||
* - 0x03: Alarm + Tamper
|
||||
* - 0x08: Reset
|
||||
* - 0x09: LowBatt
|
||||
* - 0x0A: BattOk
|
||||
* - 0x0B: Learn
|
||||
* - Others: Unknown
|
||||
*
|
||||
*/
|
||||
uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data >> 8, 24);
|
||||
@@ -376,18 +401,71 @@ static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric*
|
||||
}
|
||||
|
||||
static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriString* output) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s%s%s%s%s%s%s%s",
|
||||
((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") :
|
||||
(event & 0x1 ? " Motion" : " Ok")),
|
||||
((event >> 1) & 0x1 ? ", Tamper On\n(Alarm)" : ""),
|
||||
((event >> 2) & 0x1 ? ", ?" : ""),
|
||||
((event >> 3) & 0x1 ? ", Power On" : ""),
|
||||
((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""),
|
||||
((event >> 5) & 0x1 ? ", MT:Motion_Sensor" : ""),
|
||||
((event >> 6) & 0x1 ? ", ?" : ""),
|
||||
((event >> 7) & 0x1 ? ", ?" : ""));
|
||||
const char* event_type;
|
||||
const char* event_subtype;
|
||||
|
||||
switch((event >> 4) & 0x0F) {
|
||||
case 0x00:
|
||||
event_type = "Nothing";
|
||||
break;
|
||||
case 0x01:
|
||||
event_type = "Door";
|
||||
break;
|
||||
case 0x02:
|
||||
event_type = "Motion";
|
||||
break;
|
||||
case 0x03:
|
||||
event_type = "Smoke Alarm";
|
||||
break;
|
||||
case 0x04:
|
||||
event_type = "REM1";
|
||||
break;
|
||||
case 0x05:
|
||||
event_type = "REM1";
|
||||
event_subtype = "Off1";
|
||||
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
||||
return;
|
||||
case 0x06:
|
||||
event_type = "REM2";
|
||||
event_subtype = "Off1";
|
||||
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
||||
return;
|
||||
default:
|
||||
event_type = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
switch(event & 0x0F) {
|
||||
case 0x00:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Arm1" : "Sealed";
|
||||
break;
|
||||
case 0x01:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn1" : "Alarm";
|
||||
break;
|
||||
case 0x02:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn2" : "Tamper";
|
||||
break;
|
||||
case 0x03:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn3" : "Alarm + Tamper";
|
||||
break;
|
||||
case 0x08:
|
||||
event_subtype = "Reset";
|
||||
break;
|
||||
case 0x09:
|
||||
event_subtype = "LowBatt";
|
||||
break;
|
||||
case 0x0A:
|
||||
event_subtype = "BattOk";
|
||||
break;
|
||||
case 0x0B:
|
||||
event_subtype = "Learn";
|
||||
break;
|
||||
default:
|
||||
event_subtype = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) {
|
||||
|
||||
@@ -165,7 +165,7 @@ static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMar
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x08;
|
||||
uint8_t crc = 0x01;
|
||||
size_t i, j;
|
||||
for(i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
@@ -184,6 +184,18 @@ uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) {
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Key samples
|
||||
// 1307EDF6486C5 = 000 100110000 01111110110111110110 0100 10000110 11000101
|
||||
// 1303EFAFD8683 = 000 100110000 00111110111110101111 1101 10000110 10000011
|
||||
|
||||
// From unittests
|
||||
// 1300710DF869F
|
||||
|
||||
// const serial button serial crc
|
||||
// 130 7EDF6 4 86 C5
|
||||
// 130 3EFAF D 86 83
|
||||
// 130 0710D F 86 9F
|
||||
|
||||
instance->btn = (instance->data >> 16) & 0xF;
|
||||
instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF);
|
||||
}
|
||||
@@ -207,6 +219,7 @@ SubGhzProtocolStatus
|
||||
|
||||
subghz_protocol_marantec_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_marantec_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -216,6 +229,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_marantec_stop(void* context) {
|
||||
SubGhzProtocolEncoderMarantec* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_marantec_yield(void* context) {
|
||||
@@ -253,6 +267,7 @@ void subghz_protocol_decoder_marantec_free(void* context) {
|
||||
void subghz_protocol_decoder_marantec_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec* instance = context;
|
||||
instance->decoder.parser_step = MarantecDecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
@@ -367,16 +382,30 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp
|
||||
SubGhzProtocolDecoderMarantec* instance = context;
|
||||
subghz_protocol_marantec_remote_controller(&instance->generic);
|
||||
|
||||
uint8_t tdata[6] = {
|
||||
instance->generic.data >> 48,
|
||||
instance->generic.data >> 40,
|
||||
instance->generic.data >> 32,
|
||||
instance->generic.data >> 24,
|
||||
instance->generic.data >> 16,
|
||||
instance->generic.data >> 8};
|
||||
|
||||
uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata));
|
||||
bool crc_ok = (crc == (instance->generic.data & 0xFF));
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%07lX \r\n"
|
||||
"Btn:%X\r\n",
|
||||
"Key: 0x%lX%08lX\r\n"
|
||||
"Sn: 0x%07lX \r\n"
|
||||
"CRC: 0x%02X - %s\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
crc,
|
||||
crc_ok ? "Valid" : "Invalid",
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -107,3 +107,11 @@ SubGhzProtocolStatus
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output);
|
||||
|
||||
/**
|
||||
* Calculate CRC8 for Marantec protocol.
|
||||
* @param data Pointer to the data buffer
|
||||
* @param len Length of the data buffer
|
||||
* @return CRC8 value
|
||||
*/
|
||||
uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len);
|
||||
|
||||
352
lib/subghz/protocols/marantec24.c
Normal file
352
lib/subghz/protocols/marantec24.c
Normal file
@@ -0,0 +1,352 @@
|
||||
#include "marantec24.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolMarantec24"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_marantec24_const = {
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 24,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderMarantec24 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderMarantec24 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Marantec24DecoderStepReset = 0,
|
||||
Marantec24DecoderStepSaveDuration,
|
||||
Marantec24DecoderStepCheckDuration,
|
||||
} Marantec24DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_marantec24_decoder = {
|
||||
.alloc = subghz_protocol_decoder_marantec24_alloc,
|
||||
.free = subghz_protocol_decoder_marantec24_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_marantec24_feed,
|
||||
.reset = subghz_protocol_decoder_marantec24_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_marantec24_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_marantec24_serialize,
|
||||
.deserialize = subghz_protocol_decoder_marantec24_deserialize,
|
||||
.get_string = subghz_protocol_decoder_marantec24_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_marantec24_encoder = {
|
||||
.alloc = subghz_protocol_encoder_marantec24_alloc,
|
||||
.free = subghz_protocol_encoder_marantec24_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_marantec24_deserialize,
|
||||
.stop = subghz_protocol_encoder_marantec24_stop,
|
||||
.yield = subghz_protocol_encoder_marantec24_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_marantec24 = {
|
||||
.name = SUBGHZ_PROTOCOL_MARANTEC24_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_marantec24_decoder,
|
||||
.encoder = &subghz_protocol_marantec24_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderMarantec24* instance = malloc(sizeof(SubGhzProtocolEncoderMarantec24));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_marantec24;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 512;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_marantec24_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
static void
|
||||
subghz_protocol_encoder_marantec24_get_upload(SubGhzProtocolEncoderMarantec24* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
// Send initial GAP to trigger decoder
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_marantec24_const.te_long * 9);
|
||||
for(size_t r = 0; r < 4; r++) {
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
|
||||
subghz_protocol_marantec24_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
|
||||
subghz_protocol_marantec24_const.te_short); // 15200
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_marantec24_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->serial = instance->data >> 4;
|
||||
instance->btn = instance->data & 0xF;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_marantec24_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_marantec24_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_marantec24_get_upload(instance);
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_marantec24_stop(void* context) {
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_marantec24_yield(void* context) {
|
||||
SubGhzProtocolEncoderMarantec24* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_marantec24_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderMarantec24* instance = malloc(sizeof(SubGhzProtocolDecoderMarantec24));
|
||||
instance->base.protocol = &subghz_protocol_marantec24;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
|
||||
// Marantec24 Decoder
|
||||
// 2024 - @xMasterX (MMX)
|
||||
|
||||
// 2025 update - The protocol is not real marantec,
|
||||
// it comes from chinese remote that pretends to be replica of original marantec, actually it was a cloner
|
||||
// which had some thing written on it, which is uknown, but since its pretentding to be marantec,
|
||||
// it was decided to keep the name of the protocol as marantec24 (24 bits)
|
||||
|
||||
// Key samples
|
||||
// 101011000000010111001000 = AC05C8
|
||||
// 101011000000010111000100 = AC05C4
|
||||
// 101011000000010111001100 = AC05CC
|
||||
// 101011000000010111000000 = AC05C0
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Marantec24DecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
|
||||
subghz_protocol_marantec24_const.te_delta * 6)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case Marantec24DecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case Marantec24DecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is long and short x2 timing = 1600us HIGH (te_last) and 2400us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) <
|
||||
subghz_protocol_marantec24_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_short * 3) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Marantec24DecoderStepSaveDuration;
|
||||
// Bit 1 is short and long x2 timing = 800us HIGH (te_last) and 3200us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) <
|
||||
subghz_protocol_marantec24_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 2) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Marantec24DecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
|
||||
subghz_protocol_marantec24_const.te_delta * 6) {
|
||||
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 24 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_marantec24_const.min_count_bit_for_found) {
|
||||
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.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_marantec24_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_marantec24_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_marantec24_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
|
||||
subghz_protocol_marantec24_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%06lX\r\n"
|
||||
"Serial: 0x%05lX\r\n"
|
||||
"Btn: %01X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.btn);
|
||||
}
|
||||
109
lib/subghz/protocols/marantec24.h
Normal file
109
lib/subghz/protocols/marantec24.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_MARANTEC24_NAME "Marantec24"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMarantec24 SubGhzProtocolDecoderMarantec24;
|
||||
typedef struct SubGhzProtocolEncoderMarantec24 SubGhzProtocolEncoderMarantec24;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_marantec24_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_marantec24_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_marantec24;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderMarantec24.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderMarantec24* pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_marantec24_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_marantec24_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_marantec24_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderMarantec24.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderMarantec24* pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_marantec24_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_marantec24_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_marantec24_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* output);
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolPhoenixV2"
|
||||
|
||||
//transmission only static mode
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = {
|
||||
@@ -91,6 +90,9 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
// Pre define functions
|
||||
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance);
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance
|
||||
@@ -107,6 +109,7 @@ static bool
|
||||
} else {
|
||||
instance->encoder.size_upload = size_upload;
|
||||
}
|
||||
|
||||
//Send header
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60);
|
||||
@@ -153,6 +156,7 @@ SubGhzProtocolStatus
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -274,16 +278,66 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) {
|
||||
uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF);
|
||||
|
||||
uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte
|
||||
uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte
|
||||
|
||||
uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial
|
||||
uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial
|
||||
|
||||
for(int i = 0; i < 16; i++) {
|
||||
// Store the most significant bit (MSB) of byte1.
|
||||
// The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys.
|
||||
uint8_t msb_of_byte1 = byte1 & 0x80;
|
||||
|
||||
// Store the least significant bit (LSB) of byte2.
|
||||
uint8_t lsb_of_byte2 = byte2 & 1;
|
||||
|
||||
// Perform a bit shuffle between the two bytes
|
||||
byte2 = (byte2 >> 1) | msb_of_byte1;
|
||||
byte1 = (byte1 << 1) | lsb_of_byte2;
|
||||
|
||||
// Conditionally apply the XOR keys based on the original MSB of byte1.
|
||||
if(msb_of_byte1 == 0) {
|
||||
byte1 ^= xor_key1;
|
||||
// The mask `& 0x7F` clears the MSB of byte2 after the XOR.
|
||||
byte2 = (byte2 ^ xor_key2) & 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint16_t)byte2 << 8 | byte1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// 2022.08 - @Skorpionm
|
||||
// 2025.07 - @xMasterX & @RocketGod-git
|
||||
// Fully supported now, with button switch and add manually
|
||||
//
|
||||
// Key samples
|
||||
// Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied
|
||||
// Serial - B9615720
|
||||
// Button - 01
|
||||
// Encrypted -> Decrypted counters
|
||||
// C63E - 025C
|
||||
// BCC1 - 025D
|
||||
// 3341 - 025E
|
||||
// 49BE - 025F
|
||||
// 99D3 - 0260
|
||||
// E32C - 0261
|
||||
|
||||
uint64_t data_rev =
|
||||
subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4);
|
||||
|
||||
instance->serial = data_rev & 0xFFFFFFFF;
|
||||
instance->cnt = (data_rev >> 40) & 0xFFFF;
|
||||
instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev);
|
||||
instance->btn = (data_rev >> 32) & 0xF;
|
||||
// encrypted cnt is (data_rev >> 40) & 0xFFFF
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) {
|
||||
@@ -318,14 +372,15 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou
|
||||
subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic);
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%02lX%08lX\r\n"
|
||||
"V2 Phoenix %dbit\r\n"
|
||||
"Key:%05lX%08lX\r\n"
|
||||
"Sn:0x%07lX \r\n"
|
||||
"Btn:%X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
"Cnt: 0x%04lX\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.cnt,
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ SubGhzProtocolStatus
|
||||
|
||||
subghz_protocol_power_smart_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_power_smart_get_upload(instance);
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -221,6 +222,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_power_smart_stop(void* context) {
|
||||
SubGhzProtocolEncoderPowerSmart* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_power_smart_yield(void* context) {
|
||||
|
||||
@@ -44,7 +44,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_kinggates_stylo_4k,
|
||||
&subghz_protocol_bin_raw,
|
||||
&subghz_protocol_mastercode,
|
||||
&subghz_protocol_legrand,
|
||||
&subghz_protocol_dickert_mahs,
|
||||
&subghz_protocol_gangqi,
|
||||
&subghz_protocol_marantec24,
|
||||
&subghz_protocol_hollarm,
|
||||
&subghz_protocol_hay21,
|
||||
&subghz_protocol_revers_rb2,
|
||||
&subghz_protocol_feron,
|
||||
&subghz_protocol_roger,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -45,4 +45,12 @@
|
||||
#include "kinggates_stylo_4k.h"
|
||||
#include "bin_raw.h"
|
||||
#include "mastercode.h"
|
||||
#include "legrand.h"
|
||||
#include "dickert_mahs.h"
|
||||
#include "gangqi.h"
|
||||
#include "marantec24.h"
|
||||
#include "hollarm.h"
|
||||
#include "hay21.h"
|
||||
#include "revers_rb2.h"
|
||||
#include "feron.h"
|
||||
#include "roger.h"
|
||||
|
||||
@@ -31,7 +31,7 @@ bool subghz_protocol_secplus_v2_create_data(
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param serial Serial number, 28 bit
|
||||
* @param btn Button number, 4 bit
|
||||
* @param cnt Container value, 16 bit
|
||||
* @param cnt Counter value, 16 bit
|
||||
* @param manufacture_name Name of manufacturer's key
|
||||
* @param preset Modulation, SubGhzRadioPreset
|
||||
* @return true On success
|
||||
|
||||
@@ -245,8 +245,8 @@ void subghz_protocol_decoder_raw_reset(void* context) {
|
||||
void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRAW* instance = context;
|
||||
|
||||
if(!instance->pause && (instance->upload_raw != NULL)) {
|
||||
// Add check if we got duration higher than 1 second, we skipping it, temp fix
|
||||
if((!instance->pause && (instance->upload_raw != NULL)) && (duration < ((uint32_t)1000000))) {
|
||||
if(duration > subghz_protocol_raw_const.te_short) {
|
||||
if(instance->last_level != level) {
|
||||
instance->last_level = (level ? true : false);
|
||||
@@ -273,7 +273,7 @@ void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
//SubGhzProtocolDecoderRAW* instance = context;
|
||||
UNUSED(context);
|
||||
furi_string_cat_printf(output, "RAW Date");
|
||||
furi_string_cat_printf(output, "RAW Data");
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) {
|
||||
|
||||
415
lib/subghz/protocols/revers_rb2.c
Normal file
415
lib/subghz/protocols/revers_rb2.c
Normal file
@@ -0,0 +1,415 @@
|
||||
#include "revers_rb2.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolRevers_RB2"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_revers_rb2_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 160,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderRevers_RB2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_saved_state;
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderRevers_RB2 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Revers_RB2DecoderStepReset = 0,
|
||||
Revers_RB2DecoderStepHeader,
|
||||
Revers_RB2DecoderStepDecoderData,
|
||||
} Revers_RB2DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_revers_rb2_alloc,
|
||||
.free = subghz_protocol_decoder_revers_rb2_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_revers_rb2_feed,
|
||||
.reset = subghz_protocol_decoder_revers_rb2_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_revers_rb2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_revers_rb2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_revers_rb2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_revers_rb2_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder = {
|
||||
.alloc = subghz_protocol_encoder_revers_rb2_alloc,
|
||||
.free = subghz_protocol_encoder_revers_rb2_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_revers_rb2_deserialize,
|
||||
.stop = subghz_protocol_encoder_revers_rb2_stop,
|
||||
.yield = subghz_protocol_encoder_revers_rb2_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_revers_rb2 = {
|
||||
.name = SUBGHZ_PROTOCOL_REVERSRB2_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_revers_rb2_decoder,
|
||||
.encoder = &subghz_protocol_revers_rb2_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolEncoderRevers_RB2));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_revers_rb2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 1768;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_revers_rb2_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static LevelDuration
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(ManchesterEncoderResult result) {
|
||||
LevelDuration data = {.duration = 0, .level = 0};
|
||||
switch(result) {
|
||||
case ManchesterEncoderResultShortLow:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_short;
|
||||
data.level = false;
|
||||
break;
|
||||
case ManchesterEncoderResultLongLow:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_long;
|
||||
data.level = false;
|
||||
break;
|
||||
case ManchesterEncoderResultLongHigh:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_long;
|
||||
data.level = true;
|
||||
break;
|
||||
case ManchesterEncoderResultShortHigh:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_short;
|
||||
data.level = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
furi_crash("SubGhz: ManchesterEncoderResult is incorrect.");
|
||||
break;
|
||||
}
|
||||
return level_duration_make(data.level, data.duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
static void
|
||||
subghz_protocol_encoder_revers_rb2_get_upload(SubGhzProtocolEncoderRevers_RB2* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
for(size_t r = 0; r < 6; r++) {
|
||||
ManchesterEncoderState enc_state;
|
||||
manchester_encoder_reset(&enc_state);
|
||||
ManchesterEncoderResult result;
|
||||
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(!manchester_encoder_advance(
|
||||
&enc_state, bit_read(instance->generic.data, i - 1), &result)) {
|
||||
instance->encoder.upload[index++] =
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
|
||||
manchester_encoder_advance(
|
||||
&enc_state, bit_read(instance->generic.data, i - 1), &result);
|
||||
}
|
||||
instance->encoder.upload[index++] =
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
|
||||
}
|
||||
instance->encoder.upload[index] =
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(
|
||||
manchester_encoder_finish(&enc_state));
|
||||
if(level_duration_get_level(instance->encoder.upload[index])) {
|
||||
index++;
|
||||
}
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320);
|
||||
}
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_revers_rb2_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Revers RB2 / RB2M Decoder
|
||||
// 02.2025 - @xMasterX (MMX)
|
||||
instance->serial = (((instance->data << 16) >> 16) >> 10);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_revers_rb2_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_revers_rb2_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_revers_rb2_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_revers_rb2_stop(void* context) {
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) {
|
||||
SubGhzProtocolEncoderRevers_RB2* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolDecoderRevers_RB2));
|
||||
instance->base.protocol = &subghz_protocol_revers_rb2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_addbit(void* context, bool data) {
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data;
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if(instance->decoder.decode_count_bit >= 65) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit <
|
||||
subghz_protocol_revers_rb2_const.min_count_bit_for_found) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Revers RB2 / RB2M Decoder
|
||||
// 02.2025 - @xMasterX (MMX)
|
||||
|
||||
uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFF;
|
||||
uint16_t stop_code = (instance->decoder.decode_data & 0x3FF);
|
||||
|
||||
if(preamble == 0xFF && stop_code == 0x200) {
|
||||
//Found header and stop code
|
||||
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.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Revers_RB2DecoderStepReset:
|
||||
if((!level) &&
|
||||
(DURATION_DIFF(duration, 600) < subghz_protocol_revers_rb2_const.te_delta)) {
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepHeader;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
case Revers_RB2DecoderStepHeader:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
if(instance->decoder.te_last == 1) {
|
||||
instance->header_count++;
|
||||
}
|
||||
instance->decoder.te_last = level;
|
||||
} else {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
if(instance->decoder.te_last == 0) {
|
||||
instance->header_count++;
|
||||
}
|
||||
instance->decoder.te_last = level;
|
||||
} else {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->header_count == 4) {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0xF;
|
||||
instance->decoder.decode_count_bit = 4;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepDecoderData;
|
||||
}
|
||||
break;
|
||||
case Revers_RB2DecoderStepDecoderData:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventShortLow;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventLongLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventShortHigh;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventLongHigh;
|
||||
} else {
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(
|
||||
instance->manchester_saved_state, event, &instance->manchester_saved_state, &data);
|
||||
|
||||
if(data_ok) {
|
||||
subghz_protocol_decoder_revers_rb2_addbit(instance, data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_revers_rb2_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
subghz_protocol_revers_rb2_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:%lX%08lX\r\n"
|
||||
"Sn:0x%08lX \r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial);
|
||||
}
|
||||
109
lib/subghz/protocols/revers_rb2.h
Normal file
109
lib/subghz/protocols/revers_rb2.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_REVERSRB2_NAME "Revers_RB2"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderRevers_RB2 SubGhzProtocolDecoderRevers_RB2;
|
||||
typedef struct SubGhzProtocolEncoderRevers_RB2 SubGhzProtocolEncoderRevers_RB2;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_revers_rb2;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderRevers_RB2.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderRevers_RB2* pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_revers_rb2_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_revers_rb2_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderRevers_RB2* pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output);
|
||||
341
lib/subghz/protocols/roger.c
Normal file
341
lib/subghz/protocols/roger.c
Normal file
@@ -0,0 +1,341 @@
|
||||
#include "roger.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolRoger"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_roger_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 270,
|
||||
.min_count_bit_for_found = 28,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderRoger {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderRoger {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
RogerDecoderStepReset = 0,
|
||||
RogerDecoderStepSaveDuration,
|
||||
RogerDecoderStepCheckDuration,
|
||||
} RogerDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_roger_decoder = {
|
||||
.alloc = subghz_protocol_decoder_roger_alloc,
|
||||
.free = subghz_protocol_decoder_roger_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_roger_feed,
|
||||
.reset = subghz_protocol_decoder_roger_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_roger_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_roger_serialize,
|
||||
.deserialize = subghz_protocol_decoder_roger_deserialize,
|
||||
.get_string = subghz_protocol_decoder_roger_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_roger_encoder = {
|
||||
.alloc = subghz_protocol_encoder_roger_alloc,
|
||||
.free = subghz_protocol_encoder_roger_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_roger_deserialize,
|
||||
.stop = subghz_protocol_encoder_roger_stop,
|
||||
.yield = subghz_protocol_encoder_roger_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_roger = {
|
||||
.name = SUBGHZ_PROTOCOL_ROGER_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_roger_decoder,
|
||||
.encoder = &subghz_protocol_roger_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_roger;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_roger_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_roger_const.te_short * 19);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_roger_const.te_short * 19);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Roger Decoder
|
||||
// 2025.07 - @xMasterX (MMX)
|
||||
|
||||
// Key samples
|
||||
// 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 End: 0x20
|
||||
// 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 End: 0x23
|
||||
// 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 End: 0x01
|
||||
// 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 End: 0x02
|
||||
// 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 End: 0x01
|
||||
// 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 End: 0x04
|
||||
// 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 End: 0x02
|
||||
// 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 End: 0x08
|
||||
|
||||
instance->serial = instance->data >> 12;
|
||||
instance->btn = (instance->data >> 8) & 0xF;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_roger_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_roger_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_roger_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_roger_stop(void* context) {
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_roger_yield(void* context) {
|
||||
SubGhzProtocolEncoderRoger* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger));
|
||||
instance->base.protocol = &subghz_protocol_roger;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case RogerDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) <
|
||||
subghz_protocol_roger_const.te_delta * 5)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case RogerDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = RogerDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case RogerDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
// Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) <
|
||||
subghz_protocol_roger_const.te_delta * 5) {
|
||||
//Found next GAP and add bit 1 or 0
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
// If got full 28 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_roger_const.min_count_bit_for_found) {
|
||||
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.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
|
||||
subghz_protocol_roger_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%07lX\r\n"
|
||||
"Serial: 0x%04lX\r\n"
|
||||
"End: 0x%02lX\r\n"
|
||||
"Btn: %01X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFF),
|
||||
instance->generic.serial,
|
||||
(uint32_t)(instance->generic.data & 0xFF),
|
||||
instance->generic.btn);
|
||||
}
|
||||
109
lib/subghz/protocols/roger.h
Normal file
109
lib/subghz/protocols/roger.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger;
|
||||
typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_roger;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderRoger.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_encoder_roger_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_encoder_roger_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_roger_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderRoger.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output);
|
||||
@@ -219,18 +219,48 @@ static void subghz_protocol_scher_khan_check_remote_controller(
|
||||
*/
|
||||
|
||||
switch(instance->data_count_bit) {
|
||||
// case 35: //MAGIC CODE, Static
|
||||
// instance->protocol_name = "MAGIC CODE, Static";
|
||||
// break;
|
||||
case 35: //MAGIC CODE, Static
|
||||
*protocol_name = "MAGIC CODE, Static";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 51: //MAGIC CODE, Dynamic
|
||||
*protocol_name = "MAGIC CODE, Dynamic";
|
||||
instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F);
|
||||
instance->btn = (instance->data >> 24) & 0x0F;
|
||||
instance->cnt = instance->data & 0xFFFF;
|
||||
break;
|
||||
// case 57: //MAGIC CODE PRO / PRO2
|
||||
// instance->protocol_name = "MAGIC CODE PRO / PRO2";
|
||||
// break;
|
||||
case 57: //MAGIC CODE PRO / PRO2
|
||||
*protocol_name = "MAGIC CODE PRO/PRO2";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 63: //MAGIC CODE, Dynamic Response
|
||||
*protocol_name = "MAGIC CODE, Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 64: //MAGICAR, Response ???
|
||||
*protocol_name = "MAGICAR, Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 81: //MAGIC CODE PRO / PRO2 Response ???
|
||||
*protocol_name = "MAGIC CODE PRO,\n Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 82: //MAGIC CODE PRO / PRO2 Response ???
|
||||
*protocol_name = "MAGIC CODE PRO,\n Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
*protocol_name = "Unknown";
|
||||
|
||||
@@ -554,6 +554,7 @@ SubGhzProtocolStatus
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -563,6 +564,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_secplus_v2_stop(void* context) {
|
||||
SubGhzProtocolEncoderSecPlus_v2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) {
|
||||
|
||||
Reference in New Issue
Block a user