1
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:
MMX
2025-10-01 17:05:50 +03:00
committed by GitHub
parent df4bf69818
commit fad487df0e
52 changed files with 4432 additions and 105 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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,

View 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));
}

View 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);

View 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));
}

View 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);

View 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));
}

View 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);

View 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));
}

View 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);

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View 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);
}

View 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

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);

View 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);
}

View 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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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 = {

View File

@@ -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"

View File

@@ -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

View File

@@ -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) {

View 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);
}

View 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);

View 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);
}

View 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);

View File

@@ -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";

View File

@@ -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) {