diff --git a/applications/main/subghz/SConscript b/applications/main/subghz/SConscript new file mode 100644 index 000000000..8fbec94ad --- /dev/null +++ b/applications/main/subghz/SConscript @@ -0,0 +1,32 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/subghz", + ], + SDK_HEADERS=[ + File("environment.h"), + File("receiver.h"), + File("subghz_worker.h"), + File("subghz_tx_rx_worker.h"), + File("transmitter.h"), + File("registry.h"), + File("protocols/protocol_items.h"), + File("protocols/raw.h"), + File("blocks/const.h"), + File("blocks/decoder.h"), + File("blocks/encoder.h"), + File("blocks/generic.h"), + File("blocks/math.h"), + File("subghz_setting.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="subghz") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 6df4b1a8a..f0dc66e89 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -12,7 +12,7 @@ App( ], provides=["subghz_start"], icon="A_Sub1ghz_14", - stack_size=2 * 1024, + stack_size=3 * 1024, order=10, ) diff --git a/applications/main/subghz/blocks/const.c b/applications/main/subghz/blocks/const.c new file mode 100644 index 000000000..15719b2ac --- /dev/null +++ b/applications/main/subghz/blocks/const.c @@ -0,0 +1 @@ +#include "const.h" diff --git a/applications/main/subghz/blocks/const.h b/applications/main/subghz/blocks/const.h new file mode 100644 index 000000000..f32334e2f --- /dev/null +++ b/applications/main/subghz/blocks/const.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const uint16_t te_long; + const uint16_t te_short; + const uint16_t te_delta; + const uint8_t min_count_bit_for_found; +} SubGhzBlockConst; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/subghz/blocks/decoder.c b/applications/main/subghz/blocks/decoder.c new file mode 100644 index 000000000..f491c87bf --- /dev/null +++ b/applications/main/subghz/blocks/decoder.c @@ -0,0 +1,27 @@ +#include "decoder.h" + +#define TAG "SubGhzBlockDecoder" + +void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit) { + decoder->decode_data = decoder->decode_data << 1 | bit; + decoder->decode_count_bit++; +} + +void subghz_protocol_blocks_add_to_128_bit( + SubGhzBlockDecoder* decoder, + uint8_t bit, + uint64_t* head_64_bit) { + if(++decoder->decode_count_bit > 64) { + (*head_64_bit) = ((*head_64_bit) << 1) | (decoder->decode_data >> 63); + } + decoder->decode_data = decoder->decode_data << 1 | bit; +} + +uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len) { + uint8_t hash = 0; + uint8_t* p = (uint8_t*)&decoder->decode_data; + for(size_t i = 0; i < len; i++) { + hash ^= p[i]; + } + return hash; +} diff --git a/applications/main/subghz/blocks/decoder.h b/applications/main/subghz/blocks/decoder.h new file mode 100644 index 000000000..a5e561e35 --- /dev/null +++ b/applications/main/subghz/blocks/decoder.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzBlockDecoder SubGhzBlockDecoder; + +struct SubGhzBlockDecoder { + uint32_t parser_step; + uint32_t te_last; + uint64_t decode_data; + uint8_t decode_count_bit; +}; + +/** + * Add data bit when decoding. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @param bit data, 1bit + */ +void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit); + +/** + * Add data to_128 bit when decoding. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @param head_64_bit Pointer to a head_64_bit + * @param bit data, 1bit + */ +void subghz_protocol_blocks_add_to_128_bit( + SubGhzBlockDecoder* decoder, + uint8_t bit, + uint64_t* head_64_bit); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/subghz/blocks/encoder.c b/applications/main/subghz/blocks/encoder.c new file mode 100644 index 000000000..49ec4f177 --- /dev/null +++ b/applications/main/subghz/blocks/encoder.c @@ -0,0 +1,58 @@ +#include "encoder.h" +#include "math.h" +#include + +#include "furi.h" + +#define TAG "SubGhzBlockEncoder" + +void subghz_protocol_blocks_set_bit_array( + bool bit_value, + uint8_t data_array[], + size_t set_index_bit, + size_t max_size_array) { + furi_assert(set_index_bit < max_size_array * 8); + bit_write(data_array[set_index_bit >> 3], 7 - (set_index_bit & 0x7), bit_value); +} + +bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_index_bit) { + return bit_read(data_array[read_index_bit >> 3], 7 - (read_index_bit & 0x7)); +} + +size_t subghz_protocol_blocks_get_upload_from_bit_array( + uint8_t data_array[], + size_t count_bit_data_array, + LevelDuration* upload, + size_t max_size_upload, + uint32_t duration_bit, + SubGhzProtocolBlockAlignBit align_bit) { + size_t bias_bit = 0; + size_t size_upload = 0; + uint32_t duration = duration_bit; + + if(align_bit == SubGhzProtocolBlockAlignBitRight) { + if(count_bit_data_array & 0x7) { + bias_bit = 8 - (count_bit_data_array & 0x7); + } + } + size_t index_bit = bias_bit; + + bool last_bit = subghz_protocol_blocks_get_bit_array(data_array, index_bit++); + for(size_t i = 1 + bias_bit; i < count_bit_data_array + bias_bit; i++) { + if(last_bit == subghz_protocol_blocks_get_bit_array(data_array, index_bit)) { + duration += duration_bit; + } else { + if(size_upload > max_size_upload) { + furi_crash("SubGhz: Encoder buffer overflow"); + } + upload[size_upload++] = level_duration_make( + subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); + last_bit = !last_bit; + duration = duration_bit; + } + index_bit++; + } + upload[size_upload++] = level_duration_make( + subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); + return size_upload; +} diff --git a/applications/main/subghz/blocks/encoder.h b/applications/main/subghz/blocks/encoder.h new file mode 100644 index 000000000..aeaa2add0 --- /dev/null +++ b/applications/main/subghz/blocks/encoder.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool is_running; + size_t repeat; + size_t front; + size_t size_upload; + LevelDuration* upload; + +} SubGhzProtocolBlockEncoder; + +typedef enum { + SubGhzProtocolBlockAlignBitLeft, + SubGhzProtocolBlockAlignBitRight, +} SubGhzProtocolBlockAlignBit; + +/** + * Set data bit when encoding HEX array. + * @param bit_value The value of the bit to be set + * @param data_array Pointer to a HEX array + * @param set_index_bit Number set a bit in the array starting from the left + * @param max_size_array array size, check not to overflow + */ +void subghz_protocol_blocks_set_bit_array( + bool bit_value, + uint8_t data_array[], + size_t set_index_bit, + size_t max_size_array); + +/** + * Get data bit when encoding HEX array. + * @param data_array Pointer to a HEX array + * @param read_index_bit Number get a bit in the array starting from the left + * @return bool value bit + */ +bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_index_bit); + +/** + * Generating an upload from data. + * @param data_array Pointer to a HEX array + * @param count_bit_data_array How many bits in the array are processed + * @param upload Pointer to a LevelDuration + * @param max_size_upload upload size, check not to overflow + * @param duration_bit duration 1 bit + * @param align_bit alignment of useful bits in an array + */ +size_t subghz_protocol_blocks_get_upload_from_bit_array( + uint8_t data_array[], + size_t count_bit_data_array, + LevelDuration* upload, + size_t max_size_upload, + uint32_t duration_bit, + SubGhzProtocolBlockAlignBit align_bit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/subghz/blocks/generic.c b/applications/main/subghz/blocks/generic.c new file mode 100644 index 000000000..3d59adc82 --- /dev/null +++ b/applications/main/subghz/blocks/generic.c @@ -0,0 +1,120 @@ +#include "generic.h" +#include +#include + +#define TAG "SubGhzBlockGeneric" + +void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +bool subghz_block_generic_serialize( + SubGhzBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + uint32_t temp = instance->data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + 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->data >> (i * 8)) & 0xFF; + } + + if(!flipper_format_write_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + instance->data_count_bit = (uint16_t)temp_data; + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Key"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + + res = true; + } while(0); + + furi_string_free(temp_str); + + return res; +} diff --git a/applications/main/subghz/blocks/generic.h b/applications/main/subghz/blocks/generic.h new file mode 100644 index 000000000..e69de8b4f --- /dev/null +++ b/applications/main/subghz/blocks/generic.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include "../types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzBlockGeneric SubGhzBlockGeneric; + +struct SubGhzBlockGeneric { + const char* protocol_name; + uint64_t data; + uint64_t data_2; + uint32_t serial; + uint16_t data_count_bit; + uint8_t btn; + uint32_t cnt; + uint8_t cnt_2; + uint32_t seed; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data SubGhzBlockGeneric. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_block_generic_serialize( + SubGhzBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzBlockGeneric. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/subghz/blocks/math.c b/applications/main/subghz/blocks/math.c new file mode 100644 index 000000000..24202ad1c --- /dev/null +++ b/applications/main/subghz/blocks/math.c @@ -0,0 +1,244 @@ +#include "math.h" + +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) { + uint64_t reverse_key = 0; + for(uint8_t i = 0; i < bit_count; i++) { + reverse_key = reverse_key << 1 | bit_read(key, i); + } + return reverse_key; +} + +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count) { + uint8_t parity = 0; + for(uint8_t i = 0; i < bit_count; i++) { + parity += bit_read(key, i); + } + return parity & 0x01; +} + +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init << 4; // LSBs are unused + uint8_t poly = polynomial << 4; + uint8_t bit; + + while(size--) { + remainder ^= *message++; + for(bit = 0; bit < 8; bit++) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 4 & 0x0f; // discard the LSBs +} + +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init << 1; // LSB is unused + uint8_t poly = polynomial << 1; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 1 & 0x7f; // discard the LSB +} + +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8); + polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8); + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte] << 8; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x8000) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + // Process message from last byte to first byte (reflected) + for(int byte = size - 1; byte >= 0; --byte) { + uint8_t data = message[byte]; + // Process individual bits of each byte (reflected) + for(uint8_t i = 0; i < 8; ++i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) { + sum ^= key; + } + + // roll the key left (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped lsb as MSB) + if(key & 0x80) + key = (key << 1) ^ gen; + else + key = (key << 1); + } + } + return sum; +} + +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + size_t size, + uint16_t gen, + uint16_t key) { + uint16_t sum = 0; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int8_t i = 7; i >= 0; --i) { + // if data bit is set then xor with key + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) { + uint32_t result = 0; + for(size_t i = 0; i < size; ++i) { + result += message[i]; + } + return (uint8_t)result; +} + +uint8_t subghz_protocol_blocks_parity8(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x6996 >> byte) & 1; +} + +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { + result ^= subghz_protocol_blocks_parity8(message[i]); + } + return result; +} + +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { + result ^= message[i]; + } + return result; +} \ No newline at end of file diff --git a/applications/main/subghz/blocks/math.h b/applications/main/subghz/blocks/math.h new file mode 100644 index 000000000..dcea3da5f --- /dev/null +++ b/applications/main/subghz/blocks/math.h @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include + +#define bit_read(value, bit) (((value) >> (bit)) & 0x01) +#define bit_set(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) |= (_one << (bit)); \ + }) +#define bit_clear(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) &= ~(_one << (bit)); \ + }) +#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) +#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) + +#ifdef __cplusplus +extern "C" { +#endif + +/** Flip the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return Reverse data + */ +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count); + +/** Get parity the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return parity + */ +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count); + +/** CRC-4 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** CRC-7 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** Generic Cyclic Redundancy Check CRC-8. Example polynomial: 0x31 = x8 + x5 + + * x4 + 1 (x8 is implicit) Example polynomial: 0x80 = x8 + x7 (a normal + * bit-by-bit parity XOR) + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** "Little-endian" Cyclic Redundancy Check CRC-8 LE Input and output are + * reflected, i.e. least significant bit is shifted in first + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** CRC-16 LSB. Input and output are reflected, i.e. least significant bit is + * shifted in first. Note that poly and init already need to be reflected + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init); + +/** CRC-16 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init); + +/** Digest-8 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key); + +/** Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key); + +/** Digest-16 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + size_t size, + uint16_t gen, + uint16_t key); + +/** Compute Addition of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value + */ +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size); + +/** Compute bit parity of a single byte (8 bits) + * + * @param byte single byte to check + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity8(uint8_t byte); + +/** Compute bit parity of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size); + +/** Compute XOR (byte-wide parity) of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value, per bit-position 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/environment.c b/applications/main/subghz/environment.c new file mode 100644 index 000000000..b39b259d4 --- /dev/null +++ b/applications/main/subghz/environment.c @@ -0,0 +1,116 @@ +#include "environment.h" +#include "registry.h" + +struct SubGhzEnvironment { + SubGhzKeystore* keystore; + const SubGhzProtocolRegistry* protocol_registry; + const char* came_atomo_rainbow_table_file_name; + const char* nice_flor_s_rainbow_table_file_name; + const char* alutech_at_4n_rainbow_table_file_name; +}; + +SubGhzEnvironment* subghz_environment_alloc() { + SubGhzEnvironment* instance = malloc(sizeof(SubGhzEnvironment)); + + instance->keystore = subghz_keystore_alloc(); + instance->protocol_registry = NULL; + instance->came_atomo_rainbow_table_file_name = NULL; + instance->nice_flor_s_rainbow_table_file_name = NULL; + + return instance; +} + +void subghz_environment_free(SubGhzEnvironment* instance) { + furi_assert(instance); + + instance->protocol_registry = NULL; + instance->came_atomo_rainbow_table_file_name = NULL; + instance->nice_flor_s_rainbow_table_file_name = NULL; + subghz_keystore_free(instance->keystore); + + free(instance); +} + +bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename) { + furi_assert(instance); + + return subghz_keystore_load(instance->keystore, filename); +} + +SubGhzKeystore* subghz_environment_get_keystore(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->keystore; +} + +void subghz_environment_set_came_atomo_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename) { + furi_assert(instance); + + instance->came_atomo_rainbow_table_file_name = filename; +} + +const char* + subghz_environment_get_came_atomo_rainbow_table_file_name(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->came_atomo_rainbow_table_file_name; +} + +void subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename) { + furi_assert(instance); + + instance->alutech_at_4n_rainbow_table_file_name = filename; +} + +const char* + subghz_environment_get_alutech_at_4n_rainbow_table_file_name(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->alutech_at_4n_rainbow_table_file_name; +} + +void subghz_environment_set_nice_flor_s_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename) { + furi_assert(instance); + + instance->nice_flor_s_rainbow_table_file_name = filename; +} + +const char* + subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->nice_flor_s_rainbow_table_file_name; +} + +void subghz_environment_set_protocol_registry( + SubGhzEnvironment* instance, + void* protocol_registry_items) { + furi_assert(instance); + const SubGhzProtocolRegistry* protocol_registry = protocol_registry_items; + instance->protocol_registry = protocol_registry; +} + +void* subghz_environment_get_protocol_registry(SubGhzEnvironment* instance) { + furi_assert(instance); + furi_assert(instance->protocol_registry); + return (void*)instance->protocol_registry; +} + +const char* + subghz_environment_get_protocol_name_registry(SubGhzEnvironment* instance, size_t idx) { + furi_assert(instance); + furi_assert(instance->protocol_registry); + const SubGhzProtocol* protocol = + subghz_protocol_registry_get_by_index(instance->protocol_registry, idx); + if(protocol != NULL) { + return protocol->name; + } else { + return NULL; + } +} \ No newline at end of file diff --git a/applications/main/subghz/environment.h b/applications/main/subghz/environment.h new file mode 100644 index 000000000..7bd38ba2f --- /dev/null +++ b/applications/main/subghz/environment.h @@ -0,0 +1,115 @@ +#pragma once + +#include + +#include "subghz_keystore.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzEnvironment SubGhzEnvironment; + +/** + * Allocate SubGhzEnvironment. + * @return SubGhzEnvironment* pointer to a SubGhzEnvironment instance + */ +SubGhzEnvironment* subghz_environment_alloc(); + +/** + * Free SubGhzEnvironment. + * @param instance Pointer to a SubGhzEnvironment instance + */ +void subghz_environment_free(SubGhzEnvironment* instance); + +/** + * Downloading the manufacture key file. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + * @return true On success + */ +bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename); + +/** + * Get pointer to a SubGhzKeystore* instance. + * @return SubGhzEnvironment* pointer to a SubGhzEnvironment instance + */ +SubGhzKeystore* subghz_environment_get_keystore(SubGhzEnvironment* instance); + +/** + * Set filename to work with Came Atomo. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + */ +void subghz_environment_set_came_atomo_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename); + +/** + * Get filename to work with Came Atomo. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Full path to the file + */ +const char* subghz_environment_get_came_atomo_rainbow_table_file_name(SubGhzEnvironment* instance); + +/** + * Set filename to work with Alutech at-4n. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + */ +void subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename); + +/** + * Get filename to work with Alutech at-4n. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Full path to the file + */ +const char* + subghz_environment_get_alutech_at_4n_rainbow_table_file_name(SubGhzEnvironment* instance); + +/** + * Set filename to work with Nice Flor-S. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + */ +void subghz_environment_set_nice_flor_s_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename); + +/** + * Get filename to work with Nice Flor-S. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Full path to the file + */ +const char* + subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance); + +/** + * Set list of protocols to work. + * @param instance Pointer to a SubGhzEnvironment instance + * @param protocol_registry_items Pointer to a SubGhzProtocolRegistry + */ +void subghz_environment_set_protocol_registry( + SubGhzEnvironment* instance, + void* protocol_registry_items); + +/** + * Get list of protocols to work. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Pointer to a SubGhzProtocolRegistry + */ +void* subghz_environment_get_protocol_registry(SubGhzEnvironment* instance); + +/** + * Get list of protocols names. + * @param instance Pointer to a SubGhzEnvironment instance + * @param idx index protocols + * @return Pointer to a SubGhzProtocolRegistry + */ +const char* subghz_environment_get_protocol_name_registry(SubGhzEnvironment* instance, size_t idx); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 0559ac67e..350e68ee6 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -50,7 +50,6 @@ typedef enum { SubGhzCustomEventSceneAnalyzerLock, SubGhzCustomEventSceneAnalyzerUnlock, SubGhzCustomEventSceneSettingLock, - SubGhzCustomEventSceneSettingError, SubGhzCustomEventSceneExit, SubGhzCustomEventSceneStay, diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 7dc67c9f2..6452792a6 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -36,13 +36,13 @@ struct SubGhzFrequencyAnalyzerWorker { }; static void subghz_frequency_analyzer_worker_load_registers(const uint8_t data[][2]) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); size_t i = 0; while(data[i][0]) { - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i][0], data[i][1]); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, data[i][0], data[i][1]); i++; } - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } // running average with adaptive coefficient @@ -80,26 +80,26 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { //Start CC1101 furi_hal_subghz_reset(); - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_flush_rx(furi_hal_subghz.spi_bus_handle); + cc1101_flush_tx(furi_hal_subghz.spi_bus_handle); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_MDMCFG3, 0b01111111); // symbol rate cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, + furi_hal_subghz.spi_bus_handle, CC1101_AGCCTRL2, 0b00000111); // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAGN_TARGET 42 dB cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, + furi_hal_subghz.spi_bus_handle, CC1101_AGCCTRL1, 0b00001000); // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 1000 - Absolute carrier sense threshold disabled cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, + furi_hal_subghz.spi_bus_handle, CC1101_AGCCTRL0, 0b00110000); // 00 - No hysteresis, medium asymmetric dead zone, medium gain ; 11 - 64 samples agc; 00 - Normal AGC, 00 - 4dB boundary - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); @@ -118,20 +118,23 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // First stage: coarse scan for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { if(furi_hal_subghz_is_frequency_valid( - subghz_setting_get_frequency(instance->setting, i))) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + subghz_setting_get_frequency(instance->setting, i)) && + !((furi_hal_subghz.radio_type == SubGhzRadioExternal) && + (subghz_setting_get_frequency(instance->setting, i) >= 311900000 && + subghz_setting_get_frequency(instance->setting, i) <= 312200000))) { + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); frequency = cc1101_set_frequency( - &furi_hal_spi_bus_handle_subghz, + furi_hal_subghz.spi_bus_handle, subghz_setting_get_frequency(instance->setting, i)); - cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + cc1101_calibrate(furi_hal_subghz.spi_bus_handle); do { - status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + status = cc1101_get_status(furi_hal_subghz.spi_bus_handle); } while(status.STATE != CC1101StateIDLE); - cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_rx(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); furi_delay_ms(2); @@ -166,17 +169,17 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { i < frequency_rssi.frequency_coarse + 300000; i += 20000) { if(furi_hal_subghz_is_frequency_valid(i)) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); - frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); + frequency = cc1101_set_frequency(furi_hal_subghz.spi_bus_handle, i); - cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + cc1101_calibrate(furi_hal_subghz.spi_bus_handle); do { - status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + status = cc1101_get_status(furi_hal_subghz.spi_bus_handle); } while(status.STATE != CC1101StateIDLE); - cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_rx(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); furi_delay_ms(2); diff --git a/applications/main/subghz/protocols/alutech_at_4n.c b/applications/main/subghz/protocols/alutech_at_4n.c new file mode 100644 index 000000000..6bcf9f25d --- /dev/null +++ b/applications/main/subghz/protocols/alutech_at_4n.c @@ -0,0 +1,455 @@ +#include "alutech_at_4n.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoAlutech_at_4n" + +#define SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE 0xFFFFFFFF + +static const SubGhzBlockConst subghz_protocol_alutech_at_4n_const = { + .te_short = 400, + .te_long = 800, + .te_delta = 140, + .min_count_bit_for_found = 72, +}; + +struct SubGhzProtocolDecoderAlutech_at_4n { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint64_t data; + uint32_t crc; + uint16_t header_count; + + const char* alutech_at_4n_rainbow_table_file_name; +}; + +struct SubGhzProtocolEncoderAlutech_at_4n { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Alutech_at_4nDecoderStepReset = 0, + Alutech_at_4nDecoderStepCheckPreambula, + Alutech_at_4nDecoderStepSaveDuration, + Alutech_at_4nDecoderStepCheckDuration, +} Alutech_at_4nDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder = { + .alloc = subghz_protocol_decoder_alutech_at_4n_alloc, + .free = subghz_protocol_decoder_alutech_at_4n_free, + + .feed = subghz_protocol_decoder_alutech_at_4n_feed, + .reset = subghz_protocol_decoder_alutech_at_4n_reset, + + .get_hash_data = subghz_protocol_decoder_alutech_at_4n_get_hash_data, + .serialize = subghz_protocol_decoder_alutech_at_4n_serialize, + .deserialize = subghz_protocol_decoder_alutech_at_4n_deserialize, + .get_string = subghz_protocol_decoder_alutech_at_4n_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_alutech_at_4n = { + .name = SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_alutech_at_4n_decoder, + .encoder = &subghz_protocol_alutech_at_4n_encoder, +}; + +/** + * Read bytes from rainbow table + * @param file_name Full path to rainbow table the file + * @param number_alutech_at_4n_magic_data number in the array + * @return alutech_at_4n_magic_data + */ +static uint32_t subghz_protocol_alutech_at_4n_get_magic_data_in_file( + const char* file_name, + uint8_t number_alutech_at_4n_magic_data) { + if(!strcmp(file_name, "")) return SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE; + + uint8_t buffer[sizeof(uint32_t)] = {0}; + uint32_t address = number_alutech_at_4n_magic_data * sizeof(uint32_t); + uint32_t alutech_at_4n_magic_data = 0; + + if(subghz_keystore_raw_get_data(file_name, address, buffer, sizeof(uint32_t))) { + for(size_t i = 0; i < sizeof(uint32_t); i++) { + alutech_at_4n_magic_data = (alutech_at_4n_magic_data << 8) | buffer[i]; + } + } else { + alutech_at_4n_magic_data = SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE; + } + return alutech_at_4n_magic_data; +} + +static uint8_t subghz_protocol_alutech_at_4n_crc(uint64_t data) { + uint8_t* p = (uint8_t*)&data; + uint8_t crc = 0xff; + for(uint8_t y = 0; y < 8; y++) { + crc = crc ^ p[y]; + for(uint8_t i = 0; i < 8; i++) { + if((crc & 0x80) != 0) { + crc <<= 1; + crc ^= 0x31; + } else { + crc <<= 1; + } + } + } + return crc; +} + +static uint8_t subghz_protocol_alutech_at_4n_decrypt_data_crc(uint8_t data) { + uint8_t crc = data; + for(uint8_t i = 0; i < 8; i++) { + if((crc & 0x80) != 0) { + crc <<= 1; + crc ^= 0x31; + } else { + crc <<= 1; + } + } + return ~crc; +} + +static uint64_t subghz_protocol_alutech_at_4n_decrypt(uint64_t data, const char* file_name) { + uint8_t* p = (uint8_t*)&data; + uint32_t data1 = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; + uint32_t data2 = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7]; + uint32_t data3 = 0; + uint32_t magic_data[] = { + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 0), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 1), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 2), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 3), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 4), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 5)}; + + uint32_t i = magic_data[0]; + do { + data2 = data2 - + ((magic_data[1] + (data1 << 4)) ^ ((magic_data[2] + (data1 >> 5)) ^ (data1 + i))); + data3 = data2 + i; + i += magic_data[3]; + data1 = + data1 - ((magic_data[4] + (data2 << 4)) ^ ((magic_data[5] + (data2 >> 5)) ^ data3)); + } while(i != 0); + + p[0] = (uint8_t)(data1 >> 24); + p[1] = (uint8_t)(data1 >> 16); + p[3] = (uint8_t)data1; + p[4] = (uint8_t)(data2 >> 24); + p[5] = (uint8_t)(data2 >> 16); + p[2] = (uint8_t)(data1 >> 8); + p[6] = (uint8_t)(data2 >> 8); + p[7] = (uint8_t)data2; + + return data; +} + +// static uint64_t subghz_protocol_alutech_at_4n_encrypt(uint64_t data, const char* file_name) { +// uint8_t* p = (uint8_t*)&data; +// uint32_t data1 = 0; +// uint32_t data2 = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +// uint32_t data3 = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7]; +// uint32_t magic_data[] = { +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 6), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 4), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 5), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 1), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 2), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 0)}; + +// do { +// data1 = data1 + magic_data[0]; +// data2 = data2 + ((magic_data[1] + (data3 << 4)) ^ +// ((magic_data[2] + (data3 >> 5)) ^ (data1 + data3))); +// data3 = data3 + ((magic_data[3] + (data2 << 4)) ^ +// ((magic_data[4] + (data2 >> 5)) ^ (data1 + data2))); +// } while(data1 != magic_data[5]); +// p[0] = (uint8_t)(data2 >> 24); +// p[1] = (uint8_t)(data2 >> 16); +// p[3] = (uint8_t)data2; +// p[4] = (uint8_t)(data3 >> 24); +// p[5] = (uint8_t)(data3 >> 16); +// p[2] = (uint8_t)(data2 >> 8); +// p[6] = (uint8_t)(data3 >> 8); +// p[7] = (uint8_t)data3; + +// return data; +// } + +void* subghz_protocol_decoder_alutech_at_4n_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderAlutech_at_4n* instance = + malloc(sizeof(SubGhzProtocolDecoderAlutech_at_4n)); + instance->base.protocol = &subghz_protocol_alutech_at_4n; + instance->generic.protocol_name = instance->base.protocol->name; + instance->alutech_at_4n_rainbow_table_file_name = + subghz_environment_get_alutech_at_4n_rainbow_table_file_name(environment); + if(instance->alutech_at_4n_rainbow_table_file_name) { + FURI_LOG_I( + TAG, "Loading rainbow table from %s", instance->alutech_at_4n_rainbow_table_file_name); + } + return instance; +} + +void subghz_protocol_decoder_alutech_at_4n_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + instance->alutech_at_4n_rainbow_table_file_name = NULL; + free(instance); +} + +void subghz_protocol_decoder_alutech_at_4n_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; +} + +void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + + switch(instance->decoder.parser_step) { + case Alutech_at_4nDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta) { + instance->decoder.parser_step = Alutech_at_4nDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case Alutech_at_4nDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta)) { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short * 10) < + subghz_protocol_alutech_at_4n_const.te_delta * 10)) { + // Found header + instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + instance->header_count = 0; + } + break; + case Alutech_at_4nDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Alutech_at_4nDecoderStepCheckDuration; + } + break; + case Alutech_at_4nDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_alutech_at_4n_const.te_short * 2 + + subghz_protocol_alutech_at_4n_const.te_delta)) { + //add last bit + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_long) < + subghz_protocol_alutech_at_4n_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + + // Found end TX + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + if(instance->decoder.decode_count_bit == + subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { + if(instance->generic.data != instance->data) { + instance->generic.data = instance->data; + + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->crc = instance->decoder.decode_data; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_long) < + subghz_protocol_alutech_at_4n_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + if(instance->decoder.decode_count_bit == 64) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_long) < + subghz_protocol_alutech_at_4n_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + if(instance->decoder.decode_count_bit == 64) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file + */ +static void subghz_protocol_alutech_at_4n_remote_controller( + SubGhzBlockGeneric* instance, + uint8_t crc, + const char* file_name) { + /** + * Message format 72bit LSB first + * data crc + * XXXXXXXXXXXXXXXX CC + * + * For analysis, you need to turn the package MSB + * in decoded messages format + * + * crc1 serial cnt key + * cc SSSSSSSS XXxx BB + * + * crc1 is calculated from the lower part of cnt + * key 1=0xff, 2=0x11, 3=0x22, 4=0x33, 5=0x44 + * + */ + + bool status = false; + uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 64); + crc = subghz_protocol_blocks_reverse_key(crc, 8); + + if(crc == subghz_protocol_alutech_at_4n_crc(data)) { + data = subghz_protocol_alutech_at_4n_decrypt(data, file_name); + status = true; + } + + if(status && ((uint8_t)(data >> 56) == + subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)((data >> 8) & 0xFF)))) { + instance->btn = (uint8_t)data & 0xFF; + instance->cnt = (uint16_t)(data >> 8) & 0xFFFF; + instance->serial = (uint32_t)(data >> 24) & 0xFFFFFFFF; + } + + if(!status) { + instance->btn = 0; + instance->cnt = 0; + instance->serial = 0; + } +} + +uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + return (uint8_t)instance->crc; +} + +bool subghz_protocol_decoder_alutech_at_4n_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "CRC", &instance->crc, 1)) { + FURI_LOG_E(TAG, "Unable to add CRC"); + res = false; + } + return res; + + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_alutech_at_4n_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "CRC", (uint32_t*)&instance->crc, 1)) { + FURI_LOG_E(TAG, "Missing CRC"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + subghz_protocol_alutech_at_4n_remote_controller( + &instance->generic, instance->crc, instance->alutech_at_4n_rainbow_table_file_name); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %d\r\n" + "Key:0x%08lX%08lX%02X\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%03lX\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + (uint8_t)instance->crc, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/applications/main/subghz/protocols/alutech_at_4n.h b/applications/main/subghz/protocols/alutech_at_4n.h new file mode 100644 index 000000000..38bac3ea6 --- /dev/null +++ b/applications/main/subghz/protocols/alutech_at_4n.h @@ -0,0 +1,74 @@ +#pragma once +#include "base.h" + +#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; + +extern const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder; +extern const SubGhzProtocol subghz_protocol_alutech_at_4n; + +/** + * Allocate SubGhzProtocolDecoderAlutech_at_4n. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderAlutech_at_4n* pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + */ +void* subghz_protocol_decoder_alutech_at_4n_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + */ +void subghz_protocol_decoder_alutech_at_4n_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + */ +void subghz_protocol_decoder_alutech_at_4n_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_alutech_at_4n_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_alutech_at_4n_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param output Resulting text + */ +void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/ansonic.c b/applications/main/subghz/protocols/ansonic.c new file mode 100644 index 000000000..81b196e36 --- /dev/null +++ b/applications/main/subghz/protocols/ansonic.c @@ -0,0 +1,346 @@ +#include "ansonic.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolAnsonic" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0800 ? '1' : '0'), (dip & 0x0400 ? '1' : '0'), (dip & 0x0200 ? '1' : '0'), \ + (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \ + (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_ansonic_const = { + .te_short = 555, + .te_long = 1111, + .te_delta = 120, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderAnsonic { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderAnsonic { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + AnsonicDecoderStepReset = 0, + AnsonicDecoderStepFoundStartBit, + AnsonicDecoderStepSaveDuration, + AnsonicDecoderStepCheckDuration, +} AnsonicDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder = { + .alloc = subghz_protocol_decoder_ansonic_alloc, + .free = subghz_protocol_decoder_ansonic_free, + + .feed = subghz_protocol_decoder_ansonic_feed, + .reset = subghz_protocol_decoder_ansonic_reset, + + .get_hash_data = subghz_protocol_decoder_ansonic_get_hash_data, + .serialize = subghz_protocol_decoder_ansonic_serialize, + .deserialize = subghz_protocol_decoder_ansonic_deserialize, + .get_string = subghz_protocol_decoder_ansonic_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder = { + .alloc = subghz_protocol_encoder_ansonic_alloc, + .free = subghz_protocol_encoder_ansonic_free, + + .deserialize = subghz_protocol_encoder_ansonic_deserialize, + .stop = subghz_protocol_encoder_ansonic_stop, + .yield = subghz_protocol_encoder_ansonic_yield, +}; + +const SubGhzProtocol subghz_protocol_ansonic = { + .name = SUBGHZ_PROTOCOL_ANSONIC_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_FM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_ansonic_decoder, + .encoder = &subghz_protocol_ansonic_encoder, +}; + +void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderAnsonic* instance = malloc(sizeof(SubGhzProtocolEncoderAnsonic)); + + instance->base.protocol = &subghz_protocol_ansonic; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_ansonic_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderAnsonic* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderAnsonic instance + * @return true On success + */ +static bool subghz_protocol_encoder_ansonic_get_upload(SubGhzProtocolEncoderAnsonic* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short * 35); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short); + } + } + return true; +} + +bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderAnsonic* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_ansonic_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_ansonic_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_ansonic_stop(void* context) { + SubGhzProtocolEncoderAnsonic* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_ansonic_yield(void* context) { + SubGhzProtocolEncoderAnsonic* 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_ansonic_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderAnsonic* instance = malloc(sizeof(SubGhzProtocolDecoderAnsonic)); + instance->base.protocol = &subghz_protocol_ansonic; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_ansonic_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + free(instance); +} + +void subghz_protocol_decoder_ansonic_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + instance->decoder.parser_step = AnsonicDecoderStepReset; +} + +void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + + switch(instance->decoder.parser_step) { + case AnsonicDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short * 35) < + subghz_protocol_ansonic_const.te_delta * 35)) { + //Found header Ansonic + instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit; + } + break; + case AnsonicDecoderStepFoundStartBit: + if(!level) { + break; + } else if( + DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) < + subghz_protocol_ansonic_const.te_delta) { + //Found start bit Ansonic + instance->decoder.parser_step = AnsonicDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = AnsonicDecoderStepReset; + } + break; + case AnsonicDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_ansonic_const.te_short * 4)) { + instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_ansonic_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + 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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = AnsonicDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = AnsonicDecoderStepReset; + } + break; + case AnsonicDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_short) < + subghz_protocol_ansonic_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_long) < + subghz_protocol_ansonic_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = AnsonicDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_long) < + subghz_protocol_ansonic_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) < + subghz_protocol_ansonic_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = AnsonicDecoderStepSaveDuration; + } else + instance->decoder.parser_step = AnsonicDecoderStepReset; + } else { + instance->decoder.parser_step = AnsonicDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_ansonic_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * 12345678(10) k 9 + * AAA => 10101010 1 01 0 + * + * 1...10 - DIP + * k- KEY + */ + instance->cnt = instance->data & 0xFFF; + instance->btn = ((instance->data >> 1) & 0x3); +} + +uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_ansonic_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_ansonic_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + subghz_protocol_ansonic_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%03lX\r\n" + "Btn:%X\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.btn, + CNT_TO_DIP(instance->generic.cnt)); +} diff --git a/applications/main/subghz/protocols/ansonic.h b/applications/main/subghz/protocols/ansonic.h new file mode 100644 index 000000000..0170a6048 --- /dev/null +++ b/applications/main/subghz/protocols/ansonic.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ANSONIC_NAME "Ansonic" + +typedef struct SubGhzProtocolDecoderAnsonic SubGhzProtocolDecoderAnsonic; +typedef struct SubGhzProtocolEncoderAnsonic SubGhzProtocolEncoderAnsonic; + +extern const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder; +extern const SubGhzProtocol subghz_protocol_ansonic; + +/** + * Allocate SubGhzProtocolEncoderAnsonic. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderAnsonic* pointer to a SubGhzProtocolEncoderAnsonic instance + */ +void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderAnsonic. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + */ +void subghz_protocol_encoder_ansonic_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + */ +void subghz_protocol_encoder_ansonic_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_ansonic_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderAnsonic. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderAnsonic* pointer to a SubGhzProtocolDecoderAnsonic instance + */ +void* subghz_protocol_decoder_ansonic_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + */ +void subghz_protocol_decoder_ansonic_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + */ +void subghz_protocol_decoder_ansonic_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_ansonic_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param output Resulting text + */ +void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/base.c b/applications/main/subghz/protocols/base.c new file mode 100644 index 000000000..36f33b9a5 --- /dev/null +++ b/applications/main/subghz/protocols/base.c @@ -0,0 +1,62 @@ +#include "base.h" +#include "registry.h" + +void subghz_protocol_decoder_base_set_decoder_callback( + SubGhzProtocolDecoderBase* decoder_base, + SubGhzProtocolDecoderBaseRxCallback callback, + void* context) { + decoder_base->callback = callback; + decoder_base->context = context; +} + +bool subghz_protocol_decoder_base_get_string( + SubGhzProtocolDecoderBase* decoder_base, + FuriString* output) { + bool status = false; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->get_string) { + decoder_base->protocol->decoder->get_string(decoder_base, output); + status = true; + } + + return status; +} + +bool subghz_protocol_decoder_base_serialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + bool status = false; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->serialize) { + status = decoder_base->protocol->decoder->serialize(decoder_base, flipper_format, preset); + } + + return status; +} + +bool subghz_protocol_decoder_base_deserialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format) { + bool status = false; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->deserialize) { + status = decoder_base->protocol->decoder->deserialize(decoder_base, flipper_format); + } + + return status; +} + +uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base) { + uint8_t hash = 0; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->get_hash_data) { + hash = decoder_base->protocol->decoder->get_hash_data(decoder_base); + } + + return hash; +} diff --git a/applications/main/subghz/protocols/base.h b/applications/main/subghz/protocols/base.h new file mode 100644 index 000000000..1f3d3e1be --- /dev/null +++ b/applications/main/subghz/protocols/base.h @@ -0,0 +1,88 @@ +#pragma once + +#include "../types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzProtocolDecoderBase SubGhzProtocolDecoderBase; + +typedef void ( + *SubGhzProtocolDecoderBaseRxCallback)(SubGhzProtocolDecoderBase* instance, void* context); + +typedef void (*SubGhzProtocolDecoderBaseSerialize)( + SubGhzProtocolDecoderBase* decoder_base, + FuriString* output); + +struct SubGhzProtocolDecoderBase { + // Decoder general section + const SubGhzProtocol* protocol; + + // Callback section + SubGhzProtocolDecoderBaseRxCallback callback; + void* context; +}; + +/** + * Set a callback upon completion of successful decoding of one of the protocols. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param callback Callback, SubGhzProtocolDecoderBaseRxCallback + * @param context Context + */ +void subghz_protocol_decoder_base_set_decoder_callback( + SubGhzProtocolDecoderBase* decoder_base, + SubGhzProtocolDecoderBaseRxCallback callback, + void* context); + +/** + * Getting a textual representation of the received data. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param output Resulting text + */ +bool subghz_protocol_decoder_base_get_string( + SubGhzProtocolDecoderBase* decoder_base, + FuriString* output); + +/** + * Serialize data SubGhzProtocolDecoderBase. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_base_serialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderBase. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_base_deserialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base); + +// Encoder Base +typedef struct SubGhzProtocolEncoderBase SubGhzProtocolEncoderBase; + +struct SubGhzProtocolEncoderBase { + // Decoder general section + const SubGhzProtocol* protocol; + + // Callback section +}; + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/protocols/bett.c b/applications/main/subghz/protocols/bett.c new file mode 100644 index 000000000..644d80fd8 --- /dev/null +++ b/applications/main/subghz/protocols/bett.c @@ -0,0 +1,342 @@ +#include "bett.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +// protocol BERNER / ELKA / TEDSEN / TELETASTER +#define TAG "SubGhzProtocolBETT" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c" +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0x8) >> 0x8) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_bett_const = { + .te_short = 340, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderBETT { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderBETT { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + BETTDecoderStepReset = 0, + BETTDecoderStepSaveDuration, + BETTDecoderStepCheckDuration, +} BETTDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_bett_decoder = { + .alloc = subghz_protocol_decoder_bett_alloc, + .free = subghz_protocol_decoder_bett_free, + + .feed = subghz_protocol_decoder_bett_feed, + .reset = subghz_protocol_decoder_bett_reset, + + .get_hash_data = subghz_protocol_decoder_bett_get_hash_data, + .serialize = subghz_protocol_decoder_bett_serialize, + .deserialize = subghz_protocol_decoder_bett_deserialize, + .get_string = subghz_protocol_decoder_bett_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_bett_encoder = { + .alloc = subghz_protocol_encoder_bett_alloc, + .free = subghz_protocol_encoder_bett_free, + + .deserialize = subghz_protocol_encoder_bett_deserialize, + .stop = subghz_protocol_encoder_bett_stop, + .yield = subghz_protocol_encoder_bett_yield, +}; + +const SubGhzProtocol subghz_protocol_bett = { + .name = SUBGHZ_PROTOCOL_BETT_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_bett_decoder, + .encoder = &subghz_protocol_bett_encoder, +}; + +void* subghz_protocol_encoder_bett_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderBETT* instance = malloc(sizeof(SubGhzProtocolEncoderBETT)); + + instance->base.protocol = &subghz_protocol_bett; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_bett_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderBETT* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderBETT instance + * @return true On success + */ +static bool subghz_protocol_encoder_bett_get_upload(SubGhzProtocolEncoderBETT* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_bett_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_bett_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_bett_const.te_short + + subghz_protocol_bett_const.te_long * 7); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_bett_const.te_long + subghz_protocol_bett_const.te_long * 7); + } + return true; +} + +bool subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderBETT* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_bett_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_bett_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_bett_stop(void* context) { + SubGhzProtocolEncoderBETT* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_bett_yield(void* context) { + SubGhzProtocolEncoderBETT* 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_bett_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderBETT* instance = malloc(sizeof(SubGhzProtocolDecoderBETT)); + instance->base.protocol = &subghz_protocol_bett; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_bett_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + free(instance); +} + +void subghz_protocol_decoder_bett_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + instance->decoder.parser_step = BETTDecoderStepReset; +} + +void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + + switch(instance->decoder.parser_step) { + case BETTDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) < + (subghz_protocol_bett_const.te_delta * 15))) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = BETTDecoderStepCheckDuration; + } + break; + case BETTDecoderStepSaveDuration: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) < + (subghz_protocol_bett_const.te_delta * 15)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_bett_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); + } else { + instance->decoder.parser_step = BETTDecoderStepReset; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + if((DURATION_DIFF(duration, subghz_protocol_bett_const.te_short) < + subghz_protocol_bett_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_bett_const.te_long) < + subghz_protocol_bett_const.te_delta * 3)) { + instance->decoder.parser_step = BETTDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = BETTDecoderStepReset; + } + } + } + break; + case BETTDecoderStepCheckDuration: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_bett_const.te_long) < + subghz_protocol_bett_const.te_delta * 3) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = BETTDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_bett_const.te_short) < + subghz_protocol_bett_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = BETTDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = BETTDecoderStepReset; + } + } else { + instance->decoder.parser_step = BETTDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_bett_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_bett_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderBETT* instance = context; + uint32_t data = (uint32_t)(instance->generic.data & 0x3FFFF); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%05lX\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN "\r\n" + " -: " DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + data, + SHOW_DIP_P(data, DIP_P), + SHOW_DIP_P(data, DIP_O), + SHOW_DIP_P(data, DIP_N)); +} diff --git a/applications/main/subghz/protocols/bett.h b/applications/main/subghz/protocols/bett.h new file mode 100644 index 000000000..c0ce0b7f4 --- /dev/null +++ b/applications/main/subghz/protocols/bett.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_BETT_NAME "BETT" + +typedef struct SubGhzProtocolDecoderBETT SubGhzProtocolDecoderBETT; +typedef struct SubGhzProtocolEncoderBETT SubGhzProtocolEncoderBETT; + +extern const SubGhzProtocolDecoder subghz_protocol_bett_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_bett_encoder; +extern const SubGhzProtocol subghz_protocol_bett; + +/** + * Allocate SubGhzProtocolEncoderBETT. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderBETT* pointer to a SubGhzProtocolEncoderBETT instance + */ +void* subghz_protocol_encoder_bett_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderBETT. + * @param context Pointer to a SubGhzProtocolEncoderBETT instance + */ +void subghz_protocol_encoder_bett_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderBETT instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderBETT instance + */ +void subghz_protocol_encoder_bett_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderBETT instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_bett_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderBETT. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderBETT* pointer to a SubGhzProtocolDecoderBETT instance + */ +void* subghz_protocol_decoder_bett_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderBETT. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + */ +void subghz_protocol_decoder_bett_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderBETT. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + */ +void subghz_protocol_decoder_bett_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderBETT. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_bett_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderBETT. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderBETT instance + * @param output Resulting text + */ +void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/bin_raw.c b/applications/main/subghz/protocols/bin_raw.c new file mode 100644 index 000000000..67e0467ee --- /dev/null +++ b/applications/main/subghz/protocols/bin_raw.c @@ -0,0 +1,1120 @@ +#include "bin_raw.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" +#include +#include +#include + +#define TAG "SubGhzProtocolBinRAW" + +//change very carefully, RAM ends at the most inopportune moment +#define BIN_RAW_BUF_RAW_SIZE 2048 +#define BIN_RAW_BUF_DATA_SIZE 512 + +#define BIN_RAW_THRESHOLD_RSSI -85.0f +#define BIN_RAW_DELTA_RSSI 7.0f +#define BIN_RAW_SEARCH_CLASSES 20 +#define BIN_RAW_TE_MIN_COUNT 40 +#define BIN_RAW_BUF_MIN_DATA_COUNT 128 +#define BIN_RAW_MAX_MARKUP_COUNT 20 + +//#define BIN_RAW_DEBUG + +#ifdef BIN_RAW_DEBUG +#define bin_raw_debug(...) FURI_LOG_RAW_D(__VA_ARGS__) +#define bin_raw_debug_tag(tag, ...) \ + FURI_LOG_RAW_D("\033[0;32m[" tag "]\033[0m "); \ + FURI_LOG_RAW_D(__VA_ARGS__) +#else +#define bin_raw_debug(...) +#define bin_raw_debug_tag(...) +#endif + +static const SubGhzBlockConst subghz_protocol_bin_raw_const = { + .te_short = 30, + .te_long = 65000, + .te_delta = 0, + .min_count_bit_for_found = 0, +}; + +typedef enum { + BinRAWDecoderStepReset = 0, + BinRAWDecoderStepWrite, + BinRAWDecoderStepBufFull, + BinRAWDecoderStepNoParse, +} BinRAWDecoderStep; + +typedef enum { + BinRAWTypeUnknown = 0, + BinRAWTypeNoGap, + BinRAWTypeGap, + BinRAWTypeGapRecurring, + BinRAWTypeGapRolling, + BinRAWTypeGapUnknown, +} BinRAWType; + +struct BinRAW_Markup { + uint16_t byte_bias; + uint16_t bit_count; +}; +typedef struct BinRAW_Markup BinRAW_Markup; + +struct SubGhzProtocolDecoderBinRAW { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + int32_t* data_raw; + uint8_t* data; + BinRAW_Markup data_markup[BIN_RAW_MAX_MARKUP_COUNT]; + size_t data_raw_ind; + uint32_t te; + float adaptive_threshold_rssi; +}; + +struct SubGhzProtocolEncoderBinRAW { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint8_t* data; + BinRAW_Markup data_markup[BIN_RAW_MAX_MARKUP_COUNT]; + uint32_t te; +}; + +const SubGhzProtocolDecoder subghz_protocol_bin_raw_decoder = { + .alloc = subghz_protocol_decoder_bin_raw_alloc, + .free = subghz_protocol_decoder_bin_raw_free, + + .feed = subghz_protocol_decoder_bin_raw_feed, + .reset = subghz_protocol_decoder_bin_raw_reset, + + .get_hash_data = subghz_protocol_decoder_bin_raw_get_hash_data, + .serialize = subghz_protocol_decoder_bin_raw_serialize, + .deserialize = subghz_protocol_decoder_bin_raw_deserialize, + .get_string = subghz_protocol_decoder_bin_raw_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_bin_raw_encoder = { + .alloc = subghz_protocol_encoder_bin_raw_alloc, + .free = subghz_protocol_encoder_bin_raw_free, + + .deserialize = subghz_protocol_encoder_bin_raw_deserialize, + .stop = subghz_protocol_encoder_bin_raw_stop, + .yield = subghz_protocol_encoder_bin_raw_yield, +}; + +const SubGhzProtocol subghz_protocol_bin_raw = { + .name = SUBGHZ_PROTOCOL_BIN_RAW_NAME, + .type = SubGhzProtocolTypeBinRAW, +#ifdef BIN_RAW_DEBUG + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, +#else + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_BinRAW | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, +#endif + .decoder = &subghz_protocol_bin_raw_decoder, + .encoder = &subghz_protocol_bin_raw_encoder, +}; + +static uint16_t subghz_protocol_bin_raw_get_full_byte(uint16_t bit_count) { + if(bit_count & 0x7) { + return (bit_count >> 3) + 1; + } else { + return (bit_count >> 3); + } +} + +void* subghz_protocol_encoder_bin_raw_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderBinRAW* instance = malloc(sizeof(SubGhzProtocolEncoderBinRAW)); + + instance->base.protocol = &subghz_protocol_bin_raw; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = BIN_RAW_BUF_DATA_SIZE * 5; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->data = malloc(instance->encoder.size_upload * sizeof(uint8_t)); + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_bin_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderBinRAW* instance = context; + free(instance->encoder.upload); + free(instance->data); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderBinRAW instance + * @return true On success + */ +static bool subghz_protocol_encoder_bin_raw_get_upload(SubGhzProtocolEncoderBinRAW* instance) { + furi_assert(instance); + + //we glue all the pieces of the package into 1 long sequence with left alignment, + //in the uploaded data we have right alignment. + + bin_raw_debug_tag(TAG, "Recovery of offset bits in sequences\r\n"); + uint16_t i = 0; + uint16_t ind = 0; + bin_raw_debug("\tind byte_bias\tbit_count\tbit_bias\r\n"); + while((i < BIN_RAW_MAX_MARKUP_COUNT) && (instance->data_markup[i].bit_count != 0)) { + uint8_t bit_bias = + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count) * 8 - + instance->data_markup[i].bit_count; + bin_raw_debug( + "\t%d\t%d\t%d :\t\t%d\r\n", + i, + instance->data_markup[i].byte_bias, + instance->data_markup[i].bit_count, + bit_bias); + for(uint16_t y = instance->data_markup[i].byte_bias * 8; + y < instance->data_markup[i].byte_bias * 8 + + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count) * 8 - + bit_bias; + y++) { + subghz_protocol_blocks_set_bit_array( + subghz_protocol_blocks_get_bit_array(instance->data, y + bit_bias), + instance->data, + ind++, + BIN_RAW_BUF_DATA_SIZE); + } + i++; + } + bin_raw_debug("\r\n"); +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag(TAG, "Restored Sequence left aligned\r\n"); + for(uint16_t y = 0; y < subghz_protocol_bin_raw_get_full_byte(ind); y++) { + bin_raw_debug("%02X ", instance->data[y]); + } + bin_raw_debug("\r\n\tbin_count_result= %d\r\n\r\n", ind); + + bin_raw_debug_tag( + TAG, "Maximum levels encoded in upload %zu\r\n", instance->encoder.size_upload); +#endif + instance->encoder.size_upload = subghz_protocol_blocks_get_upload_from_bit_array( + instance->data, + ind, + instance->encoder.upload, + instance->encoder.size_upload, + instance->te, + SubGhzProtocolBlockAlignBitLeft); + + bin_raw_debug_tag(TAG, "The result %zu is levels\r\n", instance->encoder.size_upload); + bin_raw_debug_tag(TAG, "Remaining free memory %zu\r\n", memmgr_get_free_heap()); + return true; +} + +bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderBinRAW* instance = context; + + bool res = false; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + + instance->generic.data_count_bit = (uint16_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + + temp_data = 0; + uint16_t ind = 0; + uint16_t byte_bias = 0; + uint16_t byte_count = 0; + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + while(flipper_format_read_uint32(flipper_format, "Bit_RAW", (uint32_t*)&temp_data, 1)) { + if(ind >= BIN_RAW_MAX_MARKUP_COUNT) { + FURI_LOG_E(TAG, "Markup overflow"); + break; + } + byte_count += subghz_protocol_bin_raw_get_full_byte(temp_data); + if(byte_count > BIN_RAW_BUF_DATA_SIZE) { + FURI_LOG_E(TAG, "Receive buffer overflow"); + break; + } + + instance->data_markup[ind].bit_count = temp_data; + instance->data_markup[ind].byte_bias = byte_bias; + byte_bias += subghz_protocol_bin_raw_get_full_byte(temp_data); + + if(!flipper_format_read_hex( + flipper_format, + "Data_RAW", + instance->data + instance->data_markup[ind].byte_bias, + subghz_protocol_bin_raw_get_full_byte(temp_data))) { + instance->data_markup[ind].bit_count = 0; + FURI_LOG_E(TAG, "Missing Data_RAW"); + break; + } + ind++; + } + +#ifdef BIN_RAW_DEBUG + uint16_t i = 0; + bin_raw_debug_tag(TAG, "Download data to encoder\r\n"); + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data"); + while((i < BIN_RAW_MAX_MARKUP_COUNT) && (instance->data_markup[i].bit_count != 0)) { + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + i, + instance->data_markup[i].byte_bias, + instance->data_markup[i].bit_count); + for(uint16_t y = instance->data_markup[i].byte_bias; + y < instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count); + y++) { + bin_raw_debug("%02X ", instance->data[y]); + } + i++; + } + bin_raw_debug("\r\n\r\n"); +#endif + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(0); + + return res; +} + +void subghz_protocol_encoder_bin_raw_stop(void* context) { + SubGhzProtocolEncoderBinRAW* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_bin_raw_yield(void* context) { + SubGhzProtocolEncoderBinRAW* 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_bin_raw_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderBinRAW* instance = malloc(sizeof(SubGhzProtocolDecoderBinRAW)); + instance->base.protocol = &subghz_protocol_bin_raw; + instance->generic.protocol_name = instance->base.protocol->name; + instance->data_raw_ind = 0; + instance->data_raw = malloc(BIN_RAW_BUF_RAW_SIZE * sizeof(int32_t)); + instance->data = malloc(BIN_RAW_BUF_RAW_SIZE * sizeof(uint8_t)); + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + instance->adaptive_threshold_rssi = BIN_RAW_THRESHOLD_RSSI; + return instance; +} + +void subghz_protocol_decoder_bin_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + free(instance->data_raw); + free(instance->data); + free(instance); +} + +void subghz_protocol_decoder_bin_raw_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; +#ifdef BIN_RAW_DEBUG + UNUSED(instance); +#else + instance->decoder.parser_step = BinRAWDecoderStepNoParse; + instance->data_raw_ind = 0; +#endif +} + +void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + + if(instance->decoder.parser_step == BinRAWDecoderStepWrite) { + if(instance->data_raw_ind == BIN_RAW_BUF_RAW_SIZE) { + instance->decoder.parser_step = BinRAWDecoderStepBufFull; + } else { + instance->data_raw[instance->data_raw_ind++] = (level ? duration : -duration); + } + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzProtocolDecoderBinRAW* instance + */ +static bool + subghz_protocol_bin_raw_check_remote_controller(SubGhzProtocolDecoderBinRAW* instance) { + struct { + float data; + uint16_t count; + } classes[BIN_RAW_SEARCH_CLASSES]; + + size_t ind = 0; + + memset(classes, 0x00, sizeof(classes)); + + uint16_t data_markup_ind = 0; + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + + if(instance->data_raw_ind < 512) { + ind = + instance->data_raw_ind - + 100; //there is usually garbage at the end of the record, we exclude it from the classification + } else { + ind = 512; + } + + //sort the durations to find the shortest correlated interval + for(size_t i = 0; i < ind; i++) { + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + if(classes[k].count == 0) { + classes[k].data = (float)(abs(instance->data_raw[i])); + classes[k].count++; + break; + } else if( + DURATION_DIFF((float)(abs(instance->data_raw[i])), (classes[k].data)) < + (classes[k].data / 4)) { //if the test value does not differ by more than 25% + classes[k].data += ((float)(abs(instance->data_raw[i])) - classes[k].data) * + 0.05f; //running average k=0.05 + classes[k].count++; + break; + } + } + } + + // if(classes[BIN_RAW_SEARCH_CLASSES - 1].count != 0) { + // //filling the classifier, it means that they received an unclean signal + // return false; + // } + + //looking for the minimum te with an occurrence greater than BIN_RAW_TE_MIN_COUNT + instance->te = subghz_protocol_bin_raw_const.te_long * 2; + + bool te_ok = false; + uint16_t gap_ind = 0; + uint16_t gap_delta = 0; + uint32_t gap = 0; + int data_temp = 0; + BinRAWType bin_raw_type = BinRAWTypeUnknown; + + //sort by number of occurrences + bool swap = true; + while(swap) { + swap = false; + for(size_t i = 1; i < BIN_RAW_SEARCH_CLASSES; i++) { + if(classes[i].count > classes[i - 1].count) { + uint32_t data = classes[i - 1].data; + uint32_t count = classes[i - 1].count; + classes[i - 1].data = classes[i].data; + classes[i - 1].count = classes[i].count; + classes[i].data = data; + classes[i].count = count; + swap = true; + } + } + } +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag(TAG, "Sorted durations\r\n"); + bin_raw_debug("\t\tind\tcount\tus\r\n"); + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + bin_raw_debug("\t\t%zu\t%u\t%lu\r\n", k, classes[k].count, (uint32_t)classes[k].data); + } + bin_raw_debug("\r\n"); +#endif + if((classes[0].count > BIN_RAW_TE_MIN_COUNT) && (classes[1].count == 0)) { + //adopted only the preamble + instance->te = (uint32_t)classes[0].data; + te_ok = true; + gap = 0; //gap no + } else { + //take the 2 most common durations + //check that there are enough + if((classes[0].count < BIN_RAW_TE_MIN_COUNT) || + (classes[1].count < (BIN_RAW_TE_MIN_COUNT >> 1))) + return false; + //arrange the first 2 date values in ascending order + if(classes[0].data > classes[1].data) { + uint32_t data = classes[1].data; + classes[0].data = classes[1].data; + classes[1].data = data; + } + + //determine the value to be corrected + for(uint8_t k = 1; k < 5; k++) { + float delta = (classes[1].data / (classes[0].data / k)); + bin_raw_debug_tag(TAG, "K_div= %f\r\n", (double)(delta)); + delta -= (uint32_t)delta; + + if((delta < 0.20f) || (delta > 0.80f)) { + instance->te = (uint32_t)classes[0].data / k; + bin_raw_debug_tag(TAG, "K= %d\r\n", k); + te_ok = true; //found a correlated duration + break; + } + } + if(!te_ok) { + //did not find the minimum TE satisfying the condition + return false; + } + bin_raw_debug_tag(TAG, "TE= %lu\r\n\r\n", instance->te); + + //looking for a gap + for(size_t k = 2; k < BIN_RAW_SEARCH_CLASSES; k++) { + if((classes[k].count > 2) && (classes[k].data > gap)) { + gap = (uint32_t)classes[k].data; + gap_delta = gap / 5; //calculate 20% deviation from ideal value + } + } + + if((gap / instance->te) < + 10) { //make an assumption, the longest gap should be more than 10 TE + gap = 0; //check that our signal has a gap greater than 10*TE + bin_raw_type = BinRAWTypeNoGap; + } else { + bin_raw_type = BinRAWTypeGap; + //looking for the last occurrence of gap + ind = instance->data_raw_ind - 1; + while((ind > 0) && (DURATION_DIFF(abs(instance->data_raw[ind]), gap) > gap_delta)) { + ind--; + } + gap_ind = ind; + } + } + + //if we consider that there is a gap, then we divide the signal with respect to this gap + //processing input data from the end + if(bin_raw_type == BinRAWTypeGap) { + bin_raw_debug_tag(TAG, "Tinted sequence\r\n"); + ind = (BIN_RAW_BUF_DATA_SIZE * 8); + uint16_t bit_count = 0; + do { + gap_ind--; + data_temp = (int)(round((float)(instance->data_raw[gap_ind]) / instance->te)); + bin_raw_debug("%d ", data_temp); + if(data_temp == 0) bit_count++; //there is noise in the package + for(size_t i = 0; i < abs(data_temp); i++) { + bit_count++; + if(ind) { + ind--; + } else { + break; + } + if(data_temp > 0) { + subghz_protocol_blocks_set_bit_array( + true, instance->data, ind, BIN_RAW_BUF_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + false, instance->data, ind, BIN_RAW_BUF_DATA_SIZE); + } + } + //split into full bytes if gap is caught + if(DURATION_DIFF(abs(instance->data_raw[gap_ind]), gap) < gap_delta) { + instance->data_markup[data_markup_ind].byte_bias = ind >> 3; + instance->data_markup[data_markup_ind++].bit_count = bit_count; + bit_count = 0; + + if(data_markup_ind == BIN_RAW_MAX_MARKUP_COUNT) break; + ind &= 0xFFFFFFF8; //jump to the pre whole byte + } + } while(gap_ind != 0); + if((data_markup_ind != BIN_RAW_MAX_MARKUP_COUNT) && (ind != 0)) { + instance->data_markup[data_markup_ind].byte_bias = ind >> 3; + instance->data_markup[data_markup_ind++].bit_count = bit_count; + } + + bin_raw_debug("\r\n\t count bit= %zu\r\n\r\n", (BIN_RAW_BUF_DATA_SIZE * 8) - ind); + + //reset the classifier and classify the received data + memset(classes, 0x00, sizeof(classes)); + + bin_raw_debug_tag(TAG, "Sort the found pieces by the number of bits in them\r\n"); + for(size_t i = 0; i < data_markup_ind; i++) { + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + if(classes[k].count == 0) { + classes[k].data = instance->data_markup[i].bit_count; + classes[k].count++; + break; + } else if(instance->data_markup[i].bit_count == (uint16_t)classes[k].data) { + classes[k].count++; + break; + } + } + } + +#ifdef BIN_RAW_DEBUG + bin_raw_debug("\t\tind\tcount\tus\r\n"); + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + bin_raw_debug("\t\t%zu\t%u\t%lu\r\n", k, classes[k].count, (uint32_t)classes[k].data); + } + bin_raw_debug("\r\n"); +#endif + + //choose the value with the maximum repetition + data_temp = 0; + for(size_t i = 0; i < BIN_RAW_SEARCH_CLASSES; i++) { + if((classes[i].count > 1) && (data_temp < classes[i].count)) + data_temp = (int)classes[i].data; + } + + //if(data_markup_ind == 0) return false; + +#ifdef BIN_RAW_DEBUG + //output in reverse order + bin_raw_debug_tag(TAG, "Found sequences\r\n"); + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data\r\n"); + uint16_t data_markup_ind_temp = data_markup_ind; + if(data_markup_ind) { + data_markup_ind_temp--; + for(size_t i = (ind / 8); i < BIN_RAW_BUF_DATA_SIZE; i++) { + if(instance->data_markup[data_markup_ind_temp].byte_bias == i) { + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + data_markup_ind_temp, + instance->data_markup[data_markup_ind_temp].byte_bias, + instance->data_markup[data_markup_ind_temp].bit_count); + if(data_markup_ind_temp != 0) data_markup_ind_temp--; + } + bin_raw_debug("%02X ", instance->data[i]); + } + bin_raw_debug("\r\n\r\n"); + } + //compare data in chunks with the same number of bits + bin_raw_debug_tag(TAG, "Analyze sequences of long %d bit\r\n\r\n", data_temp); +#endif + + //if(data_temp == 0) data_temp = (int)classes[0].data; + + if(data_temp != 0) { + //check that data in transmission is repeated every packet + for(uint16_t i = 0; i < data_markup_ind - 1; i++) { + if((instance->data_markup[i].bit_count == data_temp) && + (instance->data_markup[i + 1].bit_count == data_temp)) { + //if the number of bits in adjacent parcels is the same, compare the data + bin_raw_debug_tag( + TAG, + "Comparison of neighboring sequences ind_1=%d ind_2=%d %02X=%02X .... %02X=%02X\r\n", + i, + i + 1, + instance->data[instance->data_markup[i].byte_bias], + instance->data[instance->data_markup[i + 1].byte_bias], + instance->data + [instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i].bit_count) - + 1], + instance->data + [instance->data_markup[i + 1].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i + 1].bit_count) - + 1]); + + uint16_t byte_count = + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count); + if(memcmp( + instance->data + instance->data_markup[i].byte_bias, + instance->data + instance->data_markup[i + 1].byte_bias, + byte_count - 1) == 0) { + bin_raw_debug_tag( + TAG, "Match found bin_raw_type=BinRAWTypeGapRecurring\r\n\r\n"); + + //place in 1 element the offset to valid data + instance->data_markup[0].bit_count = instance->data_markup[i].bit_count; + instance->data_markup[0].byte_bias = instance->data_markup[i].byte_bias; + //markup end sign + instance->data_markup[1].bit_count = 0; + instance->data_markup[1].byte_bias = 0; + + bin_raw_type = BinRAWTypeGapRecurring; + i = data_markup_ind; + break; + } + } + } + } + + if(bin_raw_type == BinRAWTypeGap) { + // check that retry occurs every n packets + for(uint16_t i = 0; i < data_markup_ind - 2; i++) { + uint16_t byte_count = + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count); + for(uint16_t y = i + 1; y < data_markup_ind - 1; y++) { + bin_raw_debug_tag( + TAG, + "Comparison every N sequences ind_1=%d ind_2=%d %02X=%02X .... %02X=%02X\r\n", + i, + y, + instance->data[instance->data_markup[i].byte_bias], + instance->data[instance->data_markup[y].byte_bias], + instance->data + [instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i].bit_count) - + 1], + instance->data + [instance->data_markup[y].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[y].bit_count) - + 1]); + + if(byte_count == + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[y].bit_count)) { //if the length in bytes matches + + if((memcmp( + instance->data + instance->data_markup[i].byte_bias, + instance->data + instance->data_markup[y].byte_bias, + byte_count - 1) == 0) && + (memcmp( + instance->data + instance->data_markup[i + 1].byte_bias, + instance->data + instance->data_markup[y + 1].byte_bias, + byte_count - 1) == 0)) { + uint8_t index = 0; +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag( + TAG, "Match found bin_raw_type=BinRAWTypeGapRolling\r\n\r\n"); + //output in reverse order + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data\r\n"); + index = y - 1; + for(size_t z = instance->data_markup[y].byte_bias + byte_count; + z < instance->data_markup[i].byte_bias + byte_count; + z++) { + if(instance->data_markup[index].byte_bias == z) { + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + index, + instance->data_markup[index].byte_bias, + instance->data_markup[index].bit_count); + if(index != 0) index--; + } + bin_raw_debug("%02X ", instance->data[z]); + } + + bin_raw_debug("\r\n\r\n"); +#endif + //todo can be optimized + BinRAW_Markup markup_temp[BIN_RAW_MAX_MARKUP_COUNT]; + memcpy( + markup_temp, + instance->data_markup, + BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + memset( + instance->data_markup, + 0x00, + BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + + for(index = i; index < y; index++) { + instance->data_markup[index - i].bit_count = + markup_temp[y - index - 1].bit_count; + instance->data_markup[index - i].byte_bias = + markup_temp[y - index - 1].byte_bias; + } + + bin_raw_type = BinRAWTypeGapRolling; + i = data_markup_ind; + break; + } + } + } + } + } + //todo can be optimized + if(bin_raw_type == BinRAWTypeGap) { + if(data_temp != 0) { //there are sequences with the same number of bits + + BinRAW_Markup markup_temp[BIN_RAW_MAX_MARKUP_COUNT]; + memcpy( + markup_temp, + instance->data_markup, + BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + memset( + instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + uint16_t byte_count = subghz_protocol_bin_raw_get_full_byte(data_temp); + uint16_t index = 0; + uint16_t it = BIN_RAW_MAX_MARKUP_COUNT; + do { + it--; + if(subghz_protocol_bin_raw_get_full_byte(markup_temp[it].bit_count) == + byte_count) { + instance->data_markup[index].bit_count = markup_temp[it].bit_count; + instance->data_markup[index].byte_bias = markup_temp[it].byte_bias; + index++; + bin_raw_type = BinRAWTypeGapUnknown; + } + } while(it != 0); + } + } + + if(bin_raw_type != BinRAWTypeGap) + return true; + else + return false; + + } else { + // if bin_raw_type == BinRAWTypeGap + bin_raw_debug_tag(TAG, "Sequence analysis without gap\r\n"); + ind = 0; + for(size_t i = 0; i < instance->data_raw_ind; i++) { + int data_temp = (int)(round((float)(instance->data_raw[i]) / instance->te)); + if(data_temp == 0) break; //found an interval 2 times shorter than TE, this is noise + bin_raw_debug("%d ", data_temp); + + for(size_t k = 0; k < abs(data_temp); k++) { + if(data_temp > 0) { + subghz_protocol_blocks_set_bit_array( + true, instance->data, ind++, BIN_RAW_BUF_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + false, instance->data, ind++, BIN_RAW_BUF_DATA_SIZE); + } + if(ind == BIN_RAW_BUF_DATA_SIZE * 8) { + i = instance->data_raw_ind; + break; + } + } + } + + if(ind != 0) { + bin_raw_type = BinRAWTypeNoGap; + //right alignment + uint8_t bit_bias = (subghz_protocol_bin_raw_get_full_byte(ind) << 3) - ind; +#ifdef BIN_RAW_DEBUG + bin_raw_debug( + "\r\n\t count bit= %zu\tcount full byte= %d\tbias bit= %d\r\n\r\n", + ind, + subghz_protocol_bin_raw_get_full_byte(ind), + bit_bias); + + for(size_t i = 0; i < subghz_protocol_bin_raw_get_full_byte(ind); i++) { + bin_raw_debug("%02X ", instance->data[i]); + } + bin_raw_debug("\r\n\r\n"); +#endif + //checking that the received sequence contains useful data + bool data_check = false; + for(size_t i = 0; i < subghz_protocol_bin_raw_get_full_byte(ind); i++) { + if(instance->data[i] != 0) { + data_check = true; + break; + } + } + + if(data_check) { + for(size_t i = subghz_protocol_bin_raw_get_full_byte(ind) - 1; i > 0; i--) { + instance->data[i] = (instance->data[i - 1] << (8 - bit_bias)) | + (instance->data[i] >> bit_bias); + } + instance->data[0] = (instance->data[0] >> bit_bias); + +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag(TAG, "Data right alignment\r\n"); + for(size_t i = 0; i < subghz_protocol_bin_raw_get_full_byte(ind); i++) { + bin_raw_debug("%02X ", instance->data[i]); + } + bin_raw_debug("\r\n\r\n"); +#endif + instance->data_markup[0].bit_count = ind; + instance->data_markup[0].byte_bias = 0; + + return true; + } else { + return false; + } + } else { + return false; + } + } + return false; +} + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi) { + furi_assert(instance); + switch(instance->decoder.parser_step) { + case BinRAWDecoderStepReset: + + bin_raw_debug("%ld %ld :", (int32_t)rssi, (int32_t)instance->adaptive_threshold_rssi); + if(rssi > (instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI)) { + instance->data_raw_ind = 0; + memset(instance->data_raw, 0x00, BIN_RAW_BUF_RAW_SIZE * sizeof(int32_t)); + memset(instance->data, 0x00, BIN_RAW_BUF_RAW_SIZE * sizeof(uint8_t)); + instance->decoder.parser_step = BinRAWDecoderStepWrite; + bin_raw_debug_tag(TAG, "RSSI\r\n"); + } else { + //adaptive noise level adjustment + instance->adaptive_threshold_rssi += (rssi - instance->adaptive_threshold_rssi) * 0.2f; + } + break; + + case BinRAWDecoderStepBufFull: + case BinRAWDecoderStepWrite: +#ifdef BIN_RAW_DEBUG + if(rssi > (instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI)) { + bin_raw_debug("\033[0;32m%ld \033[0m ", (int32_t)rssi); + } else { + bin_raw_debug("%ld ", (int32_t)rssi); + } +#endif + if(rssi < instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI) { +#ifdef BIN_RAW_DEBUG + bin_raw_debug("\r\n\r\n"); + bin_raw_debug_tag(TAG, "Data for analysis, positive high, negative low, us\r\n"); + for(size_t i = 0; i < instance->data_raw_ind; i++) { + bin_raw_debug("%ld ", instance->data_raw[i]); + } + bin_raw_debug("\r\n\t count data= %zu\r\n\r\n", instance->data_raw_ind); +#endif + instance->decoder.parser_step = BinRAWDecoderStepReset; + instance->generic.data_count_bit = 0; + if(instance->data_raw_ind >= BIN_RAW_BUF_MIN_DATA_COUNT) { + if(subghz_protocol_bin_raw_check_remote_controller(instance)) { + bin_raw_debug_tag(TAG, "Sequence found\r\n"); + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data"); + uint16_t i = 0; + while((i < BIN_RAW_MAX_MARKUP_COUNT) && + (instance->data_markup[i].bit_count != 0)) { + instance->generic.data_count_bit += instance->data_markup[i].bit_count; +#ifdef BIN_RAW_DEBUG + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + i, + instance->data_markup[i].byte_bias, + instance->data_markup[i].bit_count); + for(uint16_t y = instance->data_markup[i].byte_bias; + y < instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i].bit_count); + y++) { + bin_raw_debug("%02X ", instance->data[y]); + } +#endif + i++; + } + bin_raw_debug("\r\n"); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + } + break; + + default: + //if instance->decoder.parser_step == BinRAWDecoderStepNoParse or others, restore the initial state + if(rssi < instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI) { + instance->decoder.parser_step = BinRAWDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + return subghz_protocol_blocks_add_bytes( + instance->data + instance->data_markup[0].byte_bias, + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[0].bit_count)); +} + +bool subghz_protocol_decoder_bin_raw_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr( + flipper_format, "Protocol", instance->generic.protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + uint32_t temp = instance->generic.data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + break; + } + + uint16_t i = 0; + while((i < BIN_RAW_MAX_MARKUP_COUNT) && (instance->data_markup[i].bit_count != 0)) { + temp = instance->data_markup[i].bit_count; + if(!flipper_format_write_uint32(flipper_format, "Bit_RAW", &temp, 1)) { + FURI_LOG_E(TAG, "Bit_RAW"); + break; + } + if(!flipper_format_write_hex( + flipper_format, + "Data_RAW", + instance->data + instance->data_markup[i].byte_bias, + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count))) { + FURI_LOG_E(TAG, "Unable to add Data_RAW"); + break; + } + i++; + } + + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + + bool res = false; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + + instance->generic.data_count_bit = (uint16_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + + temp_data = 0; + uint16_t ind = 0; + uint16_t byte_bias = 0; + uint16_t byte_count = 0; + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + while(flipper_format_read_uint32(flipper_format, "Bit_RAW", (uint32_t*)&temp_data, 1)) { + if(ind >= BIN_RAW_MAX_MARKUP_COUNT) { + FURI_LOG_E(TAG, "Markup overflow"); + break; + } + byte_count += subghz_protocol_bin_raw_get_full_byte(temp_data); + if(byte_count > BIN_RAW_BUF_DATA_SIZE) { + FURI_LOG_E(TAG, "Receive buffer overflow"); + break; + } + + instance->data_markup[ind].bit_count = temp_data; + instance->data_markup[ind].byte_bias = byte_bias; + byte_bias += subghz_protocol_bin_raw_get_full_byte(temp_data); + + if(!flipper_format_read_hex( + flipper_format, + "Data_RAW", + instance->data + instance->data_markup[ind].byte_bias, + subghz_protocol_bin_raw_get_full_byte(temp_data))) { + instance->data_markup[ind].bit_count = 0; + FURI_LOG_E(TAG, "Missing Data_RAW"); + break; + } + ind++; + } + + res = true; + } while(0); + + return res; +} + +void subghz_protocol_decoder_bin_raw_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:", + instance->generic.protocol_name, + instance->generic.data_count_bit); + + uint16_t byte_count = subghz_protocol_bin_raw_get_full_byte(instance->generic.data_count_bit); + for(size_t i = 0; (byte_count < 36 ? i < byte_count : i < 36); i++) { + furi_string_cat_printf(output, "%02X", instance->data[i]); + } + + furi_string_cat_printf(output, "\r\nTe:%luus\r\n", instance->te); +} diff --git a/applications/main/subghz/protocols/bin_raw.h b/applications/main/subghz/protocols/bin_raw.h new file mode 100644 index 000000000..c63f86ce6 --- /dev/null +++ b/applications/main/subghz/protocols/bin_raw.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_BIN_RAW_NAME "BinRAW" + +typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; +typedef struct SubGhzProtocolEncoderBinRAW SubGhzProtocolEncoderBinRAW; + +extern const SubGhzProtocolDecoder subghz_protocol_bin_raw_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_bin_raw_encoder; +extern const SubGhzProtocol subghz_protocol_bin_raw; + +/** + * Allocate SubGhzProtocolEncoderBinRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderBinRAW* pointer to a SubGhzProtocolEncoderBinRAW instance + */ +void* subghz_protocol_encoder_bin_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderBinRAW. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + */ +void subghz_protocol_encoder_bin_raw_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + */ +void subghz_protocol_encoder_bin_raw_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_bin_raw_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderBinRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderBinRAW* pointer to a SubGhzProtocolDecoderBinRAW instance + */ +void* subghz_protocol_decoder_bin_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + */ +void subghz_protocol_decoder_bin_raw_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + */ +void subghz_protocol_decoder_bin_raw_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context); + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi); + +/** + * Serialize data SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_bin_raw_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param output Resulting text + */ +void subghz_protocol_decoder_bin_raw_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/came.c b/applications/main/subghz/protocols/came.c new file mode 100644 index 000000000..bed26d7d2 --- /dev/null +++ b/applications/main/subghz/protocols/came.c @@ -0,0 +1,347 @@ +#include "came.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define TAG "SubGhzProtocolCAME" +#define CAME_24_COUNT_BIT 24 +#define PRASTEL_COUNT_BIT 25 +#define PRASTEL_NAME "Prastel" +#define AIRFORCE_COUNT_BIT 18 +#define AIRFORCE_NAME "Airforce" + +static const SubGhzBlockConst subghz_protocol_came_const = { + .te_short = 320, + .te_long = 640, + .te_delta = 150, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderCame { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderCame { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + CameDecoderStepReset = 0, + CameDecoderStepFoundStartBit, + CameDecoderStepSaveDuration, + CameDecoderStepCheckDuration, +} CameDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_came_decoder = { + .alloc = subghz_protocol_decoder_came_alloc, + .free = subghz_protocol_decoder_came_free, + + .feed = subghz_protocol_decoder_came_feed, + .reset = subghz_protocol_decoder_came_reset, + + .get_hash_data = subghz_protocol_decoder_came_get_hash_data, + .serialize = subghz_protocol_decoder_came_serialize, + .deserialize = subghz_protocol_decoder_came_deserialize, + .get_string = subghz_protocol_decoder_came_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_came_encoder = { + .alloc = subghz_protocol_encoder_came_alloc, + .free = subghz_protocol_encoder_came_free, + + .deserialize = subghz_protocol_encoder_came_deserialize, + .stop = subghz_protocol_encoder_came_stop, + .yield = subghz_protocol_encoder_came_yield, +}; + +const SubGhzProtocol subghz_protocol_came = { + .name = SUBGHZ_PROTOCOL_CAME_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_came_decoder, + .encoder = &subghz_protocol_came_encoder, +}; + +void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderCame* instance = malloc(sizeof(SubGhzProtocolEncoderCame)); + + instance->base.protocol = &subghz_protocol_came; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_came_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderCame* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderCame instance + * @return true On success + */ +static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = level_duration_make( + false, + (((instance->generic.data_count_bit == CAME_24_COUNT_BIT) || + (instance->generic.data_count_bit == + subghz_protocol_came_const.min_count_bit_for_found)) ? + (uint32_t)subghz_protocol_came_const.te_short * 76 : + (uint32_t)subghz_protocol_came_const.te_short * 39)); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderCame* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if((instance->generic.data_count_bit > PRASTEL_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_came_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_came_stop(void* context) { + SubGhzProtocolEncoderCame* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_came_yield(void* context) { + SubGhzProtocolEncoderCame* 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_came_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderCame* instance = malloc(sizeof(SubGhzProtocolDecoderCame)); + instance->base.protocol = &subghz_protocol_came; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_came_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + free(instance); +} + +void subghz_protocol_decoder_came_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + instance->decoder.parser_step = CameDecoderStepReset; +} + +void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + 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)) { + //Found header CAME + instance->decoder.parser_step = CameDecoderStepFoundStartBit; + } + break; + case CameDecoderStepFoundStartBit: + if(!level) { + break; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_const.te_short) < + subghz_protocol_came_const.te_delta) { + //Found start bit CAME + instance->decoder.parser_step = CameDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = CameDecoderStepReset; + } + break; + case CameDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_came_const.te_short * 4)) { + instance->decoder.parser_step = CameDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_came_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + 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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = CameDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = CameDecoderStepReset; + } + break; + case CameDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_came_const.te_short) < + subghz_protocol_came_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_came_const.te_long) < + subghz_protocol_came_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = CameDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_came_const.te_long) < + subghz_protocol_came_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_came_const.te_short) < + subghz_protocol_came_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = CameDecoderStepSaveDuration; + } else + instance->decoder.parser_step = CameDecoderStepReset; + } else { + instance->decoder.parser_step = CameDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_came_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_came_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if((instance->generic.data_count_bit > PRASTEL_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_came_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n", + (instance->generic.data_count_bit == PRASTEL_COUNT_BIT ? + PRASTEL_NAME : + (instance->generic.data_count_bit == AIRFORCE_COUNT_BIT ? + AIRFORCE_NAME : + instance->generic.protocol_name)), + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); +} diff --git a/applications/main/subghz/protocols/came.h b/applications/main/subghz/protocols/came.h new file mode 100644 index 000000000..253c93aae --- /dev/null +++ b/applications/main/subghz/protocols/came.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CAME_NAME "CAME" + +typedef struct SubGhzProtocolDecoderCame SubGhzProtocolDecoderCame; +typedef struct SubGhzProtocolEncoderCame SubGhzProtocolEncoderCame; + +extern const SubGhzProtocolDecoder subghz_protocol_came_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_came_encoder; +extern const SubGhzProtocol subghz_protocol_came; + +/** + * Allocate SubGhzProtocolEncoderCame. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderCame* pointer to a SubGhzProtocolEncoderCame instance + */ +void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderCame. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + */ +void subghz_protocol_encoder_came_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + */ +void subghz_protocol_encoder_came_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_came_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderCame. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderCame* pointer to a SubGhzProtocolDecoderCame instance + */ +void* subghz_protocol_decoder_came_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + */ +void subghz_protocol_decoder_came_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + */ +void subghz_protocol_decoder_came_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_came_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_came_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param output Resulting text + */ +void subghz_protocol_decoder_came_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/came_atomo.c b/applications/main/subghz/protocols/came_atomo.c new file mode 100644 index 000000000..d12e5976c --- /dev/null +++ b/applications/main/subghz/protocols/came_atomo.c @@ -0,0 +1,598 @@ +#include "came_atomo.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoCameAtomo" + +static const SubGhzBlockConst subghz_protocol_came_atomo_const = { + .te_short = 600, + .te_long = 1200, + .te_delta = 250, + .min_count_bit_for_found = 62, +}; + +struct SubGhzProtocolDecoderCameAtomo { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderCameAtomo { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + CameAtomoDecoderStepReset = 0, + CameAtomoDecoderStepDecoderData, +} CameAtomoDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder = { + .alloc = subghz_protocol_decoder_came_atomo_alloc, + .free = subghz_protocol_decoder_came_atomo_free, + + .feed = subghz_protocol_decoder_came_atomo_feed, + .reset = subghz_protocol_decoder_came_atomo_reset, + + .get_hash_data = subghz_protocol_decoder_came_atomo_get_hash_data, + .serialize = subghz_protocol_decoder_came_atomo_serialize, + .deserialize = subghz_protocol_decoder_came_atomo_deserialize, + .get_string = subghz_protocol_decoder_came_atomo_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder = { + .alloc = subghz_protocol_encoder_came_atomo_alloc, + .free = subghz_protocol_encoder_came_atomo_free, + + .deserialize = subghz_protocol_encoder_came_atomo_deserialize, + .stop = subghz_protocol_encoder_came_atomo_stop, + .yield = subghz_protocol_encoder_came_atomo_yield, +}; + +const SubGhzProtocol subghz_protocol_came_atomo = { + .name = SUBGHZ_PROTOCOL_CAME_ATOMO_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_came_atomo_decoder, + .encoder = &subghz_protocol_came_atomo_encoder, +}; + +static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* instance); + +void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolEncoderCameAtomo)); + + instance->base.protocol = &subghz_protocol_came_atomo; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 900; //actual size 766+ + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_came_atomo_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderCameAtomo* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_came_atomo_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_came_atomo_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_came_atomo_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_came_atomo_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_came_atomo_const.te_short; + data.level = true; + break; + + default: + FURI_LOG_E(TAG, "SubGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderCameAtomo instance + */ +static void + subghz_protocol_encoder_came_atomo_get_upload(SubGhzProtocolEncoderCameAtomo* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + uint8_t pack[8] = {}; + + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + + //Send header + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long * 15); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_long * 60); + + for(uint8_t i = 0; i < 8; i++) { + pack[0] = (instance->generic.data_2 >> 56); + pack[1] = (instance->generic.cnt >> 8); + pack[2] = (instance->generic.cnt & 0xFF); + pack[3] = ((instance->generic.data_2 >> 32) & 0xFF); + pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); + pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); + pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); + pack[7] = (instance->generic.data_2 & 0xFF); + + if(pack[0] == 0x7F) { + pack[0] = 0; + } else { + pack[0] += (i + 1); + } + + atomo_encrypt(pack); + uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; + uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; + instance->generic.data = (uint64_t)hi << 32 | lo; + + instance->generic.data ^= 0xFFFFFFFFFFFFFFFF; + instance->generic.data >>= 4; + instance->generic.data &= 0xFFFFFFFFFFFFFFF; + + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_short); + + for(uint8_t i = (instance->generic.data_count_bit - 2); i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, !bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_came_atomo_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_came_atomo_add_duration_to_upload(result); + } + instance->encoder.upload[index] = + subghz_protocol_encoder_came_atomo_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + //Send pause + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_delta * 272); + } + instance->encoder.size_upload = index; + instance->generic.cnt_2++; + pack[0] = (instance->generic.cnt_2); + pack[1] = (instance->generic.cnt >> 8); + pack[2] = (instance->generic.cnt & 0xFF); + pack[3] = ((instance->generic.data_2 >> 32) & 0xFF); + pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); + pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); + pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); + pack[7] = (instance->generic.data_2 & 0xFF); + + atomo_encrypt(pack); + uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; + uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; + instance->generic.data = (uint64_t)hi << 32 | lo; + + instance->generic.data ^= 0xFFFFFFFFFFFFFFFF; + instance->generic.data >>= 4; + instance->generic.data &= 0xFFFFFFFFFFFFFFF; +} + +bool subghz_protocol_encoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderCameAtomo* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_came_atomo_remote_controller(&instance->generic); + subghz_protocol_encoder_came_atomo_get_upload(instance); + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_came_atomo_stop(void* context) { + SubGhzProtocolEncoderCameAtomo* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_came_atomo_yield(void* context) { + SubGhzProtocolEncoderCameAtomo* 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_came_atomo_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolDecoderCameAtomo)); + instance->base.protocol = &subghz_protocol_came_atomo; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_came_atomo_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + free(instance); +} + +void subghz_protocol_decoder_came_atomo_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + instance->decoder.parser_step = CameAtomoDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case CameAtomoDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) < + subghz_protocol_came_atomo_const.te_delta * 40)) { + //Found header CAME + instance->decoder.parser_step = CameAtomoDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } + break; + case CameAtomoDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_short) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= ((uint32_t)subghz_protocol_came_atomo_const.te_long * 2 + + subghz_protocol_came_atomo_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_came_atomo_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 = 1; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = CameAtomoDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_short) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = CameAtomoDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file + */ +static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* instance) { + /* + * ***SkorP ver.*** + * 0x1fafef3ed0f7d9ef + * 0x185fcc1531ee86e7 + * 0x184fa96912c567ff + * 0x187f8a42f3dc38f7 + * 0x186f63915492a5cd + * 0x181f40bab58bfac5 + * 0x180f25c696a01bdd + * 0x183f06ed77b944d5 + * 0x182ef661d83d21a9 + * 0x18ded54a39247ea1 + * 0x18ceb0361a0f9fb9 + * 0x18fe931dfb16c0b1 + * 0x18ee7ace5c585d8b + * ........ + * transmission consists of 99 parcels with increasing counter while holding down the button + * with each new press, the counter in the encrypted part increases + * + * 0x1FAFF13ED0F7D9EF + * 0x1FAFF11ED0F7D9EF + * 0x1FAFF10ED0F7D9EF + * 0x1FAFF0FED0F7D9EF + * 0x1FAFF0EED0F7D9EF + * 0x1FAFF0DED0F7D9EF + * 0x1FAFF0CED0F7D9EF + * 0x1FAFF0BED0F7D9EF + * 0x1FAFF0AED0F7D9EF + * + * where 0x1FAF - parcel counter, 0хF0A - button press counter, + * 0xED0F7D9E - serial number, 0хF - key + * 0x1FAF parcel counter - 1 in the parcel queue ^ 0x185F = 0x07F0 + * 0x185f ^ 0x185F = 0x0000 + * 0x184f ^ 0x185F = 0x0010 + * 0x187f ^ 0x185F = 0x0020 + * ..... + * 0x182e ^ 0x185F = 0x0071 + * 0x18de ^ 0x185F = 0x0081 + * ..... + * 0x1e43 ^ 0x185F = 0x061C + * where the last nibble is incremented every 8 samples + * + * Decode + * + * 0x1cf6931dfb16c0b1 => 0x1cf6 + * 0x1cf6 ^ 0x185F = 0x04A9 + * 0x04A9 => 0x04A = 74 (dec) + * 74+1 % 32(atomo_magic_xor) = 11 + * GET atomo_magic_xor[11] = 0xXXXXXXXXXXXXXXXX + * 0x931dfb16c0b1 ^ 0xXXXXXXXXXXXXXXXX = 0xEF3ED0F7D9EF + * 0xEF3 ED0F7D9E F => 0xEF3 - CNT, 0xED0F7D9E - SN, 0xF - key + * + * ***Eng1n33r ver. (actual)*** + * 0x1FF08D9924984115 - received data + * 0x00F7266DB67BEEA0 - inverted data + * 0x0501FD0000A08300 - decrypted data, + * where: 0x05 - Button hold-cycle counter (8-bit, from 0 to 0x7F) + * 0x01FD - Parcel counter (normal 16-bit counter) + * 0x0000A083 - Serial number (32-bit) + * 0x0 - Button code (4-bit, 0x0 - #1 left-up; 0x2 - #2 right-up; 0x4 - #3 left-down; 0x6 - #4 right-down) + * 0x0 - Last zero nibble + * */ + + instance->data ^= 0xFFFFFFFFFFFFFFFF; + instance->data <<= 4; + + uint8_t pack[8] = {}; + pack[0] = (instance->data >> 56); + pack[1] = ((instance->data >> 48) & 0xFF); + pack[2] = ((instance->data >> 40) & 0xFF); + pack[3] = ((instance->data >> 32) & 0xFF); + pack[4] = ((instance->data >> 24) & 0xFF); + pack[5] = ((instance->data >> 16) & 0xFF); + pack[6] = ((instance->data >> 8) & 0xFF); + pack[7] = (instance->data & 0xFF); + + atomo_decrypt(pack); + + instance->cnt_2 = pack[0]; + instance->cnt = (uint16_t)pack[1] << 8 | pack[2]; + instance->serial = (uint32_t)(pack[3]) << 24 | pack[4] << 16 | pack[5] << 8 | pack[6]; + + uint8_t btn_decode = (pack[7] >> 4); + if(btn_decode == 0x0) { + instance->btn = 0x1; + } + if(btn_decode == 0x2) { + instance->btn = 0x2; + } + if(btn_decode == 0x4) { + instance->btn = 0x3; + } + if(btn_decode == 0x6) { + instance->btn = 0x4; + } + + uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; + uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; + instance->data_2 = (uint64_t)hi << 32 | lo; +} + +void atomo_encrypt(uint8_t* buff) { + uint8_t tmpB = (~buff[0] + 1) & 0x7F; + + uint8_t bitCnt = 8; + while(bitCnt < 59) { + if((tmpB & 0x18) && (((tmpB / 8) & 3) != 3)) { + tmpB = ((tmpB << 1) & 0xFF) | 1; + } else { + tmpB = (tmpB << 1) & 0xFF; + } + + if(tmpB & 0x80) { + buff[bitCnt / 8] ^= (0x80 >> (bitCnt & 7)); + } + + bitCnt++; + } + + buff[0] = (buff[0] ^ 5) & 0x7F; +} + +void atomo_decrypt(uint8_t* buff) { + buff[0] = (buff[0] ^ 5) & 0x7F; + uint8_t tmpB = (-buff[0]) & 0x7F; + + uint8_t bitCnt = 8; + while(bitCnt < 59) { + if((tmpB & 0x18) && (((tmpB / 8) & 3) != 3)) { + tmpB = ((tmpB << 1) & 0xFF) | 1; + } else { + tmpB = (tmpB << 1) & 0xFF; + } + + if(tmpB & 0x80) { + buff[bitCnt / 8] ^= (0x80 >> (bitCnt & 7)); + } + + bitCnt++; + } +} + +uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_came_atomo_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_came_atomo_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + subghz_protocol_came_atomo_remote_controller(&instance->generic); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%08lX%08lX\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Pcl_Cnt:0x%04lX\r\n" + "Btn_Cnt:0x%02X", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt, + instance->generic.cnt_2); +} diff --git a/applications/main/subghz/protocols/came_atomo.h b/applications/main/subghz/protocols/came_atomo.h new file mode 100644 index 000000000..736aee850 --- /dev/null +++ b/applications/main/subghz/protocols/came_atomo.h @@ -0,0 +1,110 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_CAME_ATOMO_NAME "CAME Atomo" + +typedef struct SubGhzProtocolDecoderCameAtomo SubGhzProtocolDecoderCameAtomo; +typedef struct SubGhzProtocolEncoderCameAtomo SubGhzProtocolEncoderCameAtomo; + +extern const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder; +extern const SubGhzProtocol subghz_protocol_came_atomo; + +void atomo_decrypt(uint8_t* buff); + +void atomo_encrypt(uint8_t* buff); + +/** + * Allocate SubGhzProtocolEncoderCameAtomo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderCameAtomo* pointer to a SubGhzProtocolEncoderCameAtomo instance + */ +void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderCameAtomo. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + */ +void subghz_protocol_encoder_came_atomo_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + */ +void subghz_protocol_encoder_came_atomo_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_came_atomo_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderCameAtomo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderCameAtomo* pointer to a SubGhzProtocolDecoderCameAtomo instance + */ +void* subghz_protocol_decoder_came_atomo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + */ +void subghz_protocol_decoder_came_atomo_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + */ +void subghz_protocol_decoder_came_atomo_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_came_atomo_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param output Resulting text + */ +void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/came_twee.c b/applications/main/subghz/protocols/came_twee.c new file mode 100644 index 000000000..e7eb33c42 --- /dev/null +++ b/applications/main/subghz/protocols/came_twee.c @@ -0,0 +1,468 @@ +#include "came_twee.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/forum/showthread.php?t=635&highlight=came+twin + * + */ + +#define TAG "SubGhzProtocolCAME_Twee" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') + +/** + * Rainbow table Came Twee. + */ +static const uint32_t came_twee_magic_numbers_xor[15] = { + 0x0E0E0E00, + 0x1D1D1D11, + 0x2C2C2C22, + 0x3B3B3B33, + 0x4A4A4A44, + 0x59595955, + 0x68686866, + 0x77777777, + 0x86868688, + 0x95959599, + 0xA4A4A4AA, + 0xB3B3B3BB, + 0xC2C2C2CC, + 0xD1D1D1DD, + 0xE0E0E0EE, +}; + +static const SubGhzBlockConst subghz_protocol_came_twee_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 250, + .min_count_bit_for_found = 54, +}; + +struct SubGhzProtocolDecoderCameTwee { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderCameTwee { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + CameTweeDecoderStepReset = 0, + CameTweeDecoderStepDecoderData, +} CameTweeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder = { + .alloc = subghz_protocol_decoder_came_twee_alloc, + .free = subghz_protocol_decoder_came_twee_free, + + .feed = subghz_protocol_decoder_came_twee_feed, + .reset = subghz_protocol_decoder_came_twee_reset, + + .get_hash_data = subghz_protocol_decoder_came_twee_get_hash_data, + .serialize = subghz_protocol_decoder_came_twee_serialize, + .deserialize = subghz_protocol_decoder_came_twee_deserialize, + .get_string = subghz_protocol_decoder_came_twee_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder = { + .alloc = subghz_protocol_encoder_came_twee_alloc, + .free = subghz_protocol_encoder_came_twee_free, + + .deserialize = subghz_protocol_encoder_came_twee_deserialize, + .stop = subghz_protocol_encoder_came_twee_stop, + .yield = subghz_protocol_encoder_came_twee_yield, +}; + +const SubGhzProtocol subghz_protocol_came_twee = { + .name = SUBGHZ_PROTOCOL_CAME_TWEE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_came_twee_decoder, + .encoder = &subghz_protocol_came_twee_encoder, +}; + +void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderCameTwee* instance = malloc(sizeof(SubGhzProtocolEncoderCameTwee)); + + instance->base.protocol = &subghz_protocol_came_twee; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_came_twee_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderCameTwee* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_came_twee_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_came_twee_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_came_twee_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_came_twee_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_came_twee_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 SubGhzProtocolEncoderCameTwee instance + */ +static void subghz_protocol_encoder_came_twee_get_upload(SubGhzProtocolEncoderCameTwee* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + uint64_t temp_parcel = 0x003FFF7200000000; //parcel mask + + for(int i = 14; i >= 0; i--) { + temp_parcel = (temp_parcel & 0xFFFFFFFF00000000) | + (instance->generic.serial ^ came_twee_magic_numbers_xor[i]); + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_came_twee_add_duration_to_upload(result); + manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_came_twee_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_came_twee_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)subghz_protocol_came_twee_const.te_long * 51); + } + instance->encoder.size_upload = index; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_came_twee_remote_controller(SubGhzBlockGeneric* instance) { + /* Came Twee 54 bit, rolling code 15 parcels with + * a decreasing counter from 0xE to 0x0 + * with originally coded dip switches on the console 10 bit code + * + * 0x003FFF72E04A6FEE + * 0x003FFF72D17B5EDD + * 0x003FFF72C2684DCC + * 0x003FFF72B3193CBB + * 0x003FFF72A40E2BAA + * 0x003FFF72953F1A99 + * 0x003FFF72862C0988 + * 0x003FFF7277DDF877 + * 0x003FFF7268C2E766 + * 0x003FFF7259F3D655 + * 0x003FFF724AE0C544 + * 0x003FFF723B91B433 + * 0x003FFF722C86A322 + * 0x003FFF721DB79211 + * 0x003FFF720EA48100 + * + * decryption + * the last 32 bits, do XOR by the desired number, divide the result by 4, + * convert the first 16 bits of the resulting 32-bit number to bin and do + * bit-by-bit mirroring, adding up to 10 bits + * + * Example + * Step 1. 0x003FFF721DB79211 => 0x1DB79211 + * Step 4. 0x1DB79211 xor 0x1D1D1D11 => 0x00AA8F00 + * Step 4. 0x00AA8F00 / 4 => 0x002AA3C0 + * Step 5. 0x002AA3C0 => 0x002A + * Step 6. 0x002A bin => b101010 + * Step 7. b101010 => b0101010000 + * Step 8. b0101010000 => (Dip) Off ON Off ON Off ON Off Off Off Off + */ + + uint8_t cnt_parcel = (uint8_t)(instance->data & 0xF); + uint32_t data = (uint32_t)(instance->data & 0x0FFFFFFFF); + + data = (data ^ came_twee_magic_numbers_xor[cnt_parcel]); + instance->serial = data; + data /= 4; + instance->btn = (data >> 4) & 0x0F; + data >>= 16; + data = (uint16_t)subghz_protocol_blocks_reverse_key(data, 16); + instance->cnt = data >> 6; +} + +bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderCameTwee* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_came_twee_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_came_twee_remote_controller(&instance->generic); + subghz_protocol_encoder_came_twee_get_upload(instance); + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_came_twee_stop(void* context) { + SubGhzProtocolEncoderCameTwee* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) { + SubGhzProtocolEncoderCameTwee* 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_came_twee_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderCameTwee* instance = malloc(sizeof(SubGhzProtocolDecoderCameTwee)); + instance->base.protocol = &subghz_protocol_came_twee; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_came_twee_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + free(instance); +} + +void subghz_protocol_decoder_came_twee_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + instance->decoder.parser_step = CameTweeDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case CameTweeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long * 51) < + subghz_protocol_came_twee_const.te_delta * 20)) { + //Found header CAME + instance->decoder.parser_step = CameTweeDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongLow, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } + break; + case CameTweeDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_short) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= ((uint32_t)subghz_protocol_came_twee_const.te_long * 2 + + subghz_protocol_came_twee_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_came_twee_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; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongLow, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = CameTweeDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_short) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = CameTweeDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_came_twee_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_came_twee_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + subghz_protocol_came_twee_remote_controller(&instance->generic); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "Btn:%X\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.btn, + CNT_TO_DIP(instance->generic.cnt)); +} diff --git a/applications/main/subghz/protocols/came_twee.h b/applications/main/subghz/protocols/came_twee.h new file mode 100644 index 000000000..359b964da --- /dev/null +++ b/applications/main/subghz/protocols/came_twee.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CAME_TWEE_NAME "CAME TWEE" + +typedef struct SubGhzProtocolDecoderCameTwee SubGhzProtocolDecoderCameTwee; +typedef struct SubGhzProtocolEncoderCameTwee SubGhzProtocolEncoderCameTwee; + +extern const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder; +extern const SubGhzProtocol subghz_protocol_came_twee; + +/** + * Allocate SubGhzProtocolEncoderCameTwee. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderCameTwee* pointer to a SubGhzProtocolEncoderCameTwee instance + */ +void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderCameTwee. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + */ +void subghz_protocol_encoder_came_twee_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + */ +void subghz_protocol_encoder_came_twee_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_came_twee_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderCameTwee. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderCameTwee* pointer to a SubGhzProtocolDecoderCameTwee instance + */ +void* subghz_protocol_decoder_came_twee_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + */ +void subghz_protocol_decoder_came_twee_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + */ +void subghz_protocol_decoder_came_twee_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_came_twee_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param output Resulting text + */ +void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/chamberlain_code.c b/applications/main/subghz/protocols/chamberlain_code.c new file mode 100644 index 000000000..9c8e5ee4a --- /dev/null +++ b/applications/main/subghz/protocols/chamberlain_code.c @@ -0,0 +1,499 @@ +#include "chamberlain_code.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolChamb_Code" + +#define CHAMBERLAIN_CODE_BIT_STOP 0b0001 +#define CHAMBERLAIN_CODE_BIT_1 0b0011 +#define CHAMBERLAIN_CODE_BIT_0 0b0111 + +#define CHAMBERLAIN_7_CODE_MASK 0xF000000FF0F +#define CHAMBERLAIN_8_CODE_MASK 0xF00000F00F +#define CHAMBERLAIN_9_CODE_MASK 0xF000000000F + +#define CHAMBERLAIN_7_CODE_MASK_CHECK 0x10000001101 +#define CHAMBERLAIN_8_CODE_MASK_CHECK 0x1000001001 +#define CHAMBERLAIN_9_CODE_MASK_CHECK 0x10000000001 + +#define CHAMBERLAIN_7_CODE_DIP_PATTERN "%c%c%c%c%c%c%c" +#define CHAMBERLAIN_7_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') + +#define CHAMBERLAIN_8_CODE_DIP_PATTERN "%c%c%c%c%cx%c%c" +#define CHAMBERLAIN_8_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), \ + (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \ + (dip & 0x0002 ? '1' : '0') + +#define CHAMBERLAIN_9_CODE_DIP_PATTERN "%c%c%c%c%c%c%c%c%c" +#define CHAMBERLAIN_9_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \ + (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), (dip & 0x0004 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_chamb_code_const = { + .te_short = 1000, + .te_long = 3000, + .te_delta = 200, + .min_count_bit_for_found = 10, +}; + +struct SubGhzProtocolDecoderChamb_Code { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderChamb_Code { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Chamb_CodeDecoderStepReset = 0, + Chamb_CodeDecoderStepFoundStartBit, + Chamb_CodeDecoderStepSaveDuration, + Chamb_CodeDecoderStepCheckDuration, +} Chamb_CodeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder = { + .alloc = subghz_protocol_decoder_chamb_code_alloc, + .free = subghz_protocol_decoder_chamb_code_free, + + .feed = subghz_protocol_decoder_chamb_code_feed, + .reset = subghz_protocol_decoder_chamb_code_reset, + + .get_hash_data = subghz_protocol_decoder_chamb_code_get_hash_data, + .serialize = subghz_protocol_decoder_chamb_code_serialize, + .deserialize = subghz_protocol_decoder_chamb_code_deserialize, + .get_string = subghz_protocol_decoder_chamb_code_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder = { + .alloc = subghz_protocol_encoder_chamb_code_alloc, + .free = subghz_protocol_encoder_chamb_code_free, + + .deserialize = subghz_protocol_encoder_chamb_code_deserialize, + .stop = subghz_protocol_encoder_chamb_code_stop, + .yield = subghz_protocol_encoder_chamb_code_yield, +}; + +const SubGhzProtocol subghz_protocol_chamb_code = { + .name = SUBGHZ_PROTOCOL_CHAMB_CODE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_chamb_code_decoder, + .encoder = &subghz_protocol_chamb_code_encoder, +}; + +void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolEncoderChamb_Code)); + + instance->base.protocol = &subghz_protocol_chamb_code; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 24; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_chamb_code_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderChamb_Code* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static uint64_t subghz_protocol_chamb_bit_to_code(uint64_t data, uint8_t size) { + uint64_t data_res = 0; + for(uint8_t i = 0; i < size; i++) { + if(!(bit_read(data, size - i - 1))) { + data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_0; + } else { + data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_1; + } + } + return data_res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @return true On success + */ +static bool + subghz_protocol_encoder_chamb_code_get_upload(SubGhzProtocolEncoderChamb_Code* instance) { + furi_assert(instance); + + uint64_t data = subghz_protocol_chamb_bit_to_code( + instance->generic.data, instance->generic.data_count_bit); + + switch(instance->generic.data_count_bit) { + case 7: + data = ((data >> 4) << 16) | (data & 0xF) << 4 | CHAMBERLAIN_7_CODE_MASK_CHECK; + break; + case 8: + data = ((data >> 12) << 16) | (data & 0xFF) << 4 | CHAMBERLAIN_8_CODE_MASK_CHECK; + break; + case 9: + data = (data << 4) | CHAMBERLAIN_9_CODE_MASK_CHECK; + break; + + default: + FURI_LOG_E(TAG, "Invalid bits count"); + return false; + break; + } +#define UPLOAD_HEX_DATA_SIZE 10 + uint8_t upload_hex_data[UPLOAD_HEX_DATA_SIZE] = {0}; + size_t upload_hex_count_bit = 0; + + //insert guard time + for(uint8_t i = 0; i < 36; i++) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + + //insert data + switch(instance->generic.data_count_bit) { + case 7: + case 9: + for(uint8_t i = 44; i > 0; i--) { + if(!bit_read(data, i - 1)) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + 1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + } + break; + case 8: + for(uint8_t i = 40; i > 0; i--) { + if(!bit_read(data, i - 1)) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + 1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + } + break; + } + + instance->encoder.size_upload = subghz_protocol_blocks_get_upload_from_bit_array( + upload_hex_data, + upload_hex_count_bit, + instance->encoder.upload, + instance->encoder.size_upload, + subghz_protocol_chamb_code_const.te_short, + SubGhzProtocolBlockAlignBitLeft); + + return true; +} + +bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderChamb_Code* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit > + subghz_protocol_chamb_code_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_chamb_code_stop(void* context) { + SubGhzProtocolEncoderChamb_Code* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context) { + SubGhzProtocolEncoderChamb_Code* 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_chamb_code_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolDecoderChamb_Code)); + instance->base.protocol = &subghz_protocol_chamb_code; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_chamb_code_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + free(instance); +} + +void subghz_protocol_decoder_chamb_code_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; +} + +static bool subghz_protocol_chamb_code_to_bit(uint64_t* data, uint8_t size) { + uint64_t data_tmp = data[0]; + uint64_t data_res = 0; + for(uint8_t i = 0; i < size; i++) { + if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_0) { + bit_write(data_res, i, 0); + } else if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_1) { + bit_write(data_res, i, 1); + } else { + return false; + } + data_tmp >>= 4; + } + data[0] = data_res; + return true; +} + +static bool subghz_protocol_decoder_chamb_code_check_mask_and_parse( + SubGhzProtocolDecoderChamb_Code* instance) { + furi_assert(instance); + if(instance->decoder.decode_count_bit > + subghz_protocol_chamb_code_const.min_count_bit_for_found + 1) + return false; + + if((instance->decoder.decode_data & CHAMBERLAIN_7_CODE_MASK) == + CHAMBERLAIN_7_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 7; + instance->decoder.decode_data &= ~CHAMBERLAIN_7_CODE_MASK; + instance->decoder.decode_data = (instance->decoder.decode_data >> 12) | + ((instance->decoder.decode_data >> 4) & 0xF); + } else if( + (instance->decoder.decode_data & CHAMBERLAIN_8_CODE_MASK) == + CHAMBERLAIN_8_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 8; + instance->decoder.decode_data &= ~CHAMBERLAIN_8_CODE_MASK; + instance->decoder.decode_data = instance->decoder.decode_data >> 4 | + CHAMBERLAIN_CODE_BIT_0 << 8; //DIP 6 no use + } else if( + (instance->decoder.decode_data & CHAMBERLAIN_9_CODE_MASK) == + CHAMBERLAIN_9_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 9; + instance->decoder.decode_data &= ~CHAMBERLAIN_9_CODE_MASK; + instance->decoder.decode_data >>= 4; + } else { + return false; + } + return subghz_protocol_chamb_code_to_bit( + &instance->decoder.decode_data, instance->decoder.decode_count_bit); +} + +void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + switch(instance->decoder.parser_step) { + case Chamb_CodeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 39) < + subghz_protocol_chamb_code_const.te_delta * 20)) { + //Found header Chamb_Code + instance->decoder.parser_step = Chamb_CodeDecoderStepFoundStartBit; + } + break; + case Chamb_CodeDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta)) { + //Found start bit Chamb_Code + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_STOP; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + case Chamb_CodeDecoderStepSaveDuration: + if(!level) { //save interval + if(duration > subghz_protocol_chamb_code_const.te_short * 5) { + if(instance->decoder.decode_count_bit >= + subghz_protocol_chamb_code_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + if(subghz_protocol_decoder_chamb_code_check_mask_and_parse(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Chamb_CodeDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + case Chamb_CodeDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( //Found stop bit Chamb_Code + instance->decoder.te_last, + subghz_protocol_chamb_code_const.te_short * 3) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_STOP; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short * 2) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 2) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_1; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 3) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_chamb_code_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit > + subghz_protocol_chamb_code_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%03lX\r\n" + "Yek:0x%03lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); + + switch(instance->generic.data_count_bit) { + case 7: + furi_string_cat_printf( + output, + "DIP:" CHAMBERLAIN_7_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_7_CODE_DATA_TO_DIP(code_found_lo)); + break; + case 8: + furi_string_cat_printf( + output, + "DIP:" CHAMBERLAIN_8_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_8_CODE_DATA_TO_DIP(code_found_lo)); + break; + case 9: + furi_string_cat_printf( + output, + "DIP:" CHAMBERLAIN_9_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_9_CODE_DATA_TO_DIP(code_found_lo)); + break; + + default: + break; + } +} diff --git a/applications/main/subghz/protocols/chamberlain_code.h b/applications/main/subghz/protocols/chamberlain_code.h new file mode 100644 index 000000000..f87b64d90 --- /dev/null +++ b/applications/main/subghz/protocols/chamberlain_code.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CHAMB_CODE_NAME "Cham_Code" + +typedef struct SubGhzProtocolDecoderChamb_Code SubGhzProtocolDecoderChamb_Code; +typedef struct SubGhzProtocolEncoderChamb_Code SubGhzProtocolEncoderChamb_Code; + +extern const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder; +extern const SubGhzProtocol subghz_protocol_chamb_code; + +/** + * Allocate SubGhzProtocolEncoderChamb_Code. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderChamb_Code* pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderChamb_Code. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void subghz_protocol_encoder_chamb_code_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void subghz_protocol_encoder_chamb_code_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderChamb_Code. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderChamb_Code* pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void subghz_protocol_decoder_chamb_code_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void subghz_protocol_decoder_chamb_code_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_chamb_code_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param output Resulting text + */ +void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/clemsa.c b/applications/main/subghz/protocols/clemsa.c new file mode 100644 index 000000000..a2cb7a18b --- /dev/null +++ b/applications/main/subghz/protocols/clemsa.c @@ -0,0 +1,365 @@ +#include "clemsa.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +// protocol BERNER / ELKA / TEDSEN / TELETASTER +#define TAG "SubGhzProtocolClemsa" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_clemsa_const = { + .te_short = 385, + .te_long = 2695, + .te_delta = 150, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderClemsa { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderClemsa { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ClemsaDecoderStepReset = 0, + ClemsaDecoderStepSaveDuration, + ClemsaDecoderStepCheckDuration, +} ClemsaDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_clemsa_decoder = { + .alloc = subghz_protocol_decoder_clemsa_alloc, + .free = subghz_protocol_decoder_clemsa_free, + + .feed = subghz_protocol_decoder_clemsa_feed, + .reset = subghz_protocol_decoder_clemsa_reset, + + .get_hash_data = subghz_protocol_decoder_clemsa_get_hash_data, + .serialize = subghz_protocol_decoder_clemsa_serialize, + .deserialize = subghz_protocol_decoder_clemsa_deserialize, + .get_string = subghz_protocol_decoder_clemsa_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_clemsa_encoder = { + .alloc = subghz_protocol_encoder_clemsa_alloc, + .free = subghz_protocol_encoder_clemsa_free, + + .deserialize = subghz_protocol_encoder_clemsa_deserialize, + .stop = subghz_protocol_encoder_clemsa_stop, + .yield = subghz_protocol_encoder_clemsa_yield, +}; + +const SubGhzProtocol subghz_protocol_clemsa = { + .name = SUBGHZ_PROTOCOL_CLEMSA_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_clemsa_decoder, + .encoder = &subghz_protocol_clemsa_encoder, +}; + +void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderClemsa* instance = malloc(sizeof(SubGhzProtocolEncoderClemsa)); + + instance->base.protocol = &subghz_protocol_clemsa; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_clemsa_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderClemsa* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderClemsa instance + * @return true On success + */ +static bool subghz_protocol_encoder_clemsa_get_upload(SubGhzProtocolEncoderClemsa* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_clemsa_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_clemsa_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_clemsa_const.te_short + + subghz_protocol_clemsa_const.te_long * 7); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_clemsa_const.te_long + + subghz_protocol_clemsa_const.te_long * 7); + } + return true; +} + +bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderClemsa* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_clemsa_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_clemsa_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_clemsa_stop(void* context) { + SubGhzProtocolEncoderClemsa* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_clemsa_yield(void* context) { + SubGhzProtocolEncoderClemsa* 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_clemsa_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderClemsa* instance = malloc(sizeof(SubGhzProtocolDecoderClemsa)); + instance->base.protocol = &subghz_protocol_clemsa; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_clemsa_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + free(instance); +} + +void subghz_protocol_decoder_clemsa_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + instance->decoder.parser_step = ClemsaDecoderStepReset; +} + +void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + + switch(instance->decoder.parser_step) { + case ClemsaDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short * 51) < + subghz_protocol_clemsa_const.te_delta * 25)) { + instance->decoder.parser_step = ClemsaDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case ClemsaDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ClemsaDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + break; + + case ClemsaDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_short) < + subghz_protocol_clemsa_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_long) < + subghz_protocol_clemsa_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ClemsaDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_long) < + subghz_protocol_clemsa_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short) < + subghz_protocol_clemsa_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ClemsaDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short * 51) < + subghz_protocol_clemsa_const.te_delta * 25) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_clemsa_const.te_short) < + subghz_protocol_clemsa_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_clemsa_const.te_long) < + subghz_protocol_clemsa_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + + if(instance->decoder.decode_count_bit == + subghz_protocol_clemsa_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.parser_step = ClemsaDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_clemsa_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = (instance->data >> 2) & 0xFFFF; + instance->btn = (instance->data & 0x03); +} + +uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_clemsa_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_clemsa_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + subghz_protocol_clemsa_check_remote_controller(&instance->generic); + //uint32_t data = (uint32_t)(instance->generic.data & 0xFFFFFF); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%05lX Btn %X\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN "\r\n" + " -: " DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0x3FFFF), + instance->generic.btn, + SHOW_DIP_P(instance->generic.serial, DIP_P), + SHOW_DIP_P(instance->generic.serial, DIP_O), + SHOW_DIP_P(instance->generic.serial, DIP_N)); +} diff --git a/applications/main/subghz/protocols/clemsa.h b/applications/main/subghz/protocols/clemsa.h new file mode 100644 index 000000000..8858c1a3b --- /dev/null +++ b/applications/main/subghz/protocols/clemsa.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CLEMSA_NAME "Clemsa" + +typedef struct SubGhzProtocolDecoderClemsa SubGhzProtocolDecoderClemsa; +typedef struct SubGhzProtocolEncoderClemsa SubGhzProtocolEncoderClemsa; + +extern const SubGhzProtocolDecoder subghz_protocol_clemsa_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_clemsa_encoder; +extern const SubGhzProtocol subghz_protocol_clemsa; + +/** + * Allocate SubGhzProtocolEncoderClemsa. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderClemsa* pointer to a SubGhzProtocolEncoderClemsa instance + */ +void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderClemsa. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + */ +void subghz_protocol_encoder_clemsa_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + */ +void subghz_protocol_encoder_clemsa_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_clemsa_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderClemsa. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderClemsa* pointer to a SubGhzProtocolDecoderClemsa instance + */ +void* subghz_protocol_decoder_clemsa_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + */ +void subghz_protocol_decoder_clemsa_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + */ +void subghz_protocol_decoder_clemsa_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_clemsa_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param output Resulting text + */ +void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/doitrand.c b/applications/main/subghz/protocols/doitrand.c new file mode 100644 index 000000000..6b31d4f27 --- /dev/null +++ b/applications/main/subghz/protocols/doitrand.c @@ -0,0 +1,356 @@ +#include "doitrand.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolDoitrand" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0001 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x1000 ? '1' : '0'), \ + (dip & 0x0800 ? '1' : '0'), (dip & 0x0400 ? '1' : '0'), (dip & 0x0200 ? '1' : '0'), \ + (dip & 0x0002 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_doitrand_const = { + .te_short = 400, + .te_long = 1100, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct SubGhzProtocolDecoderDoitrand { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderDoitrand { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + DoitrandDecoderStepReset = 0, + DoitrandDecoderStepFoundStartBit, + DoitrandDecoderStepSaveDuration, + DoitrandDecoderStepCheckDuration, +} DoitrandDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_doitrand_decoder = { + .alloc = subghz_protocol_decoder_doitrand_alloc, + .free = subghz_protocol_decoder_doitrand_free, + + .feed = subghz_protocol_decoder_doitrand_feed, + .reset = subghz_protocol_decoder_doitrand_reset, + + .get_hash_data = subghz_protocol_decoder_doitrand_get_hash_data, + .serialize = subghz_protocol_decoder_doitrand_serialize, + .deserialize = subghz_protocol_decoder_doitrand_deserialize, + .get_string = subghz_protocol_decoder_doitrand_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_doitrand_encoder = { + .alloc = subghz_protocol_encoder_doitrand_alloc, + .free = subghz_protocol_encoder_doitrand_free, + + .deserialize = subghz_protocol_encoder_doitrand_deserialize, + .stop = subghz_protocol_encoder_doitrand_stop, + .yield = subghz_protocol_encoder_doitrand_yield, +}; + +const SubGhzProtocol subghz_protocol_doitrand = { + .name = SUBGHZ_PROTOCOL_DOITRAND_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_doitrand_decoder, + .encoder = &subghz_protocol_doitrand_encoder, +}; + +void* subghz_protocol_encoder_doitrand_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderDoitrand* instance = malloc(sizeof(SubGhzProtocolEncoderDoitrand)); + + instance->base.protocol = &subghz_protocol_doitrand; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_doitrand_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderDoitrand* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderDoitrand instance + * @return true On success + */ +static bool subghz_protocol_encoder_doitrand_get_upload(SubGhzProtocolEncoderDoitrand* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_doitrand_const.te_short * 62); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_doitrand_const.te_short * 2 - 100); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_doitrand_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_doitrand_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_doitrand_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_doitrand_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderDoitrand* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_doitrand_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_doitrand_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_doitrand_stop(void* context) { + SubGhzProtocolEncoderDoitrand* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_doitrand_yield(void* context) { + SubGhzProtocolEncoderDoitrand* 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_doitrand_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderDoitrand* instance = malloc(sizeof(SubGhzProtocolDecoderDoitrand)); + instance->base.protocol = &subghz_protocol_doitrand; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_doitrand_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + free(instance); +} + +void subghz_protocol_decoder_doitrand_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + instance->decoder.parser_step = DoitrandDecoderStepReset; +} + +void subghz_protocol_decoder_doitrand_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + + switch(instance->decoder.parser_step) { + case DoitrandDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_doitrand_const.te_short * 62) < + subghz_protocol_doitrand_const.te_delta * 30)) { + //Found Preambula + instance->decoder.parser_step = DoitrandDecoderStepFoundStartBit; + } + break; + case DoitrandDecoderStepFoundStartBit: + if(level && ((DURATION_DIFF(duration, (subghz_protocol_doitrand_const.te_short * 2)) < + subghz_protocol_doitrand_const.te_delta * 3))) { + //Found start bit + instance->decoder.parser_step = DoitrandDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = DoitrandDecoderStepReset; + } + break; + case DoitrandDecoderStepSaveDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_doitrand_const.te_short * 10 + + subghz_protocol_doitrand_const.te_delta)) { + instance->decoder.parser_step = DoitrandDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit == + subghz_protocol_doitrand_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = DoitrandDecoderStepCheckDuration; + } + } + break; + case DoitrandDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_doitrand_const.te_short) < + subghz_protocol_doitrand_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_doitrand_const.te_long) < + subghz_protocol_doitrand_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = DoitrandDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_doitrand_const.te_long) < + subghz_protocol_doitrand_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_doitrand_const.te_short) < + subghz_protocol_doitrand_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = DoitrandDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = DoitrandDecoderStepReset; + } + } else { + instance->decoder.parser_step = DoitrandDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_doitrand_check_remote_controller(SubGhzBlockGeneric* instance) { + /* +* 67892345 0 k 1 +* 0000082F5F => 00000000000000000 10 000010111101011111 +* 0002082F5F => 00000000000100000 10 000010111101011111 +* 0200082F5F => 00010000000000000 10 000010111101011111 +* 0400082F5F => 00100000000000000 10 000010111101011111 +* 0800082F5F => 01000000000000000 10 000010111101011111 +* 1000082F5F => 10000000000000000 10 000010111101011111 +* 0020082F5F => 00000001000000000 10 000010111101011111 +* 0040082F5F => 00000010000000000 10 000010111101011111 +* 0080082F5F => 00000100000000000 10 000010111101011111 +* 0100082F5F => 00001000000000000 10 000010111101011111 +* 000008AF5F => 00000000000000000 10 001010111101011111 +* 1FE208AF5F => 11111111000100000 10 001010111101011111 +* +* 0...9 - DIP +* k- KEY +*/ + instance->cnt = (instance->data >> 24) | ((instance->data >> 15) & 0x1); + instance->btn = ((instance->data >> 18) & 0x3); +} + +uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_doitrand_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_doitrand_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderDoitrand* instance = context; + subghz_protocol_doitrand_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%02lX%08lX\r\n" + "Btn:%X\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.btn, + CNT_TO_DIP(instance->generic.cnt)); +} diff --git a/applications/main/subghz/protocols/doitrand.h b/applications/main/subghz/protocols/doitrand.h new file mode 100644 index 000000000..30f1fffd0 --- /dev/null +++ b/applications/main/subghz/protocols/doitrand.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_DOITRAND_NAME "Doitrand" + +typedef struct SubGhzProtocolDecoderDoitrand SubGhzProtocolDecoderDoitrand; +typedef struct SubGhzProtocolEncoderDoitrand SubGhzProtocolEncoderDoitrand; + +extern const SubGhzProtocolDecoder subghz_protocol_doitrand_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_doitrand_encoder; +extern const SubGhzProtocol subghz_protocol_doitrand; + +/** + * Allocate SubGhzProtocolEncoderDoitrand. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderDoitrand* pointer to a SubGhzProtocolEncoderDoitrand instance + */ +void* subghz_protocol_encoder_doitrand_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderDoitrand. + * @param context Pointer to a SubGhzProtocolEncoderDoitrand instance + */ +void subghz_protocol_encoder_doitrand_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderDoitrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderDoitrand instance + */ +void subghz_protocol_encoder_doitrand_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderDoitrand instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_doitrand_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderDoitrand. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderDoitrand* pointer to a SubGhzProtocolDecoderDoitrand instance + */ +void* subghz_protocol_decoder_doitrand_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderDoitrand. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + */ +void subghz_protocol_decoder_doitrand_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderDoitrand. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + */ +void subghz_protocol_decoder_doitrand_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_doitrand_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderDoitrand. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_doitrand_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderDoitrand. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance + * @param output Resulting text + */ +void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/dooya.c b/applications/main/subghz/protocols/dooya.c new file mode 100644 index 000000000..c70b6d54e --- /dev/null +++ b/applications/main/subghz/protocols/dooya.c @@ -0,0 +1,447 @@ +#include "dooya.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolDooya" + +#define DOYA_SINGLE_CHANNEL 0xFF + +static const SubGhzBlockConst subghz_protocol_dooya_const = { + .te_short = 366, + .te_long = 733, + .te_delta = 120, + .min_count_bit_for_found = 40, +}; + +struct SubGhzProtocolDecoderDooya { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderDooya { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + DooyaDecoderStepReset = 0, + DooyaDecoderStepFoundStartBit, + DooyaDecoderStepSaveDuration, + DooyaDecoderStepCheckDuration, +} DooyaDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_dooya_decoder = { + .alloc = subghz_protocol_decoder_dooya_alloc, + .free = subghz_protocol_decoder_dooya_free, + + .feed = subghz_protocol_decoder_dooya_feed, + .reset = subghz_protocol_decoder_dooya_reset, + + .get_hash_data = subghz_protocol_decoder_dooya_get_hash_data, + .serialize = subghz_protocol_decoder_dooya_serialize, + .deserialize = subghz_protocol_decoder_dooya_deserialize, + .get_string = subghz_protocol_decoder_dooya_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_dooya_encoder = { + .alloc = subghz_protocol_encoder_dooya_alloc, + .free = subghz_protocol_encoder_dooya_free, + + .deserialize = subghz_protocol_encoder_dooya_deserialize, + .stop = subghz_protocol_encoder_dooya_stop, + .yield = subghz_protocol_encoder_dooya_yield, +}; + +const SubGhzProtocol subghz_protocol_dooya = { + .name = SUBGHZ_PROTOCOL_DOOYA_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_dooya_decoder, + .encoder = &subghz_protocol_dooya_encoder, +}; + +void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderDooya* instance = malloc(sizeof(SubGhzProtocolEncoderDooya)); + + instance->base.protocol = &subghz_protocol_dooya; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_dooya_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderDooya* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderDooya instance + * @return true On success + */ +static bool subghz_protocol_encoder_dooya_get_upload(SubGhzProtocolEncoderDooya* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + if(bit_read(instance->generic.data, 0)) { + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_dooya_const.te_long * 12 + + subghz_protocol_dooya_const.te_long); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_dooya_const.te_long * 12 + + subghz_protocol_dooya_const.te_short); + } + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_short * 13); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_long * 2); + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderDooya* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_dooya_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_dooya_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_dooya_stop(void* context) { + SubGhzProtocolEncoderDooya* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_dooya_yield(void* context) { + SubGhzProtocolEncoderDooya* 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_dooya_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderDooya* instance = malloc(sizeof(SubGhzProtocolDecoderDooya)); + instance->base.protocol = &subghz_protocol_dooya; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_dooya_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + free(instance); +} + +void subghz_protocol_decoder_dooya_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + instance->decoder.parser_step = DooyaDecoderStepReset; +} + +void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + + switch(instance->decoder.parser_step) { + case DooyaDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long * 12) < + subghz_protocol_dooya_const.te_delta * 20)) { + instance->decoder.parser_step = DooyaDecoderStepFoundStartBit; + } + break; + + case DooyaDecoderStepFoundStartBit: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long * 2) < + subghz_protocol_dooya_const.te_delta * 3) { + instance->decoder.parser_step = DooyaDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + } else if( + DURATION_DIFF(duration, subghz_protocol_dooya_const.te_short * 13) < + subghz_protocol_dooya_const.te_delta * 5) { + break; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + break; + + case DooyaDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = DooyaDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + break; + + case DooyaDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_dooya_const.te_long * 4)) { + //add last bit + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_short) < + subghz_protocol_dooya_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if( + DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_long) < + subghz_protocol_dooya_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + break; + } + instance->decoder.parser_step = DooyaDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit == + subghz_protocol_dooya_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); + } + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_short) < + subghz_protocol_dooya_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long) < + subghz_protocol_dooya_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = DooyaDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_long) < + subghz_protocol_dooya_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_short) < + subghz_protocol_dooya_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = DooyaDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * serial s/m ch key + * long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 + * + * short press down 3 * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 + * 3 * E1DC03053C, 40b 111000011101110000000011 0000 0101 0011 1100 + * + * press stop X * E1DC030555, 40b 111000011101110000000011 0000 0101 0101 0101 + * + * long press up X * E1DC030511, 40b 111000011101110000000011 0000 0101 0001 0001 + * + * short press up 3 * E1DC030511, 40b 111000011101110000000011 0000 0101 0001 0001 + * 3 * E1DC03051E, 40b 111000011101110000000011 0000 0101 0001 1110 + * + * serial: 3 byte serial number + * s/m: single (b0000) / multi (b0001) channel console + * ch: channel if single (always b0101) or multi + * key: 0b00010001 - long press up + * 0b00011110 - short press up + * 0b00110011 - long press down + * 0b00111100 - short press down + * 0b01010101 - press stop + * 0b01111001 - press up + down + * 0b10000000 - press up + stop + * 0b10000001 - press down + stop + * 0b11001100 - press P2 + * +*/ + + instance->serial = (instance->data >> 16); + if((instance->data >> 12) & 0x0F) { + instance->cnt = (instance->data >> 8) & 0x0F; + } else { + instance->cnt = DOYA_SINGLE_CHANNEL; + } + instance->btn = instance->data & 0xFF; +} + +uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_dooya_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_dooya_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +/** + * Get button name. + * @param btn Button number, 8 bit + */ +static const char* subghz_protocol_dooya_get_name_button(uint8_t btn) { + const char* btn_name; + switch(btn) { + case 0b00010001: + btn_name = "Up_Long"; + break; + case 0b00011110: + btn_name = "Up_Short"; + break; + case 0b00110011: + btn_name = "Down_Long"; + break; + case 0b00111100: + btn_name = "Down_Short"; + break; + case 0b01010101: + btn_name = "Stop"; + break; + case 0b01111001: + btn_name = "Up+Down"; + break; + case 0b10000000: + btn_name = "Up+Stop"; + break; + case 0b10000001: + btn_name = "Down+Stop"; + break; + case 0b11001100: + btn_name = "P2"; + break; + default: + btn_name = "Unknown"; + break; + } + return btn_name; +} + +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); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%010llX\r\n" + "Sn:0x%08lX\r\n" + "Btn:%s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial, + subghz_protocol_dooya_get_name_button(instance->generic.btn)); + if(instance->generic.cnt == DOYA_SINGLE_CHANNEL) { + furi_string_cat_printf(output, "Ch:Single\r\n"); + } else { + furi_string_cat_printf(output, "Ch:%lu\r\n", instance->generic.cnt); + } +} diff --git a/applications/main/subghz/protocols/dooya.h b/applications/main/subghz/protocols/dooya.h new file mode 100644 index 000000000..f0cf843c0 --- /dev/null +++ b/applications/main/subghz/protocols/dooya.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_DOOYA_NAME "Dooya" + +typedef struct SubGhzProtocolDecoderDooya SubGhzProtocolDecoderDooya; +typedef struct SubGhzProtocolEncoderDooya SubGhzProtocolEncoderDooya; + +extern const SubGhzProtocolDecoder subghz_protocol_dooya_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_dooya_encoder; +extern const SubGhzProtocol subghz_protocol_dooya; + +/** + * Allocate SubGhzProtocolEncoderDooya. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderDooya* pointer to a SubGhzProtocolEncoderDooya instance + */ +void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderDooya. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + */ +void subghz_protocol_encoder_dooya_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + */ +void subghz_protocol_encoder_dooya_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_dooya_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderDooya. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderDooya* pointer to a SubGhzProtocolDecoderDooya instance + */ +void* subghz_protocol_decoder_dooya_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + */ +void subghz_protocol_decoder_dooya_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + */ +void subghz_protocol_decoder_dooya_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_dooya_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param output Resulting text + */ +void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/faac_slh.c b/applications/main/subghz/protocols/faac_slh.c new file mode 100644 index 000000000..7572bd8ab --- /dev/null +++ b/applications/main/subghz/protocols/faac_slh.c @@ -0,0 +1,512 @@ +#include "faac_slh.h" +#include "../subghz_keystore.h" +#include +#include "keeloq_common.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolFaacSLH" + +static const SubGhzBlockConst subghz_protocol_faac_slh_const = { + .te_short = 255, + .te_long = 595, + .te_delta = 100, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderFaacSLH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + SubGhzKeystore* keystore; + const char* manufacture_name; +}; + +struct SubGhzProtocolEncoderFaacSLH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + SubGhzKeystore* keystore; + const char* manufacture_name; +}; + +typedef enum { + FaacSLHDecoderStepReset = 0, + FaacSLHDecoderStepFoundPreambula, + FaacSLHDecoderStepSaveDuration, + FaacSLHDecoderStepCheckDuration, +} FaacSLHDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder = { + .alloc = subghz_protocol_decoder_faac_slh_alloc, + .free = subghz_protocol_decoder_faac_slh_free, + + .feed = subghz_protocol_decoder_faac_slh_feed, + .reset = subghz_protocol_decoder_faac_slh_reset, + + .get_hash_data = subghz_protocol_decoder_faac_slh_get_hash_data, + .serialize = subghz_protocol_decoder_faac_slh_serialize, + .deserialize = subghz_protocol_decoder_faac_slh_deserialize, + .get_string = subghz_protocol_decoder_faac_slh_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder = { + .alloc = subghz_protocol_encoder_faac_slh_alloc, + .free = subghz_protocol_encoder_faac_slh_free, + + .deserialize = subghz_protocol_encoder_faac_slh_deserialize, + .stop = subghz_protocol_encoder_faac_slh_stop, + .yield = subghz_protocol_encoder_faac_slh_yield, +}; + +const SubGhzProtocol subghz_protocol_faac_slh = { + .name = SUBGHZ_PROTOCOL_FAAC_SLH_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_faac_slh_decoder, + .encoder = &subghz_protocol_faac_slh_encoder, +}; + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ +static void subghz_protocol_faac_slh_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name); + +void* subghz_protocol_encoder_faac_slh_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderFaacSLH* instance = malloc(sizeof(SubGhzProtocolEncoderFaacSLH)); + + instance->base.protocol = &subghz_protocol_faac_slh; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + 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_faac_slh_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderFaacSLH* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* instance) { + instance->generic.cnt++; + uint32_t fix = instance->generic.serial << 4 | instance->generic.btn; + uint32_t hop = 0; + uint32_t decrypt = 0; + uint64_t man = 0; + int res = 0; + char fixx[8] = {}; + int shiftby = 32; + for(int i = 0; i < 8; i++) { + fixx[i] = (fix >> (shiftby -= 4)) & 0xF; + } + if((instance->generic.cnt % 2) == 0) { + decrypt = fixx[6] << 28 | fixx[7] << 24 | fixx[5] << 20 | + (instance->generic.cnt & 0xFFFFF); + } else { + decrypt = fixx[2] << 28 | fixx[3] << 24 | fixx[4] << 20 | + (instance->generic.cnt & 0xFFFFF); + } + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_FAAC: + //FAAC Learning + man = subghz_protocol_keeloq_common_faac_learning( + instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + } + break; + } + } + if(hop) { + instance->generic.data = (uint64_t)fix << 32 | hop; + } + return true; +} + +bool subghz_protocol_faac_slh_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset) { + furi_assert(context); + // roguemaster don't steal!!! + SubGhzProtocolEncoderFaacSLH* instance = context; + instance->generic.serial = serial; + instance->generic.btn = btn; + instance->generic.cnt = (cnt & 0xFFFFF); + instance->generic.seed = seed; + instance->manufacture_name = manufacture_name; + instance->generic.data_count_bit = 64; + bool res = subghz_protocol_faac_slh_gen_data(instance); + if(res) { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderFaacSLH instance + * @return true On success + */ +static bool subghz_protocol_encoder_faac_slh_get_upload(SubGhzProtocolEncoderFaacSLH* instance) { + furi_assert(instance); + + subghz_protocol_faac_slh_gen_data(instance); + size_t index = 0; + size_t size_upload = 2 + (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_faac_slh_const.te_long * 2); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_faac_slh_const.te_long * 2); + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_faac_slh_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_faac_slh_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_faac_slh_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_faac_slh_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderFaacSLH* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + uint8_t seed_data[sizeof(uint32_t)] = {0}; + for(size_t i = 0; i < sizeof(uint32_t); i++) { + seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF; + } + if(!flipper_format_read_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) { + FURI_LOG_E(TAG, "Missing Seed"); + break; + } + instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | + seed_data[3]; + + subghz_protocol_faac_slh_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_faac_slh_get_upload(instance); + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_faac_slh_stop(void* context) { + SubGhzProtocolEncoderFaacSLH* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_faac_slh_yield(void* context) { + SubGhzProtocolEncoderFaacSLH* 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_faac_slh_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderFaacSLH* instance = malloc(sizeof(SubGhzProtocolDecoderFaacSLH)); + instance->base.protocol = &subghz_protocol_faac_slh; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + return instance; +} + +void subghz_protocol_decoder_faac_slh_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + free(instance); +} + +void subghz_protocol_decoder_faac_slh_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + instance->decoder.parser_step = FaacSLHDecoderStepReset; +} + +void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + + switch(instance->decoder.parser_step) { + case FaacSLHDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long * 2) < + subghz_protocol_faac_slh_const.te_delta * 3)) { + instance->decoder.parser_step = FaacSLHDecoderStepFoundPreambula; + } + break; + case FaacSLHDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long * 2) < + subghz_protocol_faac_slh_const.te_delta * 3)) { + //Found Preambula + instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + break; + case FaacSLHDecoderStepSaveDuration: + if(level) { + if(duration >= ((uint32_t)subghz_protocol_faac_slh_const.te_short * 3 + + subghz_protocol_faac_slh_const.te_delta)) { + instance->decoder.parser_step = FaacSLHDecoderStepFoundPreambula; + if(instance->decoder.decode_count_bit == + subghz_protocol_faac_slh_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = FaacSLHDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + break; + case FaacSLHDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_faac_slh_const.te_short) < + subghz_protocol_faac_slh_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long) < + subghz_protocol_faac_slh_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_faac_slh_const.te_long) < + subghz_protocol_faac_slh_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_short) < + subghz_protocol_faac_slh_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manifacture_name Manufacturer name + */ +static void subghz_protocol_faac_slh_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint32_t code_fix = instance->data >> 32; + uint32_t code_hop = instance->data & 0xFFFFFFFF; + instance->serial = code_fix >> 4; + instance->btn = code_fix & 0xF; + uint32_t decrypt = 0; + uint64_t man; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_FAAC: + // FAAC Learning + man = subghz_protocol_keeloq_common_faac_learning( + instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(code_hop, man); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + break; + } + } + instance->cnt = decrypt & 0xFFFFF; +} + +uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_faac_slh_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + uint8_t seed_data[sizeof(uint32_t)] = {0}; + for(size_t i = 0; i < sizeof(uint32_t); i++) { + seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF; + } + if(res && !flipper_format_write_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) { + FURI_LOG_E(TAG, "Unable to add Seed"); + res = false; + } + instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | + seed_data[3]; + + subghz_protocol_faac_slh_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + return res; +} + +bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_faac_slh_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + + uint8_t seed_data[sizeof(uint32_t)] = {0}; + for(size_t i = 0; i < sizeof(uint32_t); i++) { + seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF; + } + if(!flipper_format_read_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) { + FURI_LOG_E(TAG, "Missing Seed"); + break; + } + instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | + seed_data[3]; + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + subghz_protocol_faac_slh_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + uint32_t code_fix = instance->generic.data >> 32; + uint32_t code_hop = instance->generic.data & 0xFFFFFFFF; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%lX%08lX\r\n" + "Fix:%08lX Cnt:%05lX\r\n" + "Hop:%08lX Btn:%X\r\n" + "Sn:%07lX Sd:%08lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + code_fix, + instance->generic.cnt, + code_hop, + instance->generic.btn, + instance->generic.serial, + instance->generic.seed); +} diff --git a/applications/main/subghz/protocols/faac_slh.h b/applications/main/subghz/protocols/faac_slh.h new file mode 100644 index 000000000..9390da43a --- /dev/null +++ b/applications/main/subghz/protocols/faac_slh.h @@ -0,0 +1,129 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_FAAC_SLH_NAME "Faac SLH" + +typedef struct SubGhzProtocolDecoderFaacSLH SubGhzProtocolDecoderFaacSLH; +typedef struct SubGhzProtocolEncoderFaacSLH SubGhzProtocolEncoderFaacSLH; + +extern const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder; +extern const SubGhzProtocol subghz_protocol_faac_slh; + +/** + * Allocate SubGhzProtocolEncoderFaacSLH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderFaacSLH* pointer to a SubGhzProtocolEncoderFaacSLH instance + */ +void* subghz_protocol_encoder_faac_slh_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderFaacSLH. + * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance + */ +void subghz_protocol_encoder_faac_slh_free(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param seed Seed value, 32 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_faac_slh_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance + */ +void subghz_protocol_encoder_faac_slh_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_faac_slh_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderFaacSLH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderFaacSLH* pointer to a SubGhzProtocolDecoderFaacSLH instance + */ +void* subghz_protocol_decoder_faac_slh_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + */ +void subghz_protocol_decoder_faac_slh_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + */ +void subghz_protocol_decoder_faac_slh_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_faac_slh_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param output Resulting text + */ +void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/gate_tx.c b/applications/main/subghz/protocols/gate_tx.c new file mode 100644 index 000000000..4c7c2d484 --- /dev/null +++ b/applications/main/subghz/protocols/gate_tx.c @@ -0,0 +1,334 @@ +#include "gate_tx.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolGateTx" + +static const SubGhzBlockConst subghz_protocol_gate_tx_const = { + .te_short = 350, + .te_long = 700, + .te_delta = 100, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderGateTx { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderGateTx { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + GateTXDecoderStepReset = 0, + GateTXDecoderStepFoundStartBit, + GateTXDecoderStepSaveDuration, + GateTXDecoderStepCheckDuration, +} GateTXDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder = { + .alloc = subghz_protocol_decoder_gate_tx_alloc, + .free = subghz_protocol_decoder_gate_tx_free, + + .feed = subghz_protocol_decoder_gate_tx_feed, + .reset = subghz_protocol_decoder_gate_tx_reset, + + .get_hash_data = subghz_protocol_decoder_gate_tx_get_hash_data, + .serialize = subghz_protocol_decoder_gate_tx_serialize, + .deserialize = subghz_protocol_decoder_gate_tx_deserialize, + .get_string = subghz_protocol_decoder_gate_tx_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder = { + .alloc = subghz_protocol_encoder_gate_tx_alloc, + .free = subghz_protocol_encoder_gate_tx_free, + + .deserialize = subghz_protocol_encoder_gate_tx_deserialize, + .stop = subghz_protocol_encoder_gate_tx_stop, + .yield = subghz_protocol_encoder_gate_tx_yield, +}; + +const SubGhzProtocol subghz_protocol_gate_tx = { + .name = SUBGHZ_PROTOCOL_GATE_TX_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_gate_tx_decoder, + .encoder = &subghz_protocol_gate_tx_encoder, +}; + +void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderGateTx* instance = malloc(sizeof(SubGhzProtocolEncoderGateTx)); + + instance->base.protocol = &subghz_protocol_gate_tx; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_gate_tx_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderGateTx* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderGateTx instance + * @return true On success + */ +static bool subghz_protocol_encoder_gate_tx_get_upload(SubGhzProtocolEncoderGateTx* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_short * 49); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_long); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderGateTx* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_gate_tx_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_gate_tx_stop(void* context) { + SubGhzProtocolEncoderGateTx* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context) { + SubGhzProtocolEncoderGateTx* 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_gate_tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderGateTx* instance = malloc(sizeof(SubGhzProtocolDecoderGateTx)); + instance->base.protocol = &subghz_protocol_gate_tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_gate_tx_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + free(instance); +} + +void subghz_protocol_decoder_gate_tx_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + instance->decoder.parser_step = GateTXDecoderStepReset; +} + +void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + + switch(instance->decoder.parser_step) { + case GateTXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_short * 47) < + subghz_protocol_gate_tx_const.te_delta * 47)) { + //Found Preambula + instance->decoder.parser_step = GateTXDecoderStepFoundStartBit; + } + break; + case GateTXDecoderStepFoundStartBit: + if(level && ((DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_long) < + subghz_protocol_gate_tx_const.te_delta * 3))) { + //Found start bit + instance->decoder.parser_step = GateTXDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = GateTXDecoderStepReset; + } + break; + case GateTXDecoderStepSaveDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_gate_tx_const.te_short * 10 + + subghz_protocol_gate_tx_const.te_delta)) { + instance->decoder.parser_step = GateTXDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_gate_tx_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GateTXDecoderStepCheckDuration; + } + } + break; + case GateTXDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gate_tx_const.te_short) < + subghz_protocol_gate_tx_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_long) < + subghz_protocol_gate_tx_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GateTXDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gate_tx_const.te_long) < + subghz_protocol_gate_tx_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_short) < + subghz_protocol_gate_tx_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GateTXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GateTXDecoderStepReset; + } + } else { + instance->decoder.parser_step = GateTXDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_gate_tx_check_remote_controller(SubGhzBlockGeneric* instance) { + uint32_t code_found_reverse = + subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + + instance->serial = (code_found_reverse & 0xFF) << 12 | + ((code_found_reverse >> 8) & 0xFF) << 4 | + ((code_found_reverse >> 20) & 0x0F); + instance->btn = ((code_found_reverse >> 16) & 0x0F); +} + +uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_gate_tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_gate_tx_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + subghz_protocol_gate_tx_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%06lX\r\n" + "Sn:%05lX Btn:%X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/applications/main/subghz/protocols/gate_tx.h b/applications/main/subghz/protocols/gate_tx.h new file mode 100644 index 000000000..4bfba3597 --- /dev/null +++ b/applications/main/subghz/protocols/gate_tx.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_GATE_TX_NAME "GateTX" + +typedef struct SubGhzProtocolDecoderGateTx SubGhzProtocolDecoderGateTx; +typedef struct SubGhzProtocolEncoderGateTx SubGhzProtocolEncoderGateTx; + +extern const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder; +extern const SubGhzProtocol subghz_protocol_gate_tx; + +/** + * Allocate SubGhzProtocolEncoderGateTx. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderGateTx* pointer to a SubGhzProtocolEncoderGateTx instance + */ +void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderGateTx. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + */ +void subghz_protocol_encoder_gate_tx_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + */ +void subghz_protocol_encoder_gate_tx_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderGateTx. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderGateTx* pointer to a SubGhzProtocolDecoderGateTx instance + */ +void* subghz_protocol_decoder_gate_tx_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + */ +void subghz_protocol_decoder_gate_tx_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + */ +void subghz_protocol_decoder_gate_tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_gate_tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param output Resulting text + */ +void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/holtek.c b/applications/main/subghz/protocols/holtek.c new file mode 100644 index 000000000..8aaad3b71 --- /dev/null +++ b/applications/main/subghz/protocols/holtek.c @@ -0,0 +1,374 @@ +#include "holtek.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://pdf1.alldatasheet.com/datasheet-pdf/view/82103/HOLTEK/HT640.html + * https://fccid.io/OJM-CMD-HHLR-XXXA + * + */ + +#define TAG "SubGhzProtocolHoltek" + +#define HOLTEK_HEADER_MASK 0xF000000000 +#define HOLTEK_HEADER 0x5000000000 + +static const SubGhzBlockConst subghz_protocol_holtek_const = { + .te_short = 430, + .te_long = 870, + .te_delta = 100, + .min_count_bit_for_found = 40, +}; + +struct SubGhzProtocolDecoderHoltek { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderHoltek { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + HoltekDecoderStepReset = 0, + HoltekDecoderStepFoundStartBit, + HoltekDecoderStepSaveDuration, + HoltekDecoderStepCheckDuration, +} HoltekDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_holtek_decoder = { + .alloc = subghz_protocol_decoder_holtek_alloc, + .free = subghz_protocol_decoder_holtek_free, + + .feed = subghz_protocol_decoder_holtek_feed, + .reset = subghz_protocol_decoder_holtek_reset, + + .get_hash_data = subghz_protocol_decoder_holtek_get_hash_data, + .serialize = subghz_protocol_decoder_holtek_serialize, + .deserialize = subghz_protocol_decoder_holtek_deserialize, + .get_string = subghz_protocol_decoder_holtek_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_holtek_encoder = { + .alloc = subghz_protocol_encoder_holtek_alloc, + .free = subghz_protocol_encoder_holtek_free, + + .deserialize = subghz_protocol_encoder_holtek_deserialize, + .stop = subghz_protocol_encoder_holtek_stop, + .yield = subghz_protocol_encoder_holtek_yield, +}; + +const SubGhzProtocol subghz_protocol_holtek = { + .name = SUBGHZ_PROTOCOL_HOLTEK_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_holtek_decoder, + .encoder = &subghz_protocol_holtek_encoder, +}; + +void* subghz_protocol_encoder_holtek_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoltek* instance = malloc(sizeof(SubGhzProtocolEncoderHoltek)); + + instance->base.protocol = &subghz_protocol_holtek; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_holtek_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoltek* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHoltek instance + * @return true On success + */ +static bool subghz_protocol_encoder_holtek_get_upload(SubGhzProtocolEncoderHoltek* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_holtek_const.te_short * 36); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_holtek_const.te_short); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_holtek_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_holtek_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_holtek_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_holtek_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoltek* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_holtek_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_holtek_stop(void* context) { + SubGhzProtocolEncoderHoltek* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_holtek_yield(void* context) { + SubGhzProtocolEncoderHoltek* 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_holtek_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoltek* instance = malloc(sizeof(SubGhzProtocolDecoderHoltek)); + instance->base.protocol = &subghz_protocol_holtek; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_holtek_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + free(instance); +} + +void subghz_protocol_decoder_holtek_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + instance->decoder.parser_step = HoltekDecoderStepReset; +} + +void subghz_protocol_decoder_holtek_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + + switch(instance->decoder.parser_step) { + case HoltekDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_const.te_short * 36) < + subghz_protocol_holtek_const.te_delta * 36)) { + //Found Preambula + instance->decoder.parser_step = HoltekDecoderStepFoundStartBit; + } + break; + case HoltekDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_holtek_const.te_short) < + subghz_protocol_holtek_const.te_delta)) { + //Found StartBit + instance->decoder.parser_step = HoltekDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = HoltekDecoderStepReset; + } + break; + case HoltekDecoderStepSaveDuration: + //save duration + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_holtek_const.te_short * 10 + + subghz_protocol_holtek_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_holtek_const.min_count_bit_for_found) { + if((instance->decoder.decode_data & HOLTEK_HEADER_MASK) == HOLTEK_HEADER) { + 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 = HoltekDecoderStepFoundStartBit; + break; + } else { + instance->decoder.te_last = duration; + + instance->decoder.parser_step = HoltekDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = HoltekDecoderStepReset; + } + break; + case HoltekDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_holtek_const.te_short) < + subghz_protocol_holtek_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_holtek_const.te_long) < + subghz_protocol_holtek_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = HoltekDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_holtek_const.te_long) < + subghz_protocol_holtek_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_holtek_const.te_short) < + subghz_protocol_holtek_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = HoltekDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = HoltekDecoderStepReset; + } + } else { + instance->decoder.parser_step = HoltekDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_holtek_check_remote_controller(SubGhzBlockGeneric* instance) { + if((instance->data & HOLTEK_HEADER_MASK) == HOLTEK_HEADER) { + instance->serial = + subghz_protocol_blocks_reverse_key((instance->data >> 16) & 0xFFFFF, 20); + uint16_t btn = instance->data & 0xFFFF; + if((btn & 0xf) != 0xA) { + instance->btn = 0x1 << 4 | (btn & 0xF); + } else if(((btn >> 4) & 0xF) != 0xA) { + instance->btn = 0x2 << 4 | ((btn >> 4) & 0xF); + } else if(((btn >> 8) & 0xF) != 0xA) { + instance->btn = 0x3 << 4 | ((btn >> 8) & 0xF); + } else if(((btn >> 12) & 0xF) != 0xA) { + instance->btn = 0x4 << 4 | ((btn >> 12) & 0xF); + } else { + instance->btn = 0; + } + } else { + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + } +} + +uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_holtek_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoltek* instance = context; + subghz_protocol_holtek_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%05lX Btn:%X ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF), + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + instance->generic.btn >> 4); + + if((instance->generic.btn & 0xF) == 0xE) { + furi_string_cat_printf(output, "ON\r\n"); + } else if(((instance->generic.btn & 0xF) == 0xB)) { + furi_string_cat_printf(output, "OFF\r\n"); + } +} diff --git a/applications/main/subghz/protocols/holtek.h b/applications/main/subghz/protocols/holtek.h new file mode 100644 index 000000000..252a26dc7 --- /dev/null +++ b/applications/main/subghz/protocols/holtek.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HOLTEK_NAME "Holtek" + +typedef struct SubGhzProtocolDecoderHoltek SubGhzProtocolDecoderHoltek; +typedef struct SubGhzProtocolEncoderHoltek SubGhzProtocolEncoderHoltek; + +extern const SubGhzProtocolDecoder subghz_protocol_holtek_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_holtek_encoder; +extern const SubGhzProtocol subghz_protocol_holtek; + +/** + * Allocate SubGhzProtocolEncoderHoltek. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHoltek* pointer to a SubGhzProtocolEncoderHoltek instance + */ +void* subghz_protocol_encoder_holtek_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHoltek. + * @param context Pointer to a SubGhzProtocolEncoderHoltek instance + */ +void subghz_protocol_encoder_holtek_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHoltek instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHoltek instance + */ +void subghz_protocol_encoder_holtek_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHoltek instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_holtek_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHoltek. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHoltek* pointer to a SubGhzProtocolDecoderHoltek instance + */ +void* subghz_protocol_decoder_holtek_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHoltek. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + */ +void subghz_protocol_decoder_holtek_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHoltek. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + */ +void subghz_protocol_decoder_holtek_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_holtek_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHoltek. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_holtek_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHoltek. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHoltek instance + * @param output Resulting text + */ +void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/holtek_ht12x.c b/applications/main/subghz/protocols/holtek_ht12x.c new file mode 100644 index 000000000..169387ded --- /dev/null +++ b/applications/main/subghz/protocols/holtek_ht12x.c @@ -0,0 +1,400 @@ +#include "holtek_ht12x.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://www.holtek.com/documents/10179/116711/HT12A_Ev130.pdf + * + */ + +#define TAG "SubGhzProtocolHoltek_HT12X" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0080 ? '0' : '1'), (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), \ + (dip & 0x0010 ? '0' : '1'), (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), \ + (dip & 0x0002 ? '0' : '1'), (dip & 0x0001 ? '0' : '1') + +static const SubGhzBlockConst subghz_protocol_holtek_th12x_const = { + .te_short = 320, + .te_long = 640, + .te_delta = 200, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderHoltek_HT12X { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderHoltek_HT12X { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + Holtek_HT12XDecoderStepReset = 0, + Holtek_HT12XDecoderStepFoundStartBit, + Holtek_HT12XDecoderStepSaveDuration, + Holtek_HT12XDecoderStepCheckDuration, +} Holtek_HT12XDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_holtek_th12x_decoder = { + .alloc = subghz_protocol_decoder_holtek_th12x_alloc, + .free = subghz_protocol_decoder_holtek_th12x_free, + + .feed = subghz_protocol_decoder_holtek_th12x_feed, + .reset = subghz_protocol_decoder_holtek_th12x_reset, + + .get_hash_data = subghz_protocol_decoder_holtek_th12x_get_hash_data, + .serialize = subghz_protocol_decoder_holtek_th12x_serialize, + .deserialize = subghz_protocol_decoder_holtek_th12x_deserialize, + .get_string = subghz_protocol_decoder_holtek_th12x_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_holtek_th12x_encoder = { + .alloc = subghz_protocol_encoder_holtek_th12x_alloc, + .free = subghz_protocol_encoder_holtek_th12x_free, + + .deserialize = subghz_protocol_encoder_holtek_th12x_deserialize, + .stop = subghz_protocol_encoder_holtek_th12x_stop, + .yield = subghz_protocol_encoder_holtek_th12x_yield, +}; + +const SubGhzProtocol subghz_protocol_holtek_th12x = { + .name = SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_holtek_th12x_decoder, + .encoder = &subghz_protocol_holtek_th12x_encoder, +}; + +void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoltek_HT12X* instance = + malloc(sizeof(SubGhzProtocolEncoderHoltek_HT12X)); + + instance->base.protocol = &subghz_protocol_holtek_th12x; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_holtek_th12x_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @return true On success + */ +static bool + subghz_protocol_encoder_holtek_th12x_get_upload(SubGhzProtocolEncoderHoltek_HT12X* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 36); + //Send start bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 2); + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 2); + } + } + return true; +} + +bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_holtek_th12x_stop(void* context) { + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context) { + SubGhzProtocolEncoderHoltek_HT12X* 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_holtek_th12x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoltek_HT12X* instance = + malloc(sizeof(SubGhzProtocolDecoderHoltek_HT12X)); + instance->base.protocol = &subghz_protocol_holtek_th12x; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_holtek_th12x_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + free(instance); +} + +void subghz_protocol_decoder_holtek_th12x_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; +} + +void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + + 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)) { + //Found Preambula + instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; + } + break; + case Holtek_HT12XDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta)) { + //Found StartBit + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = duration; + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + case Holtek_HT12XDecoderStepSaveDuration: + //save duration + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_holtek_th12x_const.te_short * 10 + + subghz_protocol_holtek_th12x_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 3 + 1); + + 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; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; + break; + } else { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = Holtek_HT12XDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + case Holtek_HT12XDecoderStepCheckDuration: + if(level) { + instance->te += duration; + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_holtek_th12x_const.te_long) < + subghz_protocol_holtek_th12x_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_long) < + subghz_protocol_holtek_th12x_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_holtek_th12x_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = instance->data & 0x0F; + instance->cnt = (instance->data >> 4) & 0xFF; +} + +uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_holtek_th12x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + ret = true; + } while(false); + return ret; +} + +static void subghz_protocol_holtek_th12x_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( + output, + "%s%s%s%s\r\n", + (((event >> 3) & 0x1) == 0x0 ? "B1 " : ""), + (((event >> 2) & 0x1) == 0x0 ? "B2 " : ""), + (((event >> 1) & 0x1) == 0x0 ? "B3 " : ""), + (((event >> 0) & 0x1) == 0x0 ? "B4 " : "")); +} + +void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + subghz_protocol_holtek_th12x_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%03lX\r\n" + "Btn: ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFF)); + subghz_protocol_holtek_th12x_event_serialize(instance->generic.btn, output); + furi_string_cat_printf( + output, + "DIP:" DIP_PATTERN "\r\n" + "Te:%luus\r\n", + CNT_TO_DIP(instance->generic.cnt), + instance->te); +} diff --git a/applications/main/subghz/protocols/holtek_ht12x.h b/applications/main/subghz/protocols/holtek_ht12x.h new file mode 100644 index 000000000..7b5c31dd7 --- /dev/null +++ b/applications/main/subghz/protocols/holtek_ht12x.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME "Holtek_HT12X" + +typedef struct SubGhzProtocolDecoderHoltek_HT12X SubGhzProtocolDecoderHoltek_HT12X; +typedef struct SubGhzProtocolEncoderHoltek_HT12X SubGhzProtocolEncoderHoltek_HT12X; + +extern const SubGhzProtocolDecoder subghz_protocol_holtek_th12x_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_holtek_th12x_encoder; +extern const SubGhzProtocol subghz_protocol_holtek_th12x; + +/** + * Allocate SubGhzProtocolEncoderHoltek_HT12X. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHoltek_HT12X* pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void subghz_protocol_encoder_holtek_th12x_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void subghz_protocol_encoder_holtek_th12x_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHoltek_HT12X. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHoltek_HT12X* pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void* subghz_protocol_decoder_holtek_th12x_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void subghz_protocol_decoder_holtek_th12x_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void subghz_protocol_decoder_holtek_th12x_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_holtek_th12x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param output Resulting text + */ +void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/honeywell_wdb.c b/applications/main/subghz/protocols/honeywell_wdb.c new file mode 100644 index 000000000..3b940fc67 --- /dev/null +++ b/applications/main/subghz/protocols/honeywell_wdb.c @@ -0,0 +1,399 @@ +#include "honeywell_wdb.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolHoneywellWDB" + +/* + * + * https://github.com/klohner/honeywell-wireless-doorbell + * + */ + +static const SubGhzBlockConst subghz_protocol_honeywell_wdb_const = { + .te_short = 160, + .te_long = 320, + .te_delta = 60, + .min_count_bit_for_found = 48, +}; + +struct SubGhzProtocolDecoderHoneywell_WDB { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + const char* device_type; + const char* alert; + uint8_t secret_knock; + uint8_t relay; + uint8_t lowbat; +}; + +struct SubGhzProtocolEncoderHoneywell_WDB { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Honeywell_WDBDecoderStepReset = 0, + Honeywell_WDBDecoderStepFoundStartBit, + Honeywell_WDBDecoderStepSaveDuration, + Honeywell_WDBDecoderStepCheckDuration, +} Honeywell_WDBDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_honeywell_wdb_decoder = { + .alloc = subghz_protocol_decoder_honeywell_wdb_alloc, + .free = subghz_protocol_decoder_honeywell_wdb_free, + + .feed = subghz_protocol_decoder_honeywell_wdb_feed, + .reset = subghz_protocol_decoder_honeywell_wdb_reset, + + .get_hash_data = subghz_protocol_decoder_honeywell_wdb_get_hash_data, + .serialize = subghz_protocol_decoder_honeywell_wdb_serialize, + .deserialize = subghz_protocol_decoder_honeywell_wdb_deserialize, + .get_string = subghz_protocol_decoder_honeywell_wdb_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_honeywell_wdb_encoder = { + .alloc = subghz_protocol_encoder_honeywell_wdb_alloc, + .free = subghz_protocol_encoder_honeywell_wdb_free, + + .deserialize = subghz_protocol_encoder_honeywell_wdb_deserialize, + .stop = subghz_protocol_encoder_honeywell_wdb_stop, + .yield = subghz_protocol_encoder_honeywell_wdb_yield, +}; + +const SubGhzProtocol subghz_protocol_honeywell_wdb = { + .name = SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_honeywell_wdb_decoder, + .encoder = &subghz_protocol_honeywell_wdb_encoder, +}; + +void* subghz_protocol_encoder_honeywell_wdb_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoneywell_WDB* instance = + malloc(sizeof(SubGhzProtocolEncoderHoneywell_WDB)); + + instance->base.protocol = &subghz_protocol_honeywell_wdb; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_honeywell_wdb_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoneywell_WDB* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance + * @return true On success + */ +static bool subghz_protocol_encoder_honeywell_wdb_get_upload( + SubGhzProtocolEncoderHoneywell_WDB* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short * 3); + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_honeywell_wdb_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_honeywell_wdb_const.te_long); + } + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_honeywell_wdb_const.te_short * 3); + return true; +} + +bool subghz_protocol_encoder_honeywell_wdb_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoneywell_WDB* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_honeywell_wdb_stop(void* context) { + SubGhzProtocolEncoderHoneywell_WDB* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_honeywell_wdb_yield(void* context) { + SubGhzProtocolEncoderHoneywell_WDB* 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_honeywell_wdb_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoneywell_WDB* instance = + malloc(sizeof(SubGhzProtocolDecoderHoneywell_WDB)); + instance->base.protocol = &subghz_protocol_honeywell_wdb; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_honeywell_wdb_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + free(instance); +} + +void subghz_protocol_decoder_honeywell_wdb_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + instance->decoder.parser_step = Honeywell_WDBDecoderStepReset; +} + +void subghz_protocol_decoder_honeywell_wdb_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + switch(instance->decoder.parser_step) { + case Honeywell_WDBDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_short * 3) < + subghz_protocol_honeywell_wdb_const.te_delta)) { + //Found header Honeywell_WDB + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0; + instance->decoder.parser_step = Honeywell_WDBDecoderStepSaveDuration; + } + break; + case Honeywell_WDBDecoderStepSaveDuration: + if(level) { //save interval + if(DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_short * 3) < + subghz_protocol_honeywell_wdb_const.te_delta) { + if((instance->decoder.decode_count_bit == + subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) && + ((instance->decoder.decode_data & 0x01) == + subghz_protocol_blocks_get_parity( + instance->decoder.decode_data >> 1, + subghz_protocol_honeywell_wdb_const.min_count_bit_for_found - 1))) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.parser_step = Honeywell_WDBDecoderStepReset; + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = Honeywell_WDBDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Honeywell_WDBDecoderStepReset; + } + break; + case Honeywell_WDBDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_honeywell_wdb_const.te_short) < + subghz_protocol_honeywell_wdb_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_long) < + subghz_protocol_honeywell_wdb_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Honeywell_WDBDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_honeywell_wdb_const.te_long) < + subghz_protocol_honeywell_wdb_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_honeywell_wdb_const.te_short) < + subghz_protocol_honeywell_wdb_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Honeywell_WDBDecoderStepSaveDuration; + } else + instance->decoder.parser_step = Honeywell_WDBDecoderStepReset; + } else { + instance->decoder.parser_step = Honeywell_WDBDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzProtocolDecoderHoneywell_WDB* instance + */ +static void subghz_protocol_honeywell_wdb_check_remote_controller( + SubGhzProtocolDecoderHoneywell_WDB* instance) { + /* + * + * Frame bits used in Honeywell RCWL300A, RCWL330A, Series 3, 5, 9 and all Decor Series Wireless Chimes + * 0000 0000 1111 1111 2222 2222 3333 3333 4444 4444 5555 5555 + * 7654 3210 7654 3210 7654 3210 7654 3210 7654 3210 7654 3210 + * XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XX.. XXX. .... KEY DATA (any change and receiver doesn't seem to recognize signal) + * XXXX XXXX XXXX XXXX XXXX .... .... .... .... .... .... .... KEY ID (different for each transmitter) + * .... .... .... .... .... 0000 00.. 0000 0000 00.. 000. .... KEY UNKNOWN 0 (always 0 in devices I've tested) + * .... .... .... .... .... .... ..XX .... .... .... .... .... DEVICE TYPE (10 = doorbell, 01 = PIR Motion sensor) + * .... .... .... .... .... .... .... .... .... ..XX ...X XXX. FLAG DATA (may be modified for possible effects on receiver) + * .... .... .... .... .... .... .... .... .... ..XX .... .... ALERT (00 = normal, 01 or 10 = right-left halo light pattern, 11 = full volume alarm) + * .... .... .... .... .... .... .... .... .... .... ...X .... SECRET KNOCK (0 = default, 1 if doorbell is pressed 3x rapidly) + * .... .... .... .... .... .... .... .... .... .... .... X... RELAY (1 if signal is a retransmission of a received transmission, only some models) + * .... .... .... .... .... .... .... .... .... .... .... .X.. FLAG UNKNOWN (0 = default, but 1 is accepted and I don't observe any effects) + * .... .... .... .... .... .... .... .... .... .... .... ..X. LOWBAT (1 if battery is low, receiver gives low battery alert) + * .... .... .... .... .... .... .... .... .... .... .... ...X PARITY (LSB of count of set bits in previous 47 bits) + * + */ + + instance->generic.serial = (instance->generic.data >> 28) & 0xFFFFF; + switch((instance->generic.data >> 20) & 0x3) { + case 0x02: + instance->device_type = "Doorbell"; + break; + case 0x01: + instance->device_type = "PIR-Motion"; + break; + default: + instance->device_type = "Unknown"; + break; + } + + switch((instance->generic.data >> 16) & 0x3) { + case 0x00: + instance->alert = "Normal"; + break; + case 0x01: + case 0x02: + instance->alert = "High"; + break; + case 0x03: + instance->alert = "Full"; + break; + default: + instance->alert = "Unknown"; + break; + } + + instance->secret_knock = (uint8_t)((instance->generic.data >> 4) & 0x1); + instance->relay = (uint8_t)((instance->generic.data >> 3) & 0x1); + instance->lowbat = (uint8_t)((instance->generic.data >> 1) & 0x1); +} + +uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_honeywell_wdb_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_honeywell_wdb_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell_WDB* instance = context; + subghz_protocol_honeywell_wdb_check_remote_controller(instance); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%05lX\r\n" + "DT:%s Al:%s\r\n" + "SK:%01X R:%01X LBat:%01X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF), + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + instance->device_type, + instance->alert, + instance->secret_knock, + instance->relay, + instance->lowbat); +} diff --git a/applications/main/subghz/protocols/honeywell_wdb.h b/applications/main/subghz/protocols/honeywell_wdb.h new file mode 100644 index 000000000..828631837 --- /dev/null +++ b/applications/main/subghz/protocols/honeywell_wdb.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME "Honeywell" + +typedef struct SubGhzProtocolDecoderHoneywell_WDB SubGhzProtocolDecoderHoneywell_WDB; +typedef struct SubGhzProtocolEncoderHoneywell_WDB SubGhzProtocolEncoderHoneywell_WDB; + +extern const SubGhzProtocolDecoder subghz_protocol_honeywell_wdb_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_honeywell_wdb_encoder; +extern const SubGhzProtocol subghz_protocol_honeywell_wdb; + +/** + * Allocate SubGhzProtocolEncoderHoneywell_WDB. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHoneywell_WDB* pointer to a SubGhzProtocolEncoderHoneywell_WDB instance + */ +void* subghz_protocol_encoder_honeywell_wdb_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHoneywell_WDB. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance + */ +void subghz_protocol_encoder_honeywell_wdb_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_honeywell_wdb_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance + */ +void subghz_protocol_encoder_honeywell_wdb_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_honeywell_wdb_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHoneywell_WDB. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHoneywell_WDB* pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + */ +void* subghz_protocol_decoder_honeywell_wdb_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHoneywell_WDB. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + */ +void subghz_protocol_decoder_honeywell_wdb_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHoneywell_WDB. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + */ +void subghz_protocol_decoder_honeywell_wdb_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_honeywell_wdb_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHoneywell_WDB. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_honeywell_wdb_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHoneywell_WDB. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_honeywell_wdb_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance + * @param output Resulting text + */ +void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/hormann.c b/applications/main/subghz/protocols/hormann.c new file mode 100644 index 000000000..67b8cdfca --- /dev/null +++ b/applications/main/subghz/protocols/hormann.c @@ -0,0 +1,341 @@ +#include "hormann.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolHormannHSM" + +#define HORMANN_HSM_PATTERN 0xFF000000003 + +static const SubGhzBlockConst subghz_protocol_hormann_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 200, + .min_count_bit_for_found = 44, +}; + +struct SubGhzProtocolDecoderHormann { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderHormann { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + HormannDecoderStepReset = 0, + HormannDecoderStepFoundStartHeader, + HormannDecoderStepFoundHeader, + HormannDecoderStepFoundStartBit, + HormannDecoderStepSaveDuration, + HormannDecoderStepCheckDuration, +} HormannDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_hormann_decoder = { + .alloc = subghz_protocol_decoder_hormann_alloc, + .free = subghz_protocol_decoder_hormann_free, + + .feed = subghz_protocol_decoder_hormann_feed, + .reset = subghz_protocol_decoder_hormann_reset, + + .get_hash_data = subghz_protocol_decoder_hormann_get_hash_data, + .serialize = subghz_protocol_decoder_hormann_serialize, + .deserialize = subghz_protocol_decoder_hormann_deserialize, + .get_string = subghz_protocol_decoder_hormann_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_hormann_encoder = { + .alloc = subghz_protocol_encoder_hormann_alloc, + .free = subghz_protocol_encoder_hormann_free, + + .deserialize = subghz_protocol_encoder_hormann_deserialize, + .stop = subghz_protocol_encoder_hormann_stop, + .yield = subghz_protocol_encoder_hormann_yield, +}; + +const SubGhzProtocol subghz_protocol_hormann = { + .name = SUBGHZ_PROTOCOL_HORMANN_HSM_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_hormann_decoder, + .encoder = &subghz_protocol_hormann_encoder, +}; + +void* subghz_protocol_encoder_hormann_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHormann* instance = malloc(sizeof(SubGhzProtocolEncoderHormann)); + + instance->base.protocol = &subghz_protocol_hormann; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 2048; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_hormann_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHormann* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHormann instance + * @return true On success + */ +static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHormann* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2 + 2) * 20 + 1; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + instance->encoder.repeat = 10; //original remote does 10 repeats + + for(size_t repeat = 0; repeat < 20; repeat++) { + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 24); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short); + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_long); + } + } + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 24); + return true; +} + +bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHormann* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_hormann_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_hormann_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_hormann_stop(void* context) { + SubGhzProtocolEncoderHormann* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_hormann_yield(void* context) { + SubGhzProtocolEncoderHormann* 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_hormann_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHormann* instance = malloc(sizeof(SubGhzProtocolDecoderHormann)); + instance->base.protocol = &subghz_protocol_hormann; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_hormann_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + free(instance); +} + +static bool subghz_protocol_decoder_hormann_check_pattern(SubGhzProtocolDecoderHormann* instance) { + return (instance->decoder.decode_data & HORMANN_HSM_PATTERN) == HORMANN_HSM_PATTERN; +} + +void subghz_protocol_decoder_hormann_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + instance->decoder.parser_step = HormannDecoderStepReset; +} + +void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + + switch(instance->decoder.parser_step) { + case HormannDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 24) < + subghz_protocol_hormann_const.te_delta * 24)) { + instance->decoder.parser_step = HormannDecoderStepFoundStartBit; + } + break; + case HormannDecoderStepFoundStartBit: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short) < + subghz_protocol_hormann_const.te_delta)) { + instance->decoder.parser_step = HormannDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + case HormannDecoderStepSaveDuration: + if(level) { //save interval + if(duration >= (subghz_protocol_hormann_const.te_short * 5) && + subghz_protocol_decoder_hormann_check_pattern(instance)) { + instance->decoder.parser_step = HormannDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_hormann_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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = HormannDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + case HormannDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hormann_const.te_short) < + subghz_protocol_hormann_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_long) < + subghz_protocol_hormann_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = HormannDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hormann_const.te_long) < + subghz_protocol_hormann_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short) < + subghz_protocol_hormann_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = HormannDecoderStepSaveDuration; + } else + instance->decoder.parser_step = HormannDecoderStepReset; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_hormann_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = (instance->data >> 4) & 0xF; +} + +uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_hormann_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_hormann_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_hormann_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + subghz_protocol_hormann_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s\r\n" + "%dbit\r\n" + "Key:0x%03lX%08lX\r\n" + "Btn:0x%01X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.btn); +} diff --git a/applications/main/subghz/protocols/hormann.h b/applications/main/subghz/protocols/hormann.h new file mode 100644 index 000000000..857a50041 --- /dev/null +++ b/applications/main/subghz/protocols/hormann.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HORMANN_HSM_NAME "Hormann HSM" + +typedef struct SubGhzProtocolDecoderHormann SubGhzProtocolDecoderHormann; +typedef struct SubGhzProtocolEncoderHormann SubGhzProtocolEncoderHormann; + +extern const SubGhzProtocolDecoder subghz_protocol_hormann_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_hormann_encoder; +extern const SubGhzProtocol subghz_protocol_hormann; + +/** + * Allocate SubGhzProtocolEncoderHormann. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHormann* pointer to a SubGhzProtocolEncoderHormann instance + */ +void* subghz_protocol_encoder_hormann_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHormann. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + */ +void subghz_protocol_encoder_hormann_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + */ +void subghz_protocol_encoder_hormann_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_hormann_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHormann. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHormann* pointer to a SubGhzProtocolDecoderHormann instance + */ +void* subghz_protocol_decoder_hormann_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + */ +void subghz_protocol_decoder_hormann_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + */ +void subghz_protocol_decoder_hormann_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_hormann_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param output Resulting text + */ +void subghz_protocol_decoder_hormann_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/ido.c b/applications/main/subghz/protocols/ido.c new file mode 100644 index 000000000..dff9defc0 --- /dev/null +++ b/applications/main/subghz/protocols/ido.c @@ -0,0 +1,234 @@ +#include "ido.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocol_iDo_117/111" + +static const SubGhzBlockConst subghz_protocol_ido_const = { + .te_short = 450, + .te_long = 1450, + .te_delta = 150, + .min_count_bit_for_found = 48, +}; + +struct SubGhzProtocolDecoderIDo { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderIDo { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + IDoDecoderStepReset = 0, + IDoDecoderStepFoundPreambula, + IDoDecoderStepSaveDuration, + IDoDecoderStepCheckDuration, +} IDoDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_ido_decoder = { + .alloc = subghz_protocol_decoder_ido_alloc, + .free = subghz_protocol_decoder_ido_free, + + .feed = subghz_protocol_decoder_ido_feed, + .reset = subghz_protocol_decoder_ido_reset, + + .get_hash_data = subghz_protocol_decoder_ido_get_hash_data, + .deserialize = subghz_protocol_decoder_ido_deserialize, + .serialize = subghz_protocol_decoder_ido_serialize, + .get_string = subghz_protocol_decoder_ido_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_ido_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_ido = { + .name = SUBGHZ_PROTOCOL_IDO_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Save, + + .decoder = &subghz_protocol_ido_decoder, + .encoder = &subghz_protocol_ido_encoder, +}; + +void* subghz_protocol_decoder_ido_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderIDo* instance = malloc(sizeof(SubGhzProtocolDecoderIDo)); + instance->base.protocol = &subghz_protocol_ido; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_ido_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + free(instance); +} + +void subghz_protocol_decoder_ido_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + instance->decoder.parser_step = IDoDecoderStepReset; +} + +void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + + switch(instance->decoder.parser_step) { + case IDoDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_ido_const.te_short * 10) < + subghz_protocol_ido_const.te_delta * 5)) { + instance->decoder.parser_step = IDoDecoderStepFoundPreambula; + } + break; + case IDoDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_ido_const.te_short * 10) < + subghz_protocol_ido_const.te_delta * 5)) { + //Found Preambula + instance->decoder.parser_step = IDoDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + break; + case IDoDecoderStepSaveDuration: + if(level) { + if(duration >= ((uint32_t)subghz_protocol_ido_const.te_short * 5 + + subghz_protocol_ido_const.te_delta)) { + instance->decoder.parser_step = IDoDecoderStepFoundPreambula; + if(instance->decoder.decode_count_bit >= + subghz_protocol_ido_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = IDoDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + break; + case IDoDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ido_const.te_short) < + subghz_protocol_ido_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ido_const.te_long) < + subghz_protocol_ido_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = IDoDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ido_const.te_short) < + subghz_protocol_ido_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_ido_const.te_short) < + subghz_protocol_ido_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = IDoDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_ido_check_remote_controller(SubGhzBlockGeneric* instance) { + uint64_t code_found_reverse = + subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t code_fix = code_found_reverse & 0xFFFFFF; + + instance->serial = code_fix & 0xFFFFF; + instance->btn = (code_fix >> 20) & 0x0F; +} + +uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_ido_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != subghz_protocol_ido_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + + subghz_protocol_ido_check_remote_controller(&instance->generic); + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_fix = code_found_reverse & 0xFFFFFF; + uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFFF; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Fix:%06lX \r\n" + "Hop:%06lX \r\n" + "Sn:%05lX Btn:%X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + code_fix, + code_hop, + instance->generic.serial, + instance->generic.btn); +} diff --git a/applications/main/subghz/protocols/ido.h b/applications/main/subghz/protocols/ido.h new file mode 100644 index 000000000..634f6ff89 --- /dev/null +++ b/applications/main/subghz/protocols/ido.h @@ -0,0 +1,73 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_IDO_NAME "iDo 117/111" + +typedef struct SubGhzProtocolDecoderIDo SubGhzProtocolDecoderIDo; +typedef struct SubGhzProtocolEncoderIDo SubGhzProtocolEncoderIDo; + +extern const SubGhzProtocolDecoder subghz_protocol_ido_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_ido_encoder; +extern const SubGhzProtocol subghz_protocol_ido; + +/** + * Allocate SubGhzProtocolDecoderIDo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderIDo* pointer to a SubGhzProtocolDecoderIDo instance + */ +void* subghz_protocol_decoder_ido_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + */ +void subghz_protocol_decoder_ido_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + */ +void subghz_protocol_decoder_ido_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_ido_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param output Resulting text + */ +void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/intertechno_v3.c b/applications/main/subghz/protocols/intertechno_v3.c new file mode 100644 index 000000000..2c4e514cc --- /dev/null +++ b/applications/main/subghz/protocols/intertechno_v3.c @@ -0,0 +1,472 @@ +#include "intertechno_v3.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolIntertechnoV3" + +#define CH_PATTERN "%c%c%c%c" +#define CNT_TO_CH(ch) \ + (ch & 0x8 ? '1' : '0'), (ch & 0x4 ? '1' : '0'), (ch & 0x2 ? '1' : '0'), (ch & 0x1 ? '1' : '0') + +#define INTERTECHNO_V3_DIMMING_COUNT_BIT 36 + +static const SubGhzBlockConst subghz_protocol_intertechno_v3_const = { + .te_short = 275, + .te_long = 1375, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct SubGhzProtocolDecoderIntertechno_V3 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderIntertechno_V3 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + IntertechnoV3DecoderStepReset = 0, + IntertechnoV3DecoderStepStartSync, + IntertechnoV3DecoderStepFoundSync, + IntertechnoV3DecoderStepStartDuration, + IntertechnoV3DecoderStepSaveDuration, + IntertechnoV3DecoderStepCheckDuration, + IntertechnoV3DecoderStepEndDuration, +} IntertechnoV3DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_intertechno_v3_decoder = { + .alloc = subghz_protocol_decoder_intertechno_v3_alloc, + .free = subghz_protocol_decoder_intertechno_v3_free, + + .feed = subghz_protocol_decoder_intertechno_v3_feed, + .reset = subghz_protocol_decoder_intertechno_v3_reset, + + .get_hash_data = subghz_protocol_decoder_intertechno_v3_get_hash_data, + .serialize = subghz_protocol_decoder_intertechno_v3_serialize, + .deserialize = subghz_protocol_decoder_intertechno_v3_deserialize, + .get_string = subghz_protocol_decoder_intertechno_v3_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_intertechno_v3_encoder = { + .alloc = subghz_protocol_encoder_intertechno_v3_alloc, + .free = subghz_protocol_encoder_intertechno_v3_free, + + .deserialize = subghz_protocol_encoder_intertechno_v3_deserialize, + .stop = subghz_protocol_encoder_intertechno_v3_stop, + .yield = subghz_protocol_encoder_intertechno_v3_yield, +}; + +const SubGhzProtocol subghz_protocol_intertechno_v3 = { + .name = SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_intertechno_v3_decoder, + .encoder = &subghz_protocol_intertechno_v3_encoder, +}; + +void* subghz_protocol_encoder_intertechno_v3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderIntertechno_V3* instance = + malloc(sizeof(SubGhzProtocolEncoderIntertechno_V3)); + + instance->base.protocol = &subghz_protocol_intertechno_v3; + 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_intertechno_v3_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderIntertechno_V3* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + * @return true On success + */ +static bool subghz_protocol_encoder_intertechno_v3_get_upload( + SubGhzProtocolEncoderIntertechno_V3* instance) { + furi_assert(instance); + size_t index = 0; + + //Send header + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short * 38); + //Send sync + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short * 10); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if((instance->generic.data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) && (i == 9)) { + //send bit dimm + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + } else if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_long); + } + } + instance->encoder.size_upload = index; + return true; +} + +bool subghz_protocol_encoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderIntertechno_V3* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if((instance->generic.data_count_bit != + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) && + (instance->generic.data_count_bit != INTERTECHNO_V3_DIMMING_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_intertechno_v3_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_intertechno_v3_stop(void* context) { + SubGhzProtocolEncoderIntertechno_V3* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) { + SubGhzProtocolEncoderIntertechno_V3* 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_intertechno_v3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderIntertechno_V3* instance = + malloc(sizeof(SubGhzProtocolDecoderIntertechno_V3)); + instance->base.protocol = &subghz_protocol_intertechno_v3; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_intertechno_v3_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + free(instance); +} + +void subghz_protocol_decoder_intertechno_v3_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; +} + +void subghz_protocol_decoder_intertechno_v3_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + switch(instance->decoder.parser_step) { + case IntertechnoV3DecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short * 37) < + subghz_protocol_intertechno_v3_const.te_delta * 15)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartSync; + } + break; + case IntertechnoV3DecoderStepStartSync: + if(level && (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepFoundSync; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepFoundSync: + if(!level && (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short * 10) < + subghz_protocol_intertechno_v3_const.te_delta * 3)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepStartDuration: + if(level && (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_intertechno_v3_const.te_short * 11)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartSync; + if((instance->decoder.decode_count_bit == + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT)) { + 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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = IntertechnoV3DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + case IntertechnoV3DecoderStepCheckDuration: + if(level) { + //Add 0 bit + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = IntertechnoV3DecoderStepEndDuration; + } else if( + //Add 1 bit + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_intertechno_v3_const.te_long) < + subghz_protocol_intertechno_v3_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = IntertechnoV3DecoderStepEndDuration; + + } else if( + //Add dimm_state + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta) && + (instance->decoder.decode_count_bit == 27)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = IntertechnoV3DecoderStepEndDuration; + + } else + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepEndDuration: + if(!level && ((DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_long) < + subghz_protocol_intertechno_v3_const.te_delta * 2))) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartDuration; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_intertechno_v3_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * A frame is either 32 or 36 bits: + * + * _ + * start bit: | |__________ (T,10T) + * _ _ + * '0': | |_| |_____ (T,T,T,5T) + * _ _ + * '1': | |_____| |_ (T,5T,T,T) + * _ _ + * dimm: | |_| |_ (T,T,T,T) + * + * _ + * stop bit: | |____...____ (T,38T) + * + * if frame 32 bits + * SSSSSSSSSSSSSSSSSSSSSSSSSS all_ch on/off ~ch + * Key:0x3F86C59F => 00111111100001101100010110 0 1 1111 + * + * if frame 36 bits + * SSSSSSSSSSSSSSSSSSSSSSSSSS all_ch dimm ~ch dimm_level + * Key:0x42D2E8856 => 01000010110100101110100010 0 X 0101 0110 + * + */ + + if(instance->data_count_bit == subghz_protocol_intertechno_v3_const.min_count_bit_for_found) { + instance->serial = (instance->data >> 6) & 0x3FFFFFF; + if((instance->data >> 5) & 0x1) { + instance->cnt = 1 << 5; + } else { + instance->cnt = (~instance->data & 0xF); + } + instance->btn = (instance->data >> 4) & 0x1; + } else if(instance->data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) { + instance->serial = (instance->data >> 10) & 0x3FFFFFF; + if((instance->data >> 9) & 0x1) { + instance->cnt = 1 << 5; + } else { + instance->cnt = (~(instance->data >> 4) & 0xF); + } + instance->btn = (instance->data) & 0xF; + } else { + instance->serial = 0; + instance->cnt = 0; + instance->btn = 0; + } +} + +uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_intertechno_v3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if((instance->generic.data_count_bit != + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) && + (instance->generic.data_count_bit != INTERTECHNO_V3_DIMMING_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + + subghz_protocol_intertechno_v3_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%.11s %db\r\n" + "Key:0x%08llX\r\n" + "Sn:%07lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial); + + if(instance->generic.data_count_bit == + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) { + if(instance->generic.cnt >> 5) { + furi_string_cat_printf( + output, "Ch: All Btn:%s\r\n", (instance->generic.btn ? "On" : "Off")); + } else { + furi_string_cat_printf( + output, + "Ch:" CH_PATTERN " Btn:%s\r\n", + CNT_TO_CH(instance->generic.cnt), + (instance->generic.btn ? "On" : "Off")); + } + } else if(instance->generic.data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) { + furi_string_cat_printf( + output, + "Ch:" CH_PATTERN " Dimm:%d%%\r\n", + CNT_TO_CH(instance->generic.cnt), + (int)(6.67 * (float)instance->generic.btn)); + } +} diff --git a/applications/main/subghz/protocols/intertechno_v3.h b/applications/main/subghz/protocols/intertechno_v3.h new file mode 100644 index 000000000..ffee14b04 --- /dev/null +++ b/applications/main/subghz/protocols/intertechno_v3.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME "Intertechno_V3" + +typedef struct SubGhzProtocolDecoderIntertechno_V3 SubGhzProtocolDecoderIntertechno_V3; +typedef struct SubGhzProtocolEncoderIntertechno_V3 SubGhzProtocolEncoderIntertechno_V3; + +extern const SubGhzProtocolDecoder subghz_protocol_intertechno_v3_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_intertechno_v3_encoder; +extern const SubGhzProtocol subghz_protocol_intertechno_v3; + +/** + * Allocate SubGhzProtocolEncoderIntertechno_V3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderIntertechno_V3* pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + */ +void* subghz_protocol_encoder_intertechno_v3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + */ +void subghz_protocol_encoder_intertechno_v3_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + */ +void subghz_protocol_encoder_intertechno_v3_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderIntertechno_V3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderIntertechno_V3* pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + */ +void* subghz_protocol_decoder_intertechno_v3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + */ +void subghz_protocol_decoder_intertechno_v3_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + */ +void subghz_protocol_decoder_intertechno_v3_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_intertechno_v3_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_intertechno_v3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/keeloq.c b/applications/main/subghz/protocols/keeloq.c new file mode 100644 index 000000000..a0970de4d --- /dev/null +++ b/applications/main/subghz/protocols/keeloq.c @@ -0,0 +1,1115 @@ +#include "keeloq.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolKeeloq" + +static const SubGhzBlockConst subghz_protocol_keeloq_const = { + .te_short = 400, + .te_long = 800, + .te_delta = 140, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderKeeloq { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; + const char* manufacture_name; + + FuriString* manufacture_from_file; +}; + +struct SubGhzProtocolEncoderKeeloq { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + SubGhzKeystore* keystore; + const char* manufacture_name; + + FuriString* manufacture_from_file; +}; + +typedef enum { + KeeloqDecoderStepReset = 0, + KeeloqDecoderStepCheckPreambula, + KeeloqDecoderStepSaveDuration, + KeeloqDecoderStepCheckDuration, +} KeeloqDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_keeloq_decoder = { + .alloc = subghz_protocol_decoder_keeloq_alloc, + .free = subghz_protocol_decoder_keeloq_free, + + .feed = subghz_protocol_decoder_keeloq_feed, + .reset = subghz_protocol_decoder_keeloq_reset, + + .get_hash_data = subghz_protocol_decoder_keeloq_get_hash_data, + .serialize = subghz_protocol_decoder_keeloq_serialize, + .deserialize = subghz_protocol_decoder_keeloq_deserialize, + .get_string = subghz_protocol_decoder_keeloq_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_keeloq_encoder = { + .alloc = subghz_protocol_encoder_keeloq_alloc, + .free = subghz_protocol_encoder_keeloq_free, + + .deserialize = subghz_protocol_encoder_keeloq_deserialize, + .stop = subghz_protocol_encoder_keeloq_stop, + .yield = subghz_protocol_encoder_keeloq_yield, +}; + +const SubGhzProtocol subghz_protocol_keeloq = { + .name = SUBGHZ_PROTOCOL_KEELOQ_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_keeloq_decoder, + .encoder = &subghz_protocol_keeloq_encoder, +}; + +static const char* mfname; +static int kl_type; + +void keeloq_reset_mfname() { + mfname = ""; +} + +void keeloq_reset_kl_type() { + kl_type = 0; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ +static void subghz_protocol_keeloq_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name); + +void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderKeeloq* instance = malloc(sizeof(SubGhzProtocolEncoderKeeloq)); + + instance->base.protocol = &subghz_protocol_keeloq; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + instance->encoder.repeat = 100; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + + instance->manufacture_from_file = furi_string_alloc(); + + return instance; +} + +void subghz_protocol_encoder_keeloq_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + furi_string_free(instance->manufacture_from_file); + free(instance->encoder.upload); + free(instance); +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq* instance + * @param btn Button number, 4 bit + */ +static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial; + uint32_t decrypt = (uint32_t)btn << 28 | + (instance->generic.serial & 0x3FF) + << 16 | //ToDo in some protocols the discriminator is 0 + instance->generic.cnt; + uint32_t hop = 0; + uint64_t man = 0; + uint64_t code_found_reverse; + int res = 0; + if(instance->manufacture_name == 0x0) { + instance->manufacture_name = ""; + } + + // DTM Neo uses 12bit -> simple learning -- FAAC_RC,XT , Mutanco_Mutancode -> 12bit normal learning + if((strcmp(instance->manufacture_name, "DTM_Neo") == 0) || + (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || + (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0)) { + decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; + } + + // Nice Smilo, MHouse, JCM, Normstahl -> 8bit serial - simple learning + if((strcmp(instance->manufacture_name, "NICE_Smilo") == 0) || + (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0) || + (strcmp(instance->manufacture_name, "JCM_Tech") == 0) || + (strcmp(instance->manufacture_name, "Normstahl") == 0)) { + decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; + } + + if(strcmp(instance->manufacture_name, "Unknown") == 0) { + code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + hop = code_found_reverse & 0x00000000ffffffff; + } else if(strcmp(instance->manufacture_name, "AN-Motors") == 0) { + hop = (instance->generic.cnt & 0xFF) << 24 | (instance->generic.cnt & 0xFF) << 16 | + (instance->generic.btn & 0xF) << 12 | 0x404; + } else if(strcmp(instance->manufacture_name, "HCS101") == 0) { + hop = instance->generic.cnt << 16 | (instance->generic.btn & 0xF) << 12 | 0x000; + } else { + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning + hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); + break; + case KEELOQ_LEARNING_NORMAL: + //Simple Learning + man = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_SECURE: + //Secure Learning + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: + //Magic XOR type-1 Learning + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + instance->generic.serial, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: + //Magic Serial Type 1 learning + man = subghz_protocol_keeloq_common_magic_serial_type1_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_UNKNOWN: + if(kl_type == 1) { + hop = + subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); + } + if(kl_type == 2) { + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + if(kl_type == 3) { + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + if(kl_type == 4) { + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + instance->generic.serial, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + break; + } + break; + } + } + } + if(hop) { + uint64_t yek = (uint64_t)fix << 32 | hop; + instance->generic.data = + subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); + } + return true; +} + +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->manufacture_name = manufacture_name; + instance->generic.data_count_bit = 64; + bool res = subghz_protocol_keeloq_gen_data(instance, btn); + if(res) { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +bool subghz_protocol_keeloq_bft_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + instance->generic.serial = serial; + instance->generic.btn = btn; + instance->generic.cnt = cnt; + instance->generic.seed = seed; + instance->manufacture_name = manufacture_name; + instance->generic.data_count_bit = 64; + // roguuemaster don't steal.!!!! + bool res = subghz_protocol_keeloq_gen_data(instance, btn); + if(res) { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ +static bool + subghz_protocol_encoder_keeloq_get_upload(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { + furi_assert(instance); + + //gen new key + if(subghz_protocol_keeloq_gen_data(instance, btn)) { + //ToDo if you need to add a callback to automatically update the data on the display + } else { + return false; + } + + size_t index = 0; + size_t size_upload = 11 * 2 + 2 + (instance->generic.data_count_bit * 2) + 4; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 11; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short * 10); + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short); + } + } + // +send 2 status bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_long); + // send end + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short * 40); + + return true; +} + +bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_keeloq_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + + uint8_t seed_data[sizeof(uint32_t)] = {0}; + for(size_t i = 0; i < sizeof(uint32_t); i++) { + seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF; + } + if(!flipper_format_read_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) { + FURI_LOG_D(TAG, "ENCODER: Missing Seed"); + } + instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | + seed_data[3]; + + // Read manufacturer from file + if(flipper_format_read_string( + flipper_format, "Manufacture", instance->manufacture_from_file)) { + instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file); + mfname = furi_string_get_cstr(instance->manufacture_from_file); + } else { + FURI_LOG_D(TAG, "ENCODER: Missing Manufacture"); + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + subghz_protocol_keeloq_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn)) break; + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_keeloq_stop(void* context) { + SubGhzProtocolEncoderKeeloq* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) { + SubGhzProtocolEncoderKeeloq* 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_keeloq_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderKeeloq* instance = malloc(sizeof(SubGhzProtocolDecoderKeeloq)); + instance->base.protocol = &subghz_protocol_keeloq; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + instance->manufacture_from_file = furi_string_alloc(); + + return instance; +} + +void subghz_protocol_decoder_keeloq_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + furi_string_free(instance->manufacture_from_file); + + free(instance); +} + +void subghz_protocol_decoder_keeloq_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + instance->decoder.parser_step = KeeloqDecoderStepReset; + mfname = ""; + kl_type = 0; +} + +void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + + switch(instance->decoder.parser_step) { + case KeeloqDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta) { + instance->decoder.parser_step = KeeloqDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case KeeloqDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta)) { + instance->decoder.parser_step = KeeloqDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short * 10) < + subghz_protocol_keeloq_const.te_delta * 10)) { + // Found header + instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = KeeloqDecoderStepReset; + instance->header_count = 0; + } + break; + case KeeloqDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KeeloqDecoderStepCheckDuration; + } + break; + case KeeloqDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_keeloq_const.te_short * 2 + + subghz_protocol_keeloq_const.te_delta)) { + // Found end TX + instance->decoder.parser_step = KeeloqDecoderStepReset; + if((instance->decoder.decode_count_bit >= + subghz_protocol_keeloq_const.min_count_bit_for_found) && + (instance->decoder.decode_count_bit <= + subghz_protocol_keeloq_const.min_count_bit_for_found + 2)) { + if(instance->generic.data != instance->decoder.decode_data) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + subghz_protocol_keeloq_const.min_count_bit_for_found; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_long) < + subghz_protocol_keeloq_const.te_delta * 2)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_keeloq_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.decode_count_bit++; + } + instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keeloq_const.te_long) < + subghz_protocol_keeloq_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_keeloq_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + instance->decoder.decode_count_bit++; + } + instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KeeloqDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = KeeloqDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Validation of decrypt data. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param decrypt Decrypd data + * @param btn Button number, 4 bit + * @param end_serial decrement the last 10 bits of the serial number + * @return true On success + */ +static inline bool subghz_protocol_keeloq_check_decrypt( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn, + uint32_t end_serial) { + furi_assert(instance); + if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) || + ((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) { + instance->cnt = decrypt & 0x0000FFFF; + return true; + } + return false; +} + +/** + * Checking the accepted code against the database manafacture key + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param fix Fix part of the parcel + * @param hop Hop encrypted part of the parcel + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + * @return true on successful search + */ +static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( + SubGhzBlockGeneric* instance, + uint32_t fix, + uint32_t hop, + SubGhzKeystore* keystore, + const char** manufacture_name) { + // protocol HCS300 uses 10 bits in discriminator, HCS200 uses 8 bits, for backward compatibility, we are looking for the 8-bit pattern + // HCS300 -> uint16_t end_serial = (uint16_t)(fix & 0x3FF); + // HCS200 -> uint16_t end_serial = (uint16_t)(fix & 0xFF); + + uint16_t end_serial = (uint16_t)(fix & 0xFF); + uint8_t btn = (uint8_t)(fix >> 28); + uint32_t decrypt = 0; + uint64_t man; + int res = 0; + if(mfname == 0x0) { + mfname = ""; + } + + if(strcmp(mfname, "") == 0) { + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_SECURE: + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: + man = subghz_protocol_keeloq_common_magic_serial_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2: + man = subghz_protocol_keeloq_common_magic_serial_type2_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3: + man = subghz_protocol_keeloq_common_magic_serial_type3_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + + //########################### + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + + // Secure Learning + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 3; + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_secure_learning(fix, instance->seed, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 3; + return 1; + } + + // Magic xor type1 learning + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 4; + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_magic_xor_type1_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 4; + return 1; + } + + break; + } + } + } else if(strcmp(mfname, "Unknown") == 0) { + return 1; + } else { + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), mfname); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_SECURE: + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: + man = subghz_protocol_keeloq_common_magic_serial_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + //########################### + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + + // Secure Learning + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 3; + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 3; + return 1; + } + + // Magic xor type1 learning + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 4; + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_magic_xor_type1_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 4; + return 1; + } + + break; + } + } + } + } + + *manufacture_name = "Unknown"; + mfname = "Unknown"; + instance->cnt = 0; + + return 0; +} + +static void subghz_protocol_keeloq_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t key_fix = key >> 32; + uint32_t key_hop = key & 0x00000000ffffffff; + // Check key AN-Motors + if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && + (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { + *manufacture_name = "AN-Motors"; + mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } else if((key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { + *manufacture_name = "HCS101"; + mfname = *manufacture_name; + instance->cnt = key_hop >> 16; + } else { + subghz_protocol_keeloq_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + } + + instance->serial = key_fix & 0x0FFFFFFF; + instance->btn = key_fix >> 28; +} + +uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_keeloq_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + subghz_protocol_keeloq_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + if(strcmp(instance->manufacture_name, "BFT") == 0) { + uint8_t seed_data[sizeof(uint32_t)] = {0}; + for(size_t i = 0; i < sizeof(uint32_t); i++) { + seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF; + } + if(res && !flipper_format_write_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) { + FURI_LOG_E(TAG, "DECODER Serialize: Unable to add Seed"); + res = false; + } + instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | + seed_data[3]; + } + + if(res && !flipper_format_write_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { + FURI_LOG_E(TAG, "DECODER Serialize: Unable to add manufacture name"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_keeloq_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + + uint8_t seed_data[sizeof(uint32_t)] = {0}; + for(size_t i = 0; i < sizeof(uint32_t); i++) { + seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF; + } + if(!flipper_format_read_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) { + FURI_LOG_D(TAG, "DECODER: Missing Seed"); + } + instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 | + seed_data[3]; + + // Read manufacturer from file + if(flipper_format_read_string( + flipper_format, "Manufacture", instance->manufacture_from_file)) { + instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file); + mfname = furi_string_get_cstr(instance->manufacture_from_file); + } else { + FURI_LOG_D(TAG, "DECODER: Missing Manufacture"); + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + + subghz_protocol_keeloq_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + if(strcmp(instance->manufacture_name, "BFT") == 0) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%01X\r\n" + "MF:%s Sd:%08lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name, + instance->generic.seed); + } else { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%01X\r\n" + "MF:%s", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name); + } +} diff --git a/applications/main/subghz/protocols/keeloq.h b/applications/main/subghz/protocols/keeloq.h new file mode 100644 index 000000000..7b0cfc3bd --- /dev/null +++ b/applications/main/subghz/protocols/keeloq.h @@ -0,0 +1,153 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_KEELOQ_NAME "KeeLoq" + +typedef struct SubGhzProtocolDecoderKeeloq SubGhzProtocolDecoderKeeloq; +typedef struct SubGhzProtocolEncoderKeeloq SubGhzProtocolEncoderKeeloq; + +extern const SubGhzProtocolDecoder subghz_protocol_keeloq_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_keeloq_encoder; +extern const SubGhzProtocol subghz_protocol_keeloq; + +void keeloq_reset_mfname(); + +void keeloq_reset_kl_type(); + +/** + * Allocate SubGhzProtocolEncoderKeeloq. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderKeeloq* pointer to a SubGhzProtocolEncoderKeeloq instance + */ +void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderKeeloq. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + */ +void subghz_protocol_encoder_keeloq_free(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Key generation for BFT. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param seed Seed value, 32 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_keeloq_bft_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + */ +void subghz_protocol_encoder_keeloq_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_keeloq_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderKeeloq. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKeeloq* pointer to a SubGhzProtocolDecoderKeeloq instance + */ +void* subghz_protocol_decoder_keeloq_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + */ +void subghz_protocol_decoder_keeloq_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + */ +void subghz_protocol_decoder_keeloq_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_keeloq_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param output Resulting text + */ +void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/keeloq_common.c b/applications/main/subghz/protocols/keeloq_common.c new file mode 100644 index 000000000..1d2d04457 --- /dev/null +++ b/applications/main/subghz/protocols/keeloq_common.c @@ -0,0 +1,142 @@ +#include "keeloq_common.h" + +#include + +#include + +#define bit(x, n) (((x) >> (n)) & 1) +#define g5(x, a, b, c, d, e) \ + (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) + +/** Simple Learning Encrypt + * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + * @param key - manufacture (64bit) + * @return keeloq encrypt data + */ +inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { + uint32_t x = data, r; + for(r = 0; r < 528; r++) + x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^ + bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31))) + << 31); + return x; +} + +/** Simple Learning Decrypt + * @param data - keelog encrypt data + * @param key - manufacture (64bit) + * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + */ +inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) { + uint32_t x = data, r; + for(r = 0; r < 528; r++) + x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^ + bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30)); + return x; +} + +/** Normal Learning + * @param data - serial number (28bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ +inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) { + uint32_t k1, k2; + + data &= 0x0FFFFFFF; + data |= 0x20000000; + k1 = subghz_protocol_keeloq_common_decrypt(data, key); + + data &= 0x0FFFFFFF; + data |= 0x60000000; + k2 = subghz_protocol_keeloq_common_decrypt(data, key); + + return ((uint64_t)k2 << 32) | k1; // key - shifrovanoya +} + +/** Secure Learning + * @param data - serial number (28bit) + * @param seed - seed number (32bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t subghz_protocol_keeloq_common_secure_learning( + uint32_t data, + uint32_t seed, + const uint64_t key) { + uint32_t k1, k2; + + data &= 0x0FFFFFFF; + k1 = subghz_protocol_keeloq_common_decrypt(data, key); + k2 = subghz_protocol_keeloq_common_decrypt(seed, key); + + return ((uint64_t)k1 << 32) | k2; +} + +/** Magic_xor_type1 Learning + * @param data - serial number (28bit) + * @param xor - magic xor (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) { + data &= 0x0FFFFFFF; + return (((uint64_t)data << 32) | data) ^ xor; +} + +/** Faac SLH (Spa) Learning + * @param seed - seed number (32bit) + * @param key - mfkey (64bit) + * @return man_learning for this seed number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_faac_learning(const uint32_t seed, const uint64_t key) { + uint16_t hs = seed >> 16; + const uint16_t ending = 0x544D; + uint32_t lsb = (uint32_t)hs << 16 | ending; + uint64_t man = (uint64_t)subghz_protocol_keeloq_common_encrypt(seed, key) << 32 | + subghz_protocol_keeloq_common_encrypt(lsb, key); + return man; +} +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) | + ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); +} + +/** Magic_serial_type2 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { + uint8_t* p = (uint8_t*)&data; + uint8_t* m = (uint8_t*)&man; + m[7] = p[0]; + m[6] = p[1]; + m[5] = p[2]; + m[4] = p[3]; + return man; +} + +/** Magic_serial_type3 Learning + * @param data - serial number (24bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); +} diff --git a/applications/main/subghz/protocols/keeloq_common.h b/applications/main/subghz/protocols/keeloq_common.h new file mode 100644 index 000000000..a6c0d346e --- /dev/null +++ b/applications/main/subghz/protocols/keeloq_common.h @@ -0,0 +1,100 @@ +#pragma once + +#include "base.h" + +#include + +/* + * Keeloq + * https://ru.wikipedia.org/wiki/KeeLoq + * https://phreakerclub.com/forum/showthread.php?t=1094 + * + */ +#define KEELOQ_NLF 0x3A5C742E + +/* + * KeeLoq learning types + * https://phreakerclub.com/forum/showthread.php?t=67 + */ +#define KEELOQ_LEARNING_UNKNOWN 0u +#define KEELOQ_LEARNING_SIMPLE 1u +#define KEELOQ_LEARNING_NORMAL 2u +#define KEELOQ_LEARNING_SECURE 3u +#define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u +#define KEELOQ_LEARNING_FAAC 5u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 6u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 7u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 8u + +/** + * Simple Learning Encrypt + * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + * @param key - manufacture (64bit) + * @return keeloq encrypt data + */ +uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key); + +/** + * Simple Learning Decrypt + * @param data - keeloq encrypt data + * @param key - manufacture (64bit) + * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + */ +uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key); + +/** + * Normal Learning + * @param data - serial number (28bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ +uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key); + +/** + * Secure Learning + * @param data - serial number (28bit) + * @param seed - seed number (32bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ +uint64_t + subghz_protocol_keeloq_common_secure_learning(uint32_t data, uint32_t seed, const uint64_t key); + +/** + * Magic_xor_type1 Learning + * @param data - serial number (28bit) + * @param xor - magic xor (64bit) + * @return manufacture for this serial number (64bit) + */ +uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor); + +/** Faac SLH (Spa) Learning + * @param seed - seed number (32bit) + * @param key - mfkey (64bit) + * @return man_learning for this fix number (64bit) + */ +uint64_t subghz_protocol_keeloq_common_faac_learning(const uint32_t seed, const uint64_t key); + +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man); + +/** Magic_serial_type2 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man); + +/** Magic_serial_type3 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man); diff --git a/applications/main/subghz/protocols/kia.c b/applications/main/subghz/protocols/kia.c new file mode 100644 index 000000000..a5d9e37ef --- /dev/null +++ b/applications/main/subghz/protocols/kia.c @@ -0,0 +1,279 @@ +#include "kia.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoKIA" + +static const SubGhzBlockConst subghz_protocol_kia_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 100, + .min_count_bit_for_found = 61, +}; + +struct SubGhzProtocolDecoderKIA { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderKIA { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + KIADecoderStepReset = 0, + KIADecoderStepCheckPreambula, + KIADecoderStepSaveDuration, + KIADecoderStepCheckDuration, +} KIADecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_kia_decoder = { + .alloc = subghz_protocol_decoder_kia_alloc, + .free = subghz_protocol_decoder_kia_free, + + .feed = subghz_protocol_decoder_kia_feed, + .reset = subghz_protocol_decoder_kia_reset, + + .get_hash_data = subghz_protocol_decoder_kia_get_hash_data, + .serialize = subghz_protocol_decoder_kia_serialize, + .deserialize = subghz_protocol_decoder_kia_deserialize, + .get_string = subghz_protocol_decoder_kia_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_kia_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_kia = { + .name = SUBGHZ_PROTOCOL_KIA_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_kia_decoder, + .encoder = &subghz_protocol_kia_encoder, +}; + +void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderKIA* instance = malloc(sizeof(SubGhzProtocolDecoderKIA)); + instance->base.protocol = &subghz_protocol_kia; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_kia_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + free(instance); +} + +void subghz_protocol_decoder_kia_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + instance->decoder.parser_step = KIADecoderStepReset; +} + +void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + + switch(instance->decoder.parser_step) { + case KIADecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta)) { + instance->decoder.parser_step = KIADecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case KIADecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + } else if( + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta)) { + // Found header + instance->header_count++; + break; + } else if( + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta)) { + // Found start bit + if(instance->header_count > 15) { + instance->decoder.parser_step = KIADecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + break; + case KIADecoderStepSaveDuration: + if(level) { + if(duration >= + (subghz_protocol_kia_const.te_long + subghz_protocol_kia_const.te_delta * 2UL)) { + //Found stop bit + instance->decoder.parser_step = KIADecoderStepReset; + if(instance->decoder.decode_count_bit == + subghz_protocol_kia_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KIADecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + break; + case KIADecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KIADecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KIADecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_kia_crc8(uint8_t* data, size_t len) { + uint8_t crc = 0x08; + size_t i, j; + for(i = 0; i < len; i++) { + crc ^= data[i]; + for(j = 0; j < 8; j++) { + if((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x7F); + else + crc <<= 1; + } + } + return crc; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * 0x0F 0112 43B04EC 1 7D + * 0x0F 0113 43B04EC 1 DF + * 0x0F 0114 43B04EC 1 30 + * 0x0F 0115 43B04EC 2 13 + * 0x0F 0116 43B04EC 3 F5 + * CNT Serial K CRC8 Kia (CRC8, poly 0x7f, start_crc 0x08) + */ + + instance->serial = (uint32_t)((instance->data >> 12) & 0x0FFFFFFF); + instance->btn = (instance->data >> 8) & 0x0F; + instance->cnt = (instance->data >> 40) & 0xFFFF; +} + +uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_kia_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != subghz_protocol_kia_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + + subghz_protocol_kia_check_remote_controller(&instance->generic); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Sn:%07lX Btn:%X Cnt:%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/applications/main/subghz/protocols/kia.h b/applications/main/subghz/protocols/kia.h new file mode 100644 index 000000000..a9afcf149 --- /dev/null +++ b/applications/main/subghz/protocols/kia.h @@ -0,0 +1,73 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_KIA_NAME "KIA Seed" + +typedef struct SubGhzProtocolDecoderKIA SubGhzProtocolDecoderKIA; +typedef struct SubGhzProtocolEncoderKIA SubGhzProtocolEncoderKIA; + +extern const SubGhzProtocolDecoder subghz_protocol_kia_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_kia_encoder; +extern const SubGhzProtocol subghz_protocol_kia; + +/** + * Allocate SubGhzProtocolDecoderKIA. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKIA* pointer to a SubGhzProtocolDecoderKIA instance + */ +void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + */ +void subghz_protocol_decoder_kia_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + */ +void subghz_protocol_decoder_kia_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_kia_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param output Resulting text + */ +void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/kinggates_stylo_4k.c b/applications/main/subghz/protocols/kinggates_stylo_4k.c new file mode 100644 index 000000000..5f2a83d77 --- /dev/null +++ b/applications/main/subghz/protocols/kinggates_stylo_4k.c @@ -0,0 +1,581 @@ +#include "kinggates_stylo_4k.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoKingGates_stylo_4k" + +static const SubGhzBlockConst subghz_protocol_kinggates_stylo_4k_const = { + .te_short = 400, + .te_long = 1100, + .te_delta = 140, + .min_count_bit_for_found = 89, +}; + +struct SubGhzProtocolDecoderKingGates_stylo_4k { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; +}; + +struct SubGhzProtocolEncoderKingGates_stylo_4k { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + SubGhzKeystore* keystore; +}; + +typedef enum { + KingGates_stylo_4kDecoderStepReset = 0, + KingGates_stylo_4kDecoderStepCheckPreambula, + KingGates_stylo_4kDecoderStepCheckStartBit, + KingGates_stylo_4kDecoderStepSaveDuration, + KingGates_stylo_4kDecoderStepCheckDuration, +} KingGates_stylo_4kDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_kinggates_stylo_4k_decoder = { + .alloc = subghz_protocol_decoder_kinggates_stylo_4k_alloc, + .free = subghz_protocol_decoder_kinggates_stylo_4k_free, + + .feed = subghz_protocol_decoder_kinggates_stylo_4k_feed, + .reset = subghz_protocol_decoder_kinggates_stylo_4k_reset, + + .get_hash_data = subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data, + .serialize = subghz_protocol_decoder_kinggates_stylo_4k_serialize, + .deserialize = subghz_protocol_decoder_kinggates_stylo_4k_deserialize, + .get_string = subghz_protocol_decoder_kinggates_stylo_4k_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_kinggates_stylo_4k_encoder = { + .alloc = subghz_protocol_encoder_kinggates_stylo_4k_alloc, + .free = subghz_protocol_encoder_kinggates_stylo_4k_free, + + .deserialize = subghz_protocol_encoder_kinggates_stylo_4k_deserialize, + .stop = subghz_protocol_encoder_kinggates_stylo_4k_stop, + .yield = subghz_protocol_encoder_kinggates_stylo_4k_yield, +}; + +const SubGhzProtocol subghz_protocol_kinggates_stylo_4k = { + .name = SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_kinggates_stylo_4k_decoder, + .encoder = &subghz_protocol_kinggates_stylo_4k_encoder, +}; + +// +// Encoder +// + +// Pre define function +static void subghz_protocol_kinggates_stylo_4k_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore); + +void* subghz_protocol_encoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderKingGates_stylo_4k* instance = + malloc(sizeof(SubGhzProtocolEncoderKingGates_stylo_4k)); + + instance->base.protocol = &subghz_protocol_kinggates_stylo_4k; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + 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_kinggates_stylo_4k_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderKingGates_stylo_4k* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void subghz_protocol_encoder_kinggates_stylo_4k_stop(void* context) { + SubGhzProtocolEncoderKingGates_stylo_4k* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_kinggates_stylo_4k_yield(void* context) { + SubGhzProtocolEncoderKingGates_stylo_4k* 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; +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k* instance + * @param btn Button number, 4 bit + */ +static bool subghz_protocol_kinggates_stylo_4k_gen_data( + SubGhzProtocolEncoderKingGates_stylo_4k* instance, + uint8_t btn) { + UNUSED(btn); + uint32_t hop = subghz_protocol_blocks_reverse_key(instance->generic.data_2 >> 4, 32); + uint64_t fix = subghz_protocol_blocks_reverse_key(instance->generic.data, 53); + int res = 0; + uint32_t decrypt = 0; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k"); + if(res == 0) { + //Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + break; + } + } + instance->generic.cnt = decrypt & 0xFFFF; + + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + + instance->generic.btn = (fix >> 17) & 0x0F; + instance->generic.serial = ((fix >> 5) & 0xFFFF0000) | (fix & 0xFFFF); + + uint32_t data = (decrypt & 0xFFFF0000) | instance->generic.cnt; + + uint64_t encrypt = 0; + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k"); + if(res == 0) { + //Simple Learning + encrypt = subghz_protocol_keeloq_common_encrypt(data, manufacture_code->key); + encrypt = subghz_protocol_blocks_reverse_key(encrypt, 32); + instance->generic.data_2 = encrypt << 4; + return true; + } + } + + return false; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + * @return true On success + */ +static bool subghz_protocol_encoder_kinggates_stylo_4k_get_upload( + SubGhzProtocolEncoderKingGates_stylo_4k* instance, + uint8_t btn) { + furi_assert(instance); + + // Gen new key + if(subghz_protocol_kinggates_stylo_4k_gen_data(instance, btn)) { + //ToDo if you need to add a callback to automatically update the data on the display + } else { + return false; + } + + size_t index = 0; + + // Start + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)9500); + + // Send header + for(uint8_t i = 12; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + } + + // After header + instance->encoder.upload[index - 1].duration = + (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long * 2; + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short * 2); + + // Send key fix + for(uint8_t i = 53; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + } + } + + // Send key hop + for(uint8_t i = 36; i > 0; i--) { + if(bit_read(instance->generic.data_2, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + } + } + + // Set upload size after generating upload, fix it later + + instance->encoder.size_upload = index; + + return true; +} + +bool subghz_protocol_encoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderKingGates_stylo_4k* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + subghz_protocol_kinggates_stylo_4k_remote_controller( + &instance->generic, instance->keystore); + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + + subghz_protocol_encoder_kinggates_stylo_4k_get_upload(instance, instance->generic.btn); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +// +// Decoder +// +void* subghz_protocol_decoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderKingGates_stylo_4k* instance = + malloc(sizeof(SubGhzProtocolDecoderKingGates_stylo_4k)); + instance->base.protocol = &subghz_protocol_kinggates_stylo_4k; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + return instance; +} + +void subghz_protocol_decoder_kinggates_stylo_4k_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + free(instance); +} + +void subghz_protocol_decoder_kinggates_stylo_4k_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; +} + +void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + + switch(instance->decoder.parser_step) { + case KingGates_stylo_4kDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta) { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case KingGates_stylo_4kDecoderStepCheckPreambula: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta)) { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_long * 2) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2)) { + // Found header + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepCheckStartBit; + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + break; + case KingGates_stylo_4kDecoderStepCheckStartBit: + if((level) && + DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short * 2) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->generic.data_2 = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + case KingGates_stylo_4kDecoderStepSaveDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long * 3)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found) { + instance->generic.data = instance->generic.data_2; + instance->generic.data_2 = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->decoder.decode_data = 0; + instance->generic.data_2 = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + break; + case KingGates_stylo_4kDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_long) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_kinggates_stylo_4k_const.te_long) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + if(instance->decoder.decode_count_bit == 53) { + instance->generic.data_2 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param data Input encrypted data + * @param keystore Pointer to a SubGhzKeystore* instance + */ +static void subghz_protocol_kinggates_stylo_4k_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore) { + /** + * 9500us 12*(400/400) 2200/800|1-bit|0-bit| + * _ _ _ __ ___ _ + * ________| |_| |_..._| |_____| |_| |___| |..... + * + * 1-bit 400/1100 us + * 0-bit 1100/400 us + * + * The package consists of 89 bits of data, LSB first + * Data - 1C9037F0C80000 CE280BA00 + * S[3] S[2] 1 key S[1] S[0] 2 byte always 0 Hop[3] Hop[2] Hop[1] Hop[0] 0 + * 11100100 10000001 1 0111 11110000 11001000 00000000 00000000 11001110 00101000 00001011 10100000 0000 + * + * Encryption - keeloq Simple Learning + * key C S[3] CNT + * Decrypt - 0xEC270B9C => 0x E C 27 0B9C + * + * + * +*/ + + uint32_t hop = subghz_protocol_blocks_reverse_key(instance->data_2 >> 4, 32); + uint64_t fix = subghz_protocol_blocks_reverse_key(instance->data, 53); + bool ret = false; + uint32_t decrypt = 0; + instance->btn = (fix >> 17) & 0x0F; + instance->serial = ((fix >> 5) & 0xFFFF0000) | (fix & 0xFFFF); + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE) { + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(((decrypt >> 28) == instance->btn) && (((decrypt >> 24) & 0x0F) == 0x0C) && + (((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF))) { + ret = true; + break; + } + } + } + if(ret) { + instance->cnt = decrypt & 0xFFFF; + } else { + instance->btn = 0; + instance->serial = 0; + instance->cnt = 0; + } +} + +uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + 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_2 >> (i * 8)) & 0xFF; + } + + if(res && !flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + res = false; + } + return res; + + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + subghz_protocol_kinggates_stylo_4k_remote_controller(&instance->generic, instance->keystore); + + furi_string_cat_printf( + output, + "%s\r\n" + "Key:0x%llX%07llX %dbit\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data, + instance->generic.data_2, + instance->generic.data_count_bit, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} \ No newline at end of file diff --git a/applications/main/subghz/protocols/kinggates_stylo_4k.h b/applications/main/subghz/protocols/kinggates_stylo_4k.h new file mode 100644 index 000000000..9717f6715 --- /dev/null +++ b/applications/main/subghz/protocols/kinggates_stylo_4k.h @@ -0,0 +1,110 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME "KingGates Stylo4k" + +typedef struct SubGhzProtocolDecoderKingGates_stylo_4k SubGhzProtocolDecoderKingGates_stylo_4k; +typedef struct SubGhzProtocolEncoderKingGates_stylo_4k SubGhzProtocolEncoderKingGates_stylo_4k; + +extern const SubGhzProtocolDecoder subghz_protocol_kinggates_stylo_4k_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_kinggates_stylo_4k_encoder; +extern const SubGhzProtocol subghz_protocol_kinggates_stylo_4k; + +/** + * Allocate SubGhzProtocolEncoderKingGates_stylo_4k. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderKingGates_stylo_4k* pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + */ +void* subghz_protocol_encoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + */ +void subghz_protocol_encoder_kinggates_stylo_4k_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + */ +void subghz_protocol_encoder_kinggates_stylo_4k_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_kinggates_stylo_4k_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderKingGates_stylo_4k. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKingGates_stylo_4k* pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + */ +void* subghz_protocol_decoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + */ +void subghz_protocol_decoder_kinggates_stylo_4k_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + */ +void subghz_protocol_decoder_kinggates_stylo_4k_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param output Resulting text + */ +void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/linear.c b/applications/main/subghz/protocols/linear.c new file mode 100644 index 000000000..2fc8b20c8 --- /dev/null +++ b/applications/main/subghz/protocols/linear.c @@ -0,0 +1,352 @@ +#include "linear.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolLinear" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" +#define DATA_TO_DIP(dip) \ + (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_linear_const = { + .te_short = 500, + .te_long = 1500, + .te_delta = 150, + .min_count_bit_for_found = 10, +}; + +struct SubGhzProtocolDecoderLinear { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderLinear { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + LinearDecoderStepReset = 0, + LinearDecoderStepSaveDuration, + LinearDecoderStepCheckDuration, +} LinearDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_linear_decoder = { + .alloc = subghz_protocol_decoder_linear_alloc, + .free = subghz_protocol_decoder_linear_free, + + .feed = subghz_protocol_decoder_linear_feed, + .reset = subghz_protocol_decoder_linear_reset, + + .get_hash_data = subghz_protocol_decoder_linear_get_hash_data, + .serialize = subghz_protocol_decoder_linear_serialize, + .deserialize = subghz_protocol_decoder_linear_deserialize, + .get_string = subghz_protocol_decoder_linear_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_linear_encoder = { + .alloc = subghz_protocol_encoder_linear_alloc, + .free = subghz_protocol_encoder_linear_free, + + .deserialize = subghz_protocol_encoder_linear_deserialize, + .stop = subghz_protocol_encoder_linear_stop, + .yield = subghz_protocol_encoder_linear_yield, +}; + +const SubGhzProtocol subghz_protocol_linear = { + .name = SUBGHZ_PROTOCOL_LINEAR_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_linear_decoder, + .encoder = &subghz_protocol_linear_encoder, +}; + +void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderLinear* instance = malloc(sizeof(SubGhzProtocolEncoderLinear)); + + instance->base.protocol = &subghz_protocol_linear; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 28; //max 10bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_linear_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderLinear* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderLinear instance + * @return true On success + */ +static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinear* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 3); + } + } + //Send end bit + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); + //Send PT_GUARD + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 42); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); + //Send PT_GUARD + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); + } + + return true; +} + +bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderLinear* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_linear_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_linear_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_linear_stop(void* context) { + SubGhzProtocolEncoderLinear* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_linear_yield(void* context) { + SubGhzProtocolEncoderLinear* 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_linear_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderLinear* instance = malloc(sizeof(SubGhzProtocolDecoderLinear)); + instance->base.protocol = &subghz_protocol_linear; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_linear_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + free(instance); +} + +void subghz_protocol_decoder_linear_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + instance->decoder.parser_step = LinearDecoderStepReset; +} + +void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + switch(instance->decoder.parser_step) { + case LinearDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) < + subghz_protocol_linear_const.te_delta * 20)) { + //Found header Linear + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } + break; + case LinearDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LinearDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + break; + case LinearDecoderStepCheckDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_linear_const.te_short * 5)) { + instance->decoder.parser_step = LinearDecoderStepReset; + //checking that the duration matches the guardtime + if((DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) > + subghz_protocol_linear_const.te_delta * 20)) { + break; + } + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if( + DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if(instance->decoder.decode_count_bit == + subghz_protocol_linear_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + 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); + } + break; + } + + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_linear_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_linear_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderLinear* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo, + DATA_TO_DIP(code_found_lo)); +} diff --git a/applications/main/subghz/protocols/linear.h b/applications/main/subghz/protocols/linear.h new file mode 100644 index 000000000..923337ac2 --- /dev/null +++ b/applications/main/subghz/protocols/linear.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LINEAR_NAME "Linear" + +typedef struct SubGhzProtocolDecoderLinear SubGhzProtocolDecoderLinear; +typedef struct SubGhzProtocolEncoderLinear SubGhzProtocolEncoderLinear; + +extern const SubGhzProtocolDecoder subghz_protocol_linear_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_linear_encoder; +extern const SubGhzProtocol subghz_protocol_linear; + +/** + * Allocate SubGhzProtocolEncoderLinear. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLinear* pointer to a SubGhzProtocolEncoderLinear instance + */ +void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLinear. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + */ +void subghz_protocol_encoder_linear_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + */ +void subghz_protocol_encoder_linear_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_linear_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLinear. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLinear* pointer to a SubGhzProtocolDecoderLinear instance + */ +void* subghz_protocol_decoder_linear_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + */ +void subghz_protocol_decoder_linear_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + */ +void subghz_protocol_decoder_linear_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_linear_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param output Resulting text + */ +void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/linear_delta3.c b/applications/main/subghz/protocols/linear_delta3.c new file mode 100644 index 000000000..869edac84 --- /dev/null +++ b/applications/main/subghz/protocols/linear_delta3.c @@ -0,0 +1,359 @@ +#include "linear_delta3.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolLinearDelta3" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define DATA_TO_DIP(dip) \ + (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), \ + (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), \ + (dip & 0x0002 ? '1' : '0'), (dip & 0x0001 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_linear_delta3_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 8, +}; + +struct SubGhzProtocolDecoderLinearDelta3 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderLinearDelta3 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + LinearDecoderStepReset = 0, + LinearDecoderStepSaveDuration, + LinearDecoderStepCheckDuration, +} LinearDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_linear_delta3_decoder = { + .alloc = subghz_protocol_decoder_linear_delta3_alloc, + .free = subghz_protocol_decoder_linear_delta3_free, + + .feed = subghz_protocol_decoder_linear_delta3_feed, + .reset = subghz_protocol_decoder_linear_delta3_reset, + + .get_hash_data = subghz_protocol_decoder_linear_delta3_get_hash_data, + .serialize = subghz_protocol_decoder_linear_delta3_serialize, + .deserialize = subghz_protocol_decoder_linear_delta3_deserialize, + .get_string = subghz_protocol_decoder_linear_delta3_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_linear_delta3_encoder = { + .alloc = subghz_protocol_encoder_linear_delta3_alloc, + .free = subghz_protocol_encoder_linear_delta3_free, + + .deserialize = subghz_protocol_encoder_linear_delta3_deserialize, + .stop = subghz_protocol_encoder_linear_delta3_stop, + .yield = subghz_protocol_encoder_linear_delta3_yield, +}; + +const SubGhzProtocol subghz_protocol_linear_delta3 = { + .name = SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_linear_delta3_decoder, + .encoder = &subghz_protocol_linear_delta3_encoder, +}; + +void* subghz_protocol_encoder_linear_delta3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderLinearDelta3* instance = + malloc(sizeof(SubGhzProtocolEncoderLinearDelta3)); + + instance->base.protocol = &subghz_protocol_linear_delta3; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 16; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_linear_delta3_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderLinearDelta3* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + * @return true On success + */ +static bool + subghz_protocol_encoder_linear_delta3_get_upload(SubGhzProtocolEncoderLinearDelta3* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_linear_delta3_const.te_short * 7); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_delta3_const.te_long); + } + } + //Send end bit + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_short); + //Send PT_GUARD + instance->encoder.upload[index] = level_duration_make( + false, (uint32_t)subghz_protocol_linear_delta3_const.te_short * 73); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_long); + //Send PT_GUARD + instance->encoder.upload[index] = level_duration_make( + false, (uint32_t)subghz_protocol_linear_delta3_const.te_short * 70); + } + + return true; +} + +bool subghz_protocol_encoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderLinearDelta3* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_linear_delta3_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_linear_delta3_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_linear_delta3_stop(void* context) { + SubGhzProtocolEncoderLinearDelta3* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_linear_delta3_yield(void* context) { + SubGhzProtocolEncoderLinearDelta3* 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_linear_delta3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderLinearDelta3* instance = + malloc(sizeof(SubGhzProtocolDecoderLinearDelta3)); + instance->base.protocol = &subghz_protocol_linear_delta3; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_linear_delta3_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + free(instance); +} + +void subghz_protocol_decoder_linear_delta3_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + instance->decoder.parser_step = LinearDecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_linear_delta3_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + switch(instance->decoder.parser_step) { + case LinearDecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_linear_delta3_const.te_short * 70) < + subghz_protocol_linear_delta3_const.te_delta * 24)) { + //Found header Linear + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } + break; + case LinearDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LinearDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + break; + case LinearDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_linear_delta3_const.te_short * 10)) { + instance->decoder.parser_step = LinearDecoderStepReset; + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_short) < + subghz_protocol_linear_delta3_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_long) < + subghz_protocol_linear_delta3_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if(instance->decoder.decode_count_bit == + subghz_protocol_linear_delta3_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + instance->last_data = instance->decoder.decode_data; + } + break; + } + + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_short) < + subghz_protocol_linear_delta3_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_delta3_const.te_short * 7) < + subghz_protocol_linear_delta3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_long) < + subghz_protocol_linear_delta3_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_delta3_const.te_long) < + subghz_protocol_linear_delta3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8)); +} + +bool subghz_protocol_decoder_linear_delta3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_linear_delta3_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_linear_delta3_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + + uint32_t data = instance->generic.data & 0xFF; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + data, + DATA_TO_DIP(data)); +} diff --git a/applications/main/subghz/protocols/linear_delta3.h b/applications/main/subghz/protocols/linear_delta3.h new file mode 100644 index 000000000..2f0a32e68 --- /dev/null +++ b/applications/main/subghz/protocols/linear_delta3.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME "LinearDelta3" + +typedef struct SubGhzProtocolDecoderLinearDelta3 SubGhzProtocolDecoderLinearDelta3; +typedef struct SubGhzProtocolEncoderLinearDelta3 SubGhzProtocolEncoderLinearDelta3; + +extern const SubGhzProtocolDecoder subghz_protocol_linear_delta3_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_linear_delta3_encoder; +extern const SubGhzProtocol subghz_protocol_linear_delta3; + +/** + * Allocate SubGhzProtocolEncoderLinearDelta3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLinearDelta3* pointer to a SubGhzProtocolEncoderLinearDelta3 instance + */ +void* subghz_protocol_encoder_linear_delta3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + */ +void subghz_protocol_encoder_linear_delta3_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + */ +void subghz_protocol_encoder_linear_delta3_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_linear_delta3_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLinearDelta3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLinearDelta3* pointer to a SubGhzProtocolDecoderLinearDelta3 instance + */ +void* subghz_protocol_decoder_linear_delta3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + */ +void subghz_protocol_decoder_linear_delta3_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + */ +void subghz_protocol_decoder_linear_delta3_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_linear_delta3_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_linear_delta3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_linear_delta3_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/magellan.c b/applications/main/subghz/protocols/magellan.c new file mode 100644 index 000000000..67d3fe3d3 --- /dev/null +++ b/applications/main/subghz/protocols/magellan.c @@ -0,0 +1,445 @@ +#include "magellan.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolMagellan" + +static const SubGhzBlockConst subghz_protocol_magellan_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 100, + .min_count_bit_for_found = 32, +}; + +struct SubGhzProtocolDecoderMagellan { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderMagellan { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + MagellanDecoderStepReset = 0, + MagellanDecoderStepCheckPreambula, + MagellanDecoderStepFoundPreambula, + MagellanDecoderStepSaveDuration, + MagellanDecoderStepCheckDuration, +} MagellanDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_magellan_decoder = { + .alloc = subghz_protocol_decoder_magellan_alloc, + .free = subghz_protocol_decoder_magellan_free, + + .feed = subghz_protocol_decoder_magellan_feed, + .reset = subghz_protocol_decoder_magellan_reset, + + .get_hash_data = subghz_protocol_decoder_magellan_get_hash_data, + .serialize = subghz_protocol_decoder_magellan_serialize, + .deserialize = subghz_protocol_decoder_magellan_deserialize, + .get_string = subghz_protocol_decoder_magellan_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_magellan_encoder = { + .alloc = subghz_protocol_encoder_magellan_alloc, + .free = subghz_protocol_encoder_magellan_free, + + .deserialize = subghz_protocol_encoder_magellan_deserialize, + .stop = subghz_protocol_encoder_magellan_stop, + .yield = subghz_protocol_encoder_magellan_yield, +}; + +const SubGhzProtocol subghz_protocol_magellan = { + .name = SUBGHZ_PROTOCOL_MAGELLAN_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_magellan_decoder, + .encoder = &subghz_protocol_magellan_encoder, +}; + +void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMagellan* instance = malloc(sizeof(SubGhzProtocolEncoderMagellan)); + + instance->base.protocol = &subghz_protocol_magellan; + 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_magellan_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMagellan* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderMagellan instance + * @return true On success + */ +static bool subghz_protocol_encoder_magellan_get_upload(SubGhzProtocolEncoderMagellan* instance) { + furi_assert(instance); + + size_t index = 0; + + //Send header + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short * 4); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short); + for(uint8_t i = 0; i < 12; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long); + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_long * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long); + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short); + } + } + + //Send stop bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long * 100); + + instance->encoder.size_upload = index; + return true; +} + +bool subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMagellan* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_magellan_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_magellan_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_magellan_stop(void* context) { + SubGhzProtocolEncoderMagellan* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_magellan_yield(void* context) { + SubGhzProtocolEncoderMagellan* 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_magellan_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMagellan* instance = malloc(sizeof(SubGhzProtocolDecoderMagellan)); + instance->base.protocol = &subghz_protocol_magellan; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_magellan_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + free(instance); +} + +void subghz_protocol_decoder_magellan_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + instance->decoder.parser_step = MagellanDecoderStepReset; +} + +uint8_t subghz_protocol_magellan_crc8(uint8_t* data, size_t len) { + uint8_t crc = 0x00; + size_t i, j; + for(i = 0; i < len; i++) { + crc ^= data[i]; + for(j = 0; j < 8; j++) { + if((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; +} + +static bool subghz_protocol_magellan_check_crc(SubGhzProtocolDecoderMagellan* instance) { + uint8_t data[3] = { + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + return (instance->decoder.decode_data & 0xFF) == + subghz_protocol_magellan_crc8(data, sizeof(data)); +} + +void subghz_protocol_decoder_magellan_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + + switch(instance->decoder.parser_step) { + case MagellanDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta)) { + instance->decoder.parser_step = MagellanDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case MagellanDecoderStepCheckPreambula: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta)) { + // Found header + instance->header_count++; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta * 2) && + (instance->header_count > 10)) { + instance->decoder.parser_step = MagellanDecoderStepFoundPreambula; + } else { + instance->decoder.parser_step = MagellanDecoderStepReset; + } + } + break; + + case MagellanDecoderStepFoundPreambula: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_magellan_const.te_short * 6) < + subghz_protocol_magellan_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta * 2)) { + instance->decoder.parser_step = MagellanDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = MagellanDecoderStepReset; + } + } + break; + + case MagellanDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = MagellanDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = MagellanDecoderStepReset; + } + break; + + case MagellanDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = MagellanDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = MagellanDecoderStepSaveDuration; + } else if(duration >= (subghz_protocol_magellan_const.te_long * 3)) { + //Found stop bit + if((instance->decoder.decode_count_bit == + subghz_protocol_magellan_const.min_count_bit_for_found) && + subghz_protocol_magellan_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = MagellanDecoderStepReset; + } else { + instance->decoder.parser_step = MagellanDecoderStepReset; + } + } else { + instance->decoder.parser_step = MagellanDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric* instance) { + /* +* package 32b data 24b CRC8 +* 0x037AE4828 => 001101111010111001001000 00101000 +* +* 0x037AE48 (flipped in reverse bit sequence) => 0x1275EC +* +* 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: ? +* +*/ + uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data >> 8, 24); + instance->serial = data_rev & 0xFFFF; + instance->btn = (data_rev >> 16) & 0xFF; +} + +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_\nSensor" : ""), + ((event >> 6) & 0x1 ? ", ?" : ""), + ((event >> 7) & 0x1 ? ", ?" : "")); +} + +uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_magellan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_magellan_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderMagellan* instance = context; + subghz_protocol_magellan_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Sn:%03ld%03ld, Event:0x%02X\r\n" + "Stat:", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + (instance->generic.serial >> 8) & 0xFF, + instance->generic.serial & 0xFF, + instance->generic.btn); + + subghz_protocol_magellan_get_event_serialize(instance->generic.btn, output); +} diff --git a/applications/main/subghz/protocols/magellan.h b/applications/main/subghz/protocols/magellan.h new file mode 100644 index 000000000..a179c9cb4 --- /dev/null +++ b/applications/main/subghz/protocols/magellan.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MAGELLAN_NAME "Magellan" + +typedef struct SubGhzProtocolDecoderMagellan SubGhzProtocolDecoderMagellan; +typedef struct SubGhzProtocolEncoderMagellan SubGhzProtocolEncoderMagellan; + +extern const SubGhzProtocolDecoder subghz_protocol_magellan_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_magellan_encoder; +extern const SubGhzProtocol subghz_protocol_magellan; + +/** + * Allocate SubGhzProtocolEncoderMagellan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMagellan* pointer to a SubGhzProtocolEncoderMagellan instance + */ +void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMagellan. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + */ +void subghz_protocol_encoder_magellan_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + */ +void subghz_protocol_encoder_magellan_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_magellan_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMagellan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMagellan* pointer to a SubGhzProtocolDecoderMagellan instance + */ +void* subghz_protocol_decoder_magellan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + */ +void subghz_protocol_decoder_magellan_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + */ +void subghz_protocol_decoder_magellan_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_magellan_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_magellan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param output Resulting text + */ +void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/marantec.c b/applications/main/subghz/protocols/marantec.c new file mode 100644 index 000000000..d557c29b0 --- /dev/null +++ b/applications/main/subghz/protocols/marantec.c @@ -0,0 +1,393 @@ +#include "marantec.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolMarantec" + +static const SubGhzBlockConst subghz_protocol_marantec_const = { + .te_short = 1000, + .te_long = 2000, + .te_delta = 200, + .min_count_bit_for_found = 49, +}; + +struct SubGhzProtocolDecoderMarantec { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderMarantec { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + MarantecDecoderStepReset = 0, + MarantecDecoderFoundHeader, + MarantecDecoderStepDecoderData, +} MarantecDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_marantec_decoder = { + .alloc = subghz_protocol_decoder_marantec_alloc, + .free = subghz_protocol_decoder_marantec_free, + + .feed = subghz_protocol_decoder_marantec_feed, + .reset = subghz_protocol_decoder_marantec_reset, + + .get_hash_data = subghz_protocol_decoder_marantec_get_hash_data, + .serialize = subghz_protocol_decoder_marantec_serialize, + .deserialize = subghz_protocol_decoder_marantec_deserialize, + .get_string = subghz_protocol_decoder_marantec_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_marantec_encoder = { + .alloc = subghz_protocol_encoder_marantec_alloc, + .free = subghz_protocol_encoder_marantec_free, + + .deserialize = subghz_protocol_encoder_marantec_deserialize, + .stop = subghz_protocol_encoder_marantec_stop, + .yield = subghz_protocol_encoder_marantec_yield, +}; + +const SubGhzProtocol subghz_protocol_marantec = { + .name = SUBGHZ_PROTOCOL_MARANTEC_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_marantec_decoder, + .encoder = &subghz_protocol_marantec_encoder, +}; + +void* subghz_protocol_encoder_marantec_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMarantec* instance = malloc(sizeof(SubGhzProtocolEncoderMarantec)); + + instance->base.protocol = &subghz_protocol_marantec; + 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_marantec_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMarantec* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_marantec_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_marantec_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_marantec_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_marantec_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_marantec_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 SubGhzProtocolEncoderMarantec instance + */ +static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMarantec* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + if(!manchester_encoder_advance( + &enc_state, + bit_read(instance->generic.data, instance->generic.data_count_bit - 1), + &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_marantec_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, + bit_read(instance->generic.data, instance->generic.data_count_bit - 1), + &result); + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_marantec_const.te_long * 5); + + for(uint8_t i = instance->generic.data_count_bit - 1; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_marantec_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_marantec_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_marantec_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.size_upload = index; +} + +uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { + uint8_t crc = 0x08; + size_t i, j; + for(i = 0; i < len; i++) { + crc ^= data[i]; + for(j = 0; j < 8; j++) { + if((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x1D); + else + crc <<= 1; + } + } + return crc; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = (instance->data >> 16) & 0xF; + instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF); +} + +bool subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMarantec* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_marantec_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_marantec_remote_controller(&instance->generic); + subghz_protocol_encoder_marantec_get_upload(instance); + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_marantec_stop(void* context) { + SubGhzProtocolEncoderMarantec* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_marantec_yield(void* context) { + SubGhzProtocolEncoderMarantec* 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_marantec_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMarantec* instance = malloc(sizeof(SubGhzProtocolDecoderMarantec)); + instance->base.protocol = &subghz_protocol_marantec; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_marantec_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + free(instance); +} + +void subghz_protocol_decoder_marantec_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_marantec_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + ManchesterEvent event = ManchesterEventReset; + + switch(instance->decoder.parser_step) { + case MarantecDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_marantec_const.te_long * 5) < + subghz_protocol_marantec_const.te_delta * 8)) { + //Found header marantec + instance->decoder.parser_step = MarantecDecoderStepDecoderData; + instance->decoder.decode_data = 1; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } + break; + case MarantecDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_marantec_const.te_short) < + subghz_protocol_marantec_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_marantec_const.te_long) < + subghz_protocol_marantec_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= ((uint32_t)subghz_protocol_marantec_const.te_long * 2 + + subghz_protocol_marantec_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_marantec_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 = 1; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = MarantecDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_marantec_const.te_short) < + subghz_protocol_marantec_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_marantec_const.te_long) < + subghz_protocol_marantec_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = MarantecDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_marantec_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_marantec_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderMarantec* instance = context; + subghz_protocol_marantec_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%07lX \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, + instance->generic.btn); +} diff --git a/applications/main/subghz/protocols/marantec.h b/applications/main/subghz/protocols/marantec.h new file mode 100644 index 000000000..e330ccf16 --- /dev/null +++ b/applications/main/subghz/protocols/marantec.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MARANTEC_NAME "Marantec" + +typedef struct SubGhzProtocolDecoderMarantec SubGhzProtocolDecoderMarantec; +typedef struct SubGhzProtocolEncoderMarantec SubGhzProtocolEncoderMarantec; + +extern const SubGhzProtocolDecoder subghz_protocol_marantec_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_marantec_encoder; +extern const SubGhzProtocol subghz_protocol_marantec; + +/** + * Allocate SubGhzProtocolEncoderMarantec. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMarantec* pointer to a SubGhzProtocolEncoderMarantec instance + */ +void* subghz_protocol_encoder_marantec_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMarantec. + * @param context Pointer to a SubGhzProtocolEncoderMarantec instance + */ +void subghz_protocol_encoder_marantec_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMarantec instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMarantec instance + */ +void subghz_protocol_encoder_marantec_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMarantec instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_marantec_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMarantec. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMarantec* pointer to a SubGhzProtocolDecoderMarantec instance + */ +void* subghz_protocol_decoder_marantec_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMarantec. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + */ +void subghz_protocol_decoder_marantec_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMarantec. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + */ +void subghz_protocol_decoder_marantec_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_marantec_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMarantec. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_marantec_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMarantec. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMarantec instance + * @param output Resulting text + */ +void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/megacode.c b/applications/main/subghz/protocols/megacode.c new file mode 100644 index 000000000..05b5b6894 --- /dev/null +++ b/applications/main/subghz/protocols/megacode.c @@ -0,0 +1,429 @@ +#include "megacode.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://wiki.cuvoodoo.info/doku.php?id=megacode + * https://wiki.cuvoodoo.info/lib/exe/fetch.php?media=megacode:megacode_1.pdf + * https://fccid.io/EF4ACP00872/Test-Report/Megacode-2-112615.pdf + * https://github.com/aaronsp777/megadecoder + * https://github.com/rjmendez/Linear_keyfob + * https://github.com/j07rdi/Linear_MegaCode_Garage_Remote + * + */ + +#define TAG "SubGhzProtocolMegaCode" + +static const SubGhzBlockConst subghz_protocol_megacode_const = { + .te_short = 1000, + .te_long = 1000, + .te_delta = 200, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderMegaCode { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint8_t last_bit; +}; + +struct SubGhzProtocolEncoderMegaCode { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + MegaCodeDecoderStepReset = 0, + MegaCodeDecoderStepFoundStartBit, + MegaCodeDecoderStepSaveDuration, + MegaCodeDecoderStepCheckDuration, +} MegaCodeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_megacode_decoder = { + .alloc = subghz_protocol_decoder_megacode_alloc, + .free = subghz_protocol_decoder_megacode_free, + + .feed = subghz_protocol_decoder_megacode_feed, + .reset = subghz_protocol_decoder_megacode_reset, + + .get_hash_data = subghz_protocol_decoder_megacode_get_hash_data, + .serialize = subghz_protocol_decoder_megacode_serialize, + .deserialize = subghz_protocol_decoder_megacode_deserialize, + .get_string = subghz_protocol_decoder_megacode_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_megacode_encoder = { + .alloc = subghz_protocol_encoder_megacode_alloc, + .free = subghz_protocol_encoder_megacode_free, + + .deserialize = subghz_protocol_encoder_megacode_deserialize, + .stop = subghz_protocol_encoder_megacode_stop, + .yield = subghz_protocol_encoder_megacode_yield, +}; + +const SubGhzProtocol subghz_protocol_megacode = { + .name = SUBGHZ_PROTOCOL_MEGACODE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_megacode_decoder, + .encoder = &subghz_protocol_megacode_encoder, +}; + +void* subghz_protocol_encoder_megacode_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMegaCode* instance = malloc(sizeof(SubGhzProtocolEncoderMegaCode)); + + instance->base.protocol = &subghz_protocol_megacode; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_megacode_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMegaCode* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderMegaCode instance + * @return true On success + */ +static bool subghz_protocol_encoder_megacode_get_upload(SubGhzProtocolEncoderMegaCode* instance) { + furi_assert(instance); + uint8_t last_bit = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + /* + * Due to the nature of the protocol + * + * 00000 1 + * _____|-| = 1 becomes + * + * 00 1 000 + * __|-|___ = 0 becomes + * + * it's easier for us to generate an upload backwards + */ + + size_t index = size_upload - 1; + + // Send end level + instance->encoder.upload[index--] = + level_duration_make(true, (uint32_t)subghz_protocol_megacode_const.te_short); + if(bit_read(instance->generic.data, 0)) { + last_bit = 1; + } else { + last_bit = 0; + } + + //Send key data + for(uint8_t i = 1; i < instance->generic.data_count_bit; i++) { + if(bit_read(instance->generic.data, i)) { + //if bit 1 + instance->encoder.upload[index--] = level_duration_make( + false, + last_bit ? (uint32_t)subghz_protocol_megacode_const.te_short * 5 : + (uint32_t)subghz_protocol_megacode_const.te_short * 2); + last_bit = 1; + } else { + //if bit 0 + instance->encoder.upload[index--] = level_duration_make( + false, + last_bit ? (uint32_t)subghz_protocol_megacode_const.te_short * 8 : + (uint32_t)subghz_protocol_megacode_const.te_short * 5); + last_bit = 0; + } + instance->encoder.upload[index--] = + level_duration_make(true, (uint32_t)subghz_protocol_megacode_const.te_short); + } + + //Send PT_GUARD + if(bit_read(instance->generic.data, 0)) { + //if end bit 1 + instance->encoder.upload[index] = + level_duration_make(false, (uint32_t)subghz_protocol_megacode_const.te_short * 11); + } else { + //if end bit 1 + instance->encoder.upload[index] = + level_duration_make(false, (uint32_t)subghz_protocol_megacode_const.te_short * 14); + } + + return true; +} + +bool subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMegaCode* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_megacode_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_megacode_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_megacode_stop(void* context) { + SubGhzProtocolEncoderMegaCode* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_megacode_yield(void* context) { + SubGhzProtocolEncoderMegaCode* 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_megacode_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMegaCode* instance = malloc(sizeof(SubGhzProtocolDecoderMegaCode)); + instance->base.protocol = &subghz_protocol_megacode; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_megacode_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + free(instance); +} + +void subghz_protocol_decoder_megacode_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + instance->decoder.parser_step = MegaCodeDecoderStepReset; +} + +void subghz_protocol_decoder_megacode_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + switch(instance->decoder.parser_step) { + case MegaCodeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_megacode_const.te_short * 13) < + subghz_protocol_megacode_const.te_delta * 17)) { //10..16ms + //Found header MegaCode + instance->decoder.parser_step = MegaCodeDecoderStepFoundStartBit; + } + break; + case MegaCodeDecoderStepFoundStartBit: + if(level && (DURATION_DIFF(duration, subghz_protocol_megacode_const.te_short) < + subghz_protocol_megacode_const.te_delta)) { + //Found start bit MegaCode + instance->decoder.parser_step = MegaCodeDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->last_bit = 1; + + } else { + instance->decoder.parser_step = MegaCodeDecoderStepReset; + } + break; + case MegaCodeDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_megacode_const.te_short * 10)) { + instance->decoder.parser_step = MegaCodeDecoderStepReset; + if(instance->decoder.decode_count_bit == + subghz_protocol_megacode_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); + } + break; + } + + if(!instance->last_bit) { + instance->decoder.te_last = duration - subghz_protocol_megacode_const.te_short * 3; + } else { + instance->decoder.te_last = duration; + } + instance->decoder.parser_step = MegaCodeDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = MegaCodeDecoderStepReset; + } + break; + case MegaCodeDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_megacode_const.te_short * 5) < + subghz_protocol_megacode_const.te_delta * 5) && + (DURATION_DIFF(duration, subghz_protocol_megacode_const.te_short) < + subghz_protocol_megacode_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->last_bit = 1; + instance->decoder.parser_step = MegaCodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_megacode_const.te_short * 2) < + subghz_protocol_megacode_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_megacode_const.te_short) < + subghz_protocol_megacode_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->last_bit = 0; + instance->decoder.parser_step = MegaCodeDecoderStepSaveDuration; + } else + instance->decoder.parser_step = MegaCodeDecoderStepReset; + } else { + instance->decoder.parser_step = MegaCodeDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_megacode_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * Short: 1000 µs + * Long: 1000 µs + * Gap: 11000 .. 14000 µs + * A Linear Megacode transmission consists of 24 bit frames starting with + * the most significant bit and ending with the least. Each of the 24 bit + * frames is 6 milliseconds wide and always contains a single 1 millisecond + * pulse. A frame with more than 1 pulse or a frame with no pulse is invalid + * and a receiver should reset and begin watching for another start bit. + * Start bit is always 1. + * + * + * Example (I created with my own remote): + * Remote “A” has the code “17316”, a Facility Code of “3”, and a single button. + * Start bit (S) = 1 + * Facility Code 3 (F) = 0011 + * Remote Code (Key) 17316 = 43A4 = 0100001110100100 + * Button (Btn) 1 = 001 + * S F Key Btn + * Result = 1|0011|0100001110100100|001 + * + * 00000 1 + * _____|-| = 1 becomes + * + * 00 1 000 + * __|-|___ = 0 becomes + * + * The device needs to transmit with a 9000 µs gap between retransmissions: + * 000001 001000 001000 000001 000001 001000 000001 001000 001000 001000 001000 000001 + * 000001 000001 001000 000001 001000 001000 000001 001000 001000 001000 001000 000001 + * wait 9000 µs + * 000001 001000 001000 000001 000001 001000 000001 001000 001000 001000 001000 000001 + * 000001 000001 001000 000001 001000 001000 000001 001000 001000 001000 001000 000001 + * + */ + if((instance->data >> 23) == 1) { + instance->serial = (instance->data >> 3) & 0xFFFF; + instance->btn = instance->data & 0b111; + instance->cnt = (instance->data >> 19) & 0b1111; + } else { + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + } +} + +uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_megacode_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_megacode_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderMegaCode* instance = context; + subghz_protocol_megacode_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%06lX\r\n" + "Sn:0x%04lX - %lu\r\n" + "Facility:%lX Btn:%X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)instance->generic.data, + instance->generic.serial, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); +} diff --git a/applications/main/subghz/protocols/megacode.h b/applications/main/subghz/protocols/megacode.h new file mode 100644 index 000000000..e31434fa3 --- /dev/null +++ b/applications/main/subghz/protocols/megacode.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MEGACODE_NAME "MegaCode" + +typedef struct SubGhzProtocolDecoderMegaCode SubGhzProtocolDecoderMegaCode; +typedef struct SubGhzProtocolEncoderMegaCode SubGhzProtocolEncoderMegaCode; + +extern const SubGhzProtocolDecoder subghz_protocol_megacode_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_megacode_encoder; +extern const SubGhzProtocol subghz_protocol_megacode; + +/** + * Allocate SubGhzProtocolEncoderMegaCode. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMegaCode* pointer to a SubGhzProtocolEncoderMegaCode instance + */ +void* subghz_protocol_encoder_megacode_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMegaCode. + * @param context Pointer to a SubGhzProtocolEncoderMegaCode instance + */ +void subghz_protocol_encoder_megacode_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMegaCode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMegaCode instance + */ +void subghz_protocol_encoder_megacode_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMegaCode instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_megacode_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMegaCode. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMegaCode* pointer to a SubGhzProtocolDecoderMegaCode instance + */ +void* subghz_protocol_decoder_megacode_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMegaCode. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + */ +void subghz_protocol_decoder_megacode_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMegaCode. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + */ +void subghz_protocol_decoder_megacode_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_megacode_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMegaCode. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_megacode_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMegaCode. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance + * @param output Resulting text + */ +void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/nero_radio.c b/applications/main/subghz/protocols/nero_radio.c new file mode 100644 index 000000000..c8126b1e1 --- /dev/null +++ b/applications/main/subghz/protocols/nero_radio.c @@ -0,0 +1,397 @@ +#include "nero_radio.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolNeroRadio" + +static const SubGhzBlockConst subghz_protocol_nero_radio_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 80, + .min_count_bit_for_found = 56, +}; + +struct SubGhzProtocolDecoderNeroRadio { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderNeroRadio { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NeroRadioDecoderStepReset = 0, + NeroRadioDecoderStepCheckPreambula, + NeroRadioDecoderStepSaveDuration, + NeroRadioDecoderStepCheckDuration, +} NeroRadioDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nero_radio_decoder = { + .alloc = subghz_protocol_decoder_nero_radio_alloc, + .free = subghz_protocol_decoder_nero_radio_free, + + .feed = subghz_protocol_decoder_nero_radio_feed, + .reset = subghz_protocol_decoder_nero_radio_reset, + + .get_hash_data = subghz_protocol_decoder_nero_radio_get_hash_data, + .serialize = subghz_protocol_decoder_nero_radio_serialize, + .deserialize = subghz_protocol_decoder_nero_radio_deserialize, + .get_string = subghz_protocol_decoder_nero_radio_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nero_radio_encoder = { + .alloc = subghz_protocol_encoder_nero_radio_alloc, + .free = subghz_protocol_encoder_nero_radio_free, + + .deserialize = subghz_protocol_encoder_nero_radio_deserialize, + .stop = subghz_protocol_encoder_nero_radio_stop, + .yield = subghz_protocol_encoder_nero_radio_yield, +}; + +const SubGhzProtocol subghz_protocol_nero_radio = { + .name = SUBGHZ_PROTOCOL_NERO_RADIO_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nero_radio_decoder, + .encoder = &subghz_protocol_nero_radio_encoder, +}; + +void* subghz_protocol_encoder_nero_radio_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderNeroRadio* instance = malloc(sizeof(SubGhzProtocolEncoderNeroRadio)); + + instance->base.protocol = &subghz_protocol_nero_radio; + 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_nero_radio_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNeroRadio* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNeroRadio instance + * @return true On success + */ +static bool + subghz_protocol_encoder_nero_radio_get_upload(SubGhzProtocolEncoderNeroRadio* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = 49 * 2 + 2 + (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 0; i < 49; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short); + } + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short * 4); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short); + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short * 37); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short * 37); + } + return true; +} + +bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNeroRadio* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_nero_radio_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_nero_radio_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nero_radio_stop(void* context) { + SubGhzProtocolEncoderNeroRadio* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_nero_radio_yield(void* context) { + SubGhzProtocolEncoderNeroRadio* 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_nero_radio_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderNeroRadio* instance = malloc(sizeof(SubGhzProtocolDecoderNeroRadio)); + instance->base.protocol = &subghz_protocol_nero_radio; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nero_radio_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nero_radio_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + instance->decoder.parser_step = NeroRadioDecoderStepReset; +} + +void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + + switch(instance->decoder.parser_step) { + case NeroRadioDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta)) { + instance->decoder.parser_step = NeroRadioDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case NeroRadioDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short * 4) < + subghz_protocol_nero_radio_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else if( + DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) { + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short * 4) < + subghz_protocol_nero_radio_const.te_delta) { + // Found start bit + if(instance->header_count > 40) { + instance->decoder.parser_step = NeroRadioDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + break; + case NeroRadioDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = NeroRadioDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + break; + case NeroRadioDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_nero_radio_const.te_short * 10 + + subghz_protocol_nero_radio_const.te_delta * 2)) { + //Found stop bit + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_long) < + subghz_protocol_nero_radio_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + instance->decoder.parser_step = NeroRadioDecoderStepReset; + if(instance->decoder.decode_count_bit == + subghz_protocol_nero_radio_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 = NeroRadioDecoderStepReset; //-V1048 + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_long) < + subghz_protocol_nero_radio_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NeroRadioDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_long) < + subghz_protocol_nero_radio_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NeroRadioDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nero_radio_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_nero_radio_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Yek:0x%lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + code_found_reverse_lo); +} diff --git a/applications/main/subghz/protocols/nero_radio.h b/applications/main/subghz/protocols/nero_radio.h new file mode 100644 index 000000000..361da6173 --- /dev/null +++ b/applications/main/subghz/protocols/nero_radio.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NERO_RADIO_NAME "Nero Radio" + +typedef struct SubGhzProtocolDecoderNeroRadio SubGhzProtocolDecoderNeroRadio; +typedef struct SubGhzProtocolEncoderNeroRadio SubGhzProtocolEncoderNeroRadio; + +extern const SubGhzProtocolDecoder subghz_protocol_nero_radio_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nero_radio_encoder; +extern const SubGhzProtocol subghz_protocol_nero_radio; + +/** + * Allocate SubGhzProtocolEncoderNeroRadio. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNeroRadio* pointer to a SubGhzProtocolEncoderNeroRadio instance + */ +void* subghz_protocol_encoder_nero_radio_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNeroRadio. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + */ +void subghz_protocol_encoder_nero_radio_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + */ +void subghz_protocol_encoder_nero_radio_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_nero_radio_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNeroRadio. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNeroRadio* pointer to a SubGhzProtocolDecoderNeroRadio instance + */ +void* subghz_protocol_decoder_nero_radio_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + */ +void subghz_protocol_decoder_nero_radio_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + */ +void subghz_protocol_decoder_nero_radio_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_nero_radio_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param output Resulting text + */ +void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/nero_sketch.c b/applications/main/subghz/protocols/nero_sketch.c new file mode 100644 index 000000000..b124b717b --- /dev/null +++ b/applications/main/subghz/protocols/nero_sketch.c @@ -0,0 +1,382 @@ +#include "nero_sketch.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolNeroSketch" + +static const SubGhzBlockConst subghz_protocol_nero_sketch_const = { + .te_short = 330, + .te_long = 660, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct SubGhzProtocolDecoderNeroSketch { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderNeroSketch { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NeroSketchDecoderStepReset = 0, + NeroSketchDecoderStepCheckPreambula, + NeroSketchDecoderStepSaveDuration, + NeroSketchDecoderStepCheckDuration, +} NeroSketchDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nero_sketch_decoder = { + .alloc = subghz_protocol_decoder_nero_sketch_alloc, + .free = subghz_protocol_decoder_nero_sketch_free, + + .feed = subghz_protocol_decoder_nero_sketch_feed, + .reset = subghz_protocol_decoder_nero_sketch_reset, + + .get_hash_data = subghz_protocol_decoder_nero_sketch_get_hash_data, + .serialize = subghz_protocol_decoder_nero_sketch_serialize, + .deserialize = subghz_protocol_decoder_nero_sketch_deserialize, + .get_string = subghz_protocol_decoder_nero_sketch_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nero_sketch_encoder = { + .alloc = subghz_protocol_encoder_nero_sketch_alloc, + .free = subghz_protocol_encoder_nero_sketch_free, + + .deserialize = subghz_protocol_encoder_nero_sketch_deserialize, + .stop = subghz_protocol_encoder_nero_sketch_stop, + .yield = subghz_protocol_encoder_nero_sketch_yield, +}; + +const SubGhzProtocol subghz_protocol_nero_sketch = { + .name = SUBGHZ_PROTOCOL_NERO_SKETCH_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nero_sketch_decoder, + .encoder = &subghz_protocol_nero_sketch_encoder, +}; + +void* subghz_protocol_encoder_nero_sketch_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderNeroSketch* instance = malloc(sizeof(SubGhzProtocolEncoderNeroSketch)); + + instance->base.protocol = &subghz_protocol_nero_sketch; + 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_nero_sketch_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNeroSketch* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNeroSketch instance + * @return true On success + */ +static bool + subghz_protocol_encoder_nero_sketch_get_upload(SubGhzProtocolEncoderNeroSketch* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = 47 * 2 + 2 + (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 0; i < 47; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + } + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short * 4); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_long); + } + } + + //Send stop bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + + return true; +} + +bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNeroSketch* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_nero_sketch_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_nero_sketch_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nero_sketch_stop(void* context) { + SubGhzProtocolEncoderNeroSketch* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_nero_sketch_yield(void* context) { + SubGhzProtocolEncoderNeroSketch* 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_nero_sketch_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderNeroSketch* instance = malloc(sizeof(SubGhzProtocolDecoderNeroSketch)); + instance->base.protocol = &subghz_protocol_nero_sketch; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nero_sketch_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nero_sketch_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + instance->decoder.parser_step = NeroSketchDecoderStepReset; +} + +void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + + switch(instance->decoder.parser_step) { + case NeroSketchDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta)) { + instance->decoder.parser_step = NeroSketchDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case NeroSketchDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short * 4) < + subghz_protocol_nero_sketch_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else if( + DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) { + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_short * 4) < + subghz_protocol_nero_sketch_const.te_delta) { + // Found start bit + if(instance->header_count > 40) { + instance->decoder.parser_step = NeroSketchDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + break; + case NeroSketchDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_nero_sketch_const.te_short * 2 + + subghz_protocol_nero_sketch_const.te_delta * 2)) { + //Found stop bit + instance->decoder.parser_step = NeroSketchDecoderStepReset; + if(instance->decoder.decode_count_bit == + subghz_protocol_nero_sketch_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = NeroSketchDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + break; + case NeroSketchDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_long) < + subghz_protocol_nero_sketch_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NeroSketchDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_long) < + subghz_protocol_nero_sketch_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NeroSketchDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nero_sketch_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_nero_sketch_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_nero_sketch_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Yek:0x%lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + code_found_reverse_lo); +} diff --git a/applications/main/subghz/protocols/nero_sketch.h b/applications/main/subghz/protocols/nero_sketch.h new file mode 100644 index 000000000..ac87fb00a --- /dev/null +++ b/applications/main/subghz/protocols/nero_sketch.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NERO_SKETCH_NAME "Nero Sketch" + +typedef struct SubGhzProtocolDecoderNeroSketch SubGhzProtocolDecoderNeroSketch; +typedef struct SubGhzProtocolEncoderNeroSketch SubGhzProtocolEncoderNeroSketch; + +extern const SubGhzProtocolDecoder subghz_protocol_nero_sketch_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nero_sketch_encoder; +extern const SubGhzProtocol subghz_protocol_nero_sketch; + +/** + * Allocate SubGhzProtocolEncoderNeroSketch. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNeroSketch* pointer to a SubGhzProtocolEncoderNeroSketch instance + */ +void* subghz_protocol_encoder_nero_sketch_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNeroSketch. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + */ +void subghz_protocol_encoder_nero_sketch_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + */ +void subghz_protocol_encoder_nero_sketch_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_nero_sketch_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNeroSketch. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNeroSketch* pointer to a SubGhzProtocolDecoderNeroSketch instance + */ +void* subghz_protocol_decoder_nero_sketch_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + */ +void subghz_protocol_decoder_nero_sketch_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + */ +void subghz_protocol_decoder_nero_sketch_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_nero_sketch_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param output Resulting text + */ +void subghz_protocol_decoder_nero_sketch_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/nice_flo.c b/applications/main/subghz/protocols/nice_flo.c new file mode 100644 index 000000000..a57d5f4da --- /dev/null +++ b/applications/main/subghz/protocols/nice_flo.c @@ -0,0 +1,330 @@ +#include "nice_flo.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolNiceFLO" + +static const SubGhzBlockConst subghz_protocol_nice_flo_const = { + .te_short = 700, + .te_long = 1400, + .te_delta = 200, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderNiceFlo { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderNiceFlo { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NiceFloDecoderStepReset = 0, + NiceFloDecoderStepFoundStartBit, + NiceFloDecoderStepSaveDuration, + NiceFloDecoderStepCheckDuration, +} NiceFloDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nice_flo_decoder = { + .alloc = subghz_protocol_decoder_nice_flo_alloc, + .free = subghz_protocol_decoder_nice_flo_free, + + .feed = subghz_protocol_decoder_nice_flo_feed, + .reset = subghz_protocol_decoder_nice_flo_reset, + + .get_hash_data = subghz_protocol_decoder_nice_flo_get_hash_data, + .serialize = subghz_protocol_decoder_nice_flo_serialize, + .deserialize = subghz_protocol_decoder_nice_flo_deserialize, + .get_string = subghz_protocol_decoder_nice_flo_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nice_flo_encoder = { + .alloc = subghz_protocol_encoder_nice_flo_alloc, + .free = subghz_protocol_encoder_nice_flo_free, + + .deserialize = subghz_protocol_encoder_nice_flo_deserialize, + .stop = subghz_protocol_encoder_nice_flo_stop, + .yield = subghz_protocol_encoder_nice_flo_yield, +}; + +const SubGhzProtocol subghz_protocol_nice_flo = { + .name = SUBGHZ_PROTOCOL_NICE_FLO_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nice_flo_decoder, + .encoder = &subghz_protocol_nice_flo_encoder, +}; + +void* subghz_protocol_encoder_nice_flo_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderNiceFlo* instance = malloc(sizeof(SubGhzProtocolEncoderNiceFlo)); + + instance->base.protocol = &subghz_protocol_nice_flo; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_nice_flo_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlo* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNiceFlo instance + * @return true On success + */ +static bool subghz_protocol_encoder_nice_flo_get_upload(SubGhzProtocolEncoderNiceFlo* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flo_const.te_short * 36); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flo_const.te_short); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flo_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flo_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flo_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flo_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlo* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if((instance->generic.data_count_bit < + subghz_protocol_nice_flo_const.min_count_bit_for_found) || + (instance->generic.data_count_bit > + 2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_nice_flo_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nice_flo_stop(void* context) { + SubGhzProtocolEncoderNiceFlo* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_nice_flo_yield(void* context) { + SubGhzProtocolEncoderNiceFlo* 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_nice_flo_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderNiceFlo* instance = malloc(sizeof(SubGhzProtocolDecoderNiceFlo)); + instance->base.protocol = &subghz_protocol_nice_flo; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nice_flo_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nice_flo_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + instance->decoder.parser_step = NiceFloDecoderStepReset; +} + +void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + + switch(instance->decoder.parser_step) { + case NiceFloDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short * 36) < + subghz_protocol_nice_flo_const.te_delta * 36)) { + //Found header Nice Flo + instance->decoder.parser_step = NiceFloDecoderStepFoundStartBit; + } + break; + case NiceFloDecoderStepFoundStartBit: + if(!level) { + break; + } else if( + DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short) < + subghz_protocol_nice_flo_const.te_delta) { + //Found start bit Nice Flo + instance->decoder.parser_step = NiceFloDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NiceFloDecoderStepReset; + } + break; + case NiceFloDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_nice_flo_const.te_short * 4)) { + instance->decoder.parser_step = NiceFloDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_nice_flo_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + 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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = NiceFloDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = NiceFloDecoderStepReset; + } + break; + case NiceFloDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nice_flo_const.te_short) < + subghz_protocol_nice_flo_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_long) < + subghz_protocol_nice_flo_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NiceFloDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nice_flo_const.te_long) < + subghz_protocol_nice_flo_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short) < + subghz_protocol_nice_flo_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NiceFloDecoderStepSaveDuration; + } else + instance->decoder.parser_step = NiceFloDecoderStepReset; + } else { + instance->decoder.parser_step = NiceFloDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nice_flo_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if((instance->generic.data_count_bit < + subghz_protocol_nice_flo_const.min_count_bit_for_found) || + (instance->generic.data_count_bit > + 2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_nice_flo_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); +} diff --git a/applications/main/subghz/protocols/nice_flo.h b/applications/main/subghz/protocols/nice_flo.h new file mode 100644 index 000000000..e382e6146 --- /dev/null +++ b/applications/main/subghz/protocols/nice_flo.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NICE_FLO_NAME "Nice FLO" + +typedef struct SubGhzProtocolDecoderNiceFlo SubGhzProtocolDecoderNiceFlo; +typedef struct SubGhzProtocolEncoderNiceFlo SubGhzProtocolEncoderNiceFlo; + +extern const SubGhzProtocolDecoder subghz_protocol_nice_flo_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nice_flo_encoder; +extern const SubGhzProtocol subghz_protocol_nice_flo; + +/** + * Allocate SubGhzProtocolEncoderNiceFlo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNiceFlo* pointer to a SubGhzProtocolEncoderNiceFlo instance + */ +void* subghz_protocol_encoder_nice_flo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNiceFlo. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + */ +void subghz_protocol_encoder_nice_flo_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + */ +void subghz_protocol_encoder_nice_flo_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_nice_flo_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNiceFlo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNiceFlo* pointer to a SubGhzProtocolDecoderNiceFlo instance + */ +void* subghz_protocol_decoder_nice_flo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + */ +void subghz_protocol_decoder_nice_flo_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + */ +void subghz_protocol_decoder_nice_flo_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_nice_flo_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param output Resulting text + */ +void subghz_protocol_decoder_nice_flo_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/nice_flor_s.c b/applications/main/subghz/protocols/nice_flor_s.c new file mode 100644 index 000000000..6447676cc --- /dev/null +++ b/applications/main/subghz/protocols/nice_flor_s.c @@ -0,0 +1,694 @@ +#include "nice_flor_s.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * https://phreakerclub.com/1615 + * https://phreakerclub.com/forum/showthread.php?t=2360 + * https://vrtp.ru/index.php?showtopic=27867 + */ + +#define TAG "SubGhzProtocolNiceFlorS" + +#define NICE_ONE_COUNT_BIT 72 +#define NICE_ONE_NAME "Nice One" + +static const SubGhzBlockConst subghz_protocol_nice_flor_s_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 300, + .min_count_bit_for_found = 52, +}; + +struct SubGhzProtocolDecoderNiceFlorS { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + const char* nice_flor_s_rainbow_table_file_name; + uint64_t data; +}; + +struct SubGhzProtocolEncoderNiceFlorS { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + const char* nice_flor_s_rainbow_table_file_name; +}; + +typedef enum { + NiceFlorSDecoderStepReset = 0, + NiceFlorSDecoderStepCheckHeader, + NiceFlorSDecoderStepFoundHeader, + NiceFlorSDecoderStepSaveDuration, + NiceFlorSDecoderStepCheckDuration, +} NiceFlorSDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder = { + .alloc = subghz_protocol_decoder_nice_flor_s_alloc, + .free = subghz_protocol_decoder_nice_flor_s_free, + + .feed = subghz_protocol_decoder_nice_flor_s_feed, + .reset = subghz_protocol_decoder_nice_flor_s_reset, + + .get_hash_data = subghz_protocol_decoder_nice_flor_s_get_hash_data, + .serialize = subghz_protocol_decoder_nice_flor_s_serialize, + .deserialize = subghz_protocol_decoder_nice_flor_s_deserialize, + .get_string = subghz_protocol_decoder_nice_flor_s_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nice_flor_s_encoder = { + .alloc = subghz_protocol_encoder_nice_flor_s_alloc, + .free = subghz_protocol_encoder_nice_flor_s_free, + + .deserialize = subghz_protocol_encoder_nice_flor_s_deserialize, + .stop = subghz_protocol_encoder_nice_flor_s_stop, + .yield = subghz_protocol_encoder_nice_flor_s_yield, +}; + +const SubGhzProtocol subghz_protocol_nice_flor_s = { + .name = SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nice_flor_s_decoder, + .encoder = &subghz_protocol_nice_flor_s_encoder, +}; + +static void subghz_protocol_nice_flor_s_remote_controller( + SubGhzBlockGeneric* instance, + const char* file_name); + +void* subghz_protocol_encoder_nice_flor_s_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderNiceFlorS* instance = malloc(sizeof(SubGhzProtocolEncoderNiceFlorS)); + + instance->base.protocol = &subghz_protocol_nice_flor_s; + instance->generic.protocol_name = instance->base.protocol->name; + instance->nice_flor_s_rainbow_table_file_name = + subghz_environment_get_nice_flor_s_rainbow_table_file_name(environment); + if(instance->nice_flor_s_rainbow_table_file_name) { + FURI_LOG_D( + TAG, "Loading rainbow table from %s", instance->nice_flor_s_rainbow_table_file_name); + } + instance->encoder.repeat = 10; + instance->encoder.size_upload = 1728; //wrong!! upload 186*16 = 2976 - actual size about 1728 + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_nice_flor_s_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlorS* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNiceFlorS instance + * @return true On success + */ +static void subghz_protocol_encoder_nice_flor_s_get_upload( + SubGhzProtocolEncoderNiceFlorS* instance, + uint8_t btn, + const char* file_name) { + furi_assert(instance); + size_t index = 0; + btn = instance->generic.btn; + + size_t size_upload = ((instance->generic.data_count_bit * 2) + ((37 + 2 + 2) * 2) * 16); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + } else { + instance->encoder.size_upload = size_upload; + } + + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + uint64_t decrypt = ((uint64_t)instance->generic.serial << 16) | instance->generic.cnt; + uint64_t enc_part = subghz_protocol_nice_flor_s_encrypt(decrypt, file_name); + + for(int i = 0; i < 16; i++) { + static const uint64_t loops[16] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; + + uint8_t byte; + + byte = btn << 4 | (0xF ^ btn ^ loops[i]); + instance->generic.data = (uint64_t)byte << 44 | enc_part; + + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flor_s_const.te_short * 37); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flor_s_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flor_s_const.te_short * 3); + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flor_s_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_nice_flor_s_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_nice_flor_s_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_nice_flor_s_const.te_long); + } + } + //Send stop bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flor_s_const.te_short * 3); + //instance->encoder.upload[index++] = + //level_duration_make(false, (uint32_t)subghz_protocol_nice_flor_s_const.te_short * 3); + } + instance->encoder.size_upload = index; +} + +bool subghz_protocol_encoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlorS* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_nice_flor_s_remote_controller( + &instance->generic, instance->nice_flor_s_rainbow_table_file_name); + subghz_protocol_encoder_nice_flor_s_get_upload( + instance, instance->generic.btn, instance->nice_flor_s_rainbow_table_file_name); + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nice_flor_s_stop(void* context) { + SubGhzProtocolEncoderNiceFlorS* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_nice_flor_s_yield(void* context) { + SubGhzProtocolEncoderNiceFlorS* 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; +} + +// /** +// * Read bytes from rainbow table +// * @param p array[10] P0-P1|P2-P3-P4-P5-P6-P7-P8-P9-P10 +// * @return crc +// */ +// static uint32_t subghz_protocol_nice_one_crc(uint8_t* p) { +// uint8_t crc = 0; +// uint8_t crc_data = 0xff; +// for(uint8_t i = 4; i < 68; i++) { +// if(subghz_protocol_blocks_get_bit_array(p, i)) { +// crc = crc_data ^ 1; +// } else { +// crc = crc_data; +// } +// crc_data >>= 1; +// if((crc & 0x01)) { +// crc_data ^= 0x97; +// } +// } +// crc = 0; +// for(uint8_t i = 0; i < 8; i++) { +// crc <<= 1; +// if((crc_data >> i) & 0x01) crc = crc | 1; +// } +// return crc; +// } + +// /** +// * Read bytes from rainbow table +// * @param p array[10] P0-P1|P2-P3-P4-P5-P6-P7-XX-XX-XX +// * @param num_parcel parcel number 0..15 +// * @param hold_bit 0 - the button was only pressed, 1 - the button was held down +// */ +// static void subghz_protocol_nice_one_get_data(uint8_t* p, uint8_t num_parcel, uint8_t hold_bit) { +// uint8_t k = 0; +// uint8_t crc = 0; +// p[1] = (p[1] & 0x0f) | ((0x0f ^ (p[0] & 0x0F) ^ num_parcel) << 4); +// if(num_parcel < 4) { +// k = 0x8f; +// } else { +// k = 0x80; +// } + +// if(!hold_bit) { +// hold_bit = 0; +// } else { +// hold_bit = 0x10; +// } +// k = num_parcel ^ k; +// p[7] = k; +// p[8] = hold_bit ^ (k << 4); + +// crc = subghz_protocol_nice_one_crc(p); + +// p[8] |= crc >> 4; +// p[9] = crc << 4; +// } + +/** + * Read bytes from rainbow table + * @param file_name Full path to rainbow table the file + * @param address Byte address in file + * @return data + */ +static uint8_t + subghz_protocol_nice_flor_s_get_byte_in_file(const char* file_name, uint32_t address) { + if(!file_name) return 0; + + uint8_t buffer[1] = {0}; + if(subghz_keystore_raw_get_data(file_name, address, buffer, sizeof(uint8_t))) { + return buffer[0]; + } else { + return 0; + } +} + +static inline void subghz_protocol_decoder_nice_flor_s_magic_xor(uint8_t* p, uint8_t k) { + for(uint8_t i = 1; i < 6; i++) { + p[i] ^= k; + } +} + +uint64_t subghz_protocol_nice_flor_s_encrypt(uint64_t data, const char* file_name) { + uint8_t* p = (uint8_t*)&data; + + uint8_t k = 0; + for(uint8_t y = 0; y < 2; y++) { + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] & 0x1f); + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0xe0; + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] >> 3) + 0x25; + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0x7; + if(y == 0) { + k = p[0]; + p[0] = p[1]; + p[1] = k; + } + } + + p[5] = ~p[5] & 0x0f; + k = ~p[4]; + p[4] = ~p[0]; + p[0] = ~p[2]; + p[2] = k; + k = ~p[3]; + p[3] = ~p[1]; + p[1] = k; + + return data; +} + +static uint64_t + subghz_protocol_nice_flor_s_decrypt(SubGhzBlockGeneric* instance, const char* file_name) { + furi_assert(instance); + uint64_t data = instance->data; + uint8_t* p = (uint8_t*)&data; + + uint8_t k = 0; + + k = ~p[4]; + p[5] = ~p[5]; + p[4] = ~p[2]; + p[2] = ~p[0]; + p[0] = k; + k = ~p[3]; + p[3] = ~p[1]; + p[1] = k; + + for(uint8_t y = 0; y < 2; y++) { + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] >> 3) + 0x25; + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0x7; + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] & 0x1f); + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0xe0; + + if(y == 0) { + k = p[0]; + p[0] = p[1]; + p[1] = k; + } + } + + return data; +} + +bool subghz_protocol_nice_flor_s_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlorS* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.data_count_bit = 52; + uint64_t decrypt = ((uint64_t)instance->generic.serial << 16) | instance->generic.cnt; + uint64_t enc_part = subghz_protocol_nice_flor_s_encrypt( + decrypt, instance->nice_flor_s_rainbow_table_file_name); + uint8_t byte = btn << 4 | (0xF ^ btn ^ 0x3); + instance->generic.data = (uint64_t)byte << 44 | enc_part; + + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + return res; +} + +void* subghz_protocol_decoder_nice_flor_s_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderNiceFlorS* instance = malloc(sizeof(SubGhzProtocolDecoderNiceFlorS)); + instance->base.protocol = &subghz_protocol_nice_flor_s; + instance->generic.protocol_name = instance->base.protocol->name; + instance->nice_flor_s_rainbow_table_file_name = + subghz_environment_get_nice_flor_s_rainbow_table_file_name(environment); + if(instance->nice_flor_s_rainbow_table_file_name) { + FURI_LOG_D( + TAG, "Loading rainbow table from %s", instance->nice_flor_s_rainbow_table_file_name); + } + return instance; +} + +void subghz_protocol_decoder_nice_flor_s_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + instance->nice_flor_s_rainbow_table_file_name = NULL; + free(instance); +} + +void subghz_protocol_decoder_nice_flor_s_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + instance->decoder.parser_step = NiceFlorSDecoderStepReset; +} + +void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + + switch(instance->decoder.parser_step) { + case NiceFlorSDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 38) < + subghz_protocol_nice_flor_s_const.te_delta * 38)) { + //Found start header Nice Flor-S + instance->decoder.parser_step = NiceFlorSDecoderStepCheckHeader; + } + break; + case NiceFlorSDecoderStepCheckHeader: + if((level) && (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 3) < + subghz_protocol_nice_flor_s_const.te_delta * 3)) { + //Found next header Nice Flor-S + instance->decoder.parser_step = NiceFlorSDecoderStepFoundHeader; + } else { + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } + break; + case NiceFlorSDecoderStepFoundHeader: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 3) < + subghz_protocol_nice_flor_s_const.te_delta * 3)) { + //Found header Nice Flor-S + instance->decoder.parser_step = NiceFlorSDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } + break; + case NiceFlorSDecoderStepSaveDuration: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 3) < + subghz_protocol_nice_flor_s_const.te_delta) { + //Found STOP bit + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + if((instance->decoder.decode_count_bit == + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == NICE_ONE_COUNT_BIT)) { + instance->generic.data = instance->data; + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = instance->generic.data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } else { + //save interval + instance->decoder.te_last = duration; + instance->decoder.parser_step = NiceFlorSDecoderStepCheckDuration; + } + } + break; + case NiceFlorSDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nice_flor_s_const.te_short) < + subghz_protocol_nice_flor_s_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_long) < + subghz_protocol_nice_flor_s_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NiceFlorSDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nice_flor_s_const.te_long) < + subghz_protocol_nice_flor_s_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short) < + subghz_protocol_nice_flor_s_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NiceFlorSDecoderStepSaveDuration; + } else + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } else { + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } + if(instance->decoder.decode_count_bit == + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file + */ +static void subghz_protocol_nice_flor_s_remote_controller( + SubGhzBlockGeneric* instance, + const char* file_name) { + /* + * Protocol Nice Flor-S + * Packet format Nice Flor-s: START-P0-P1-P2-P3-P4-P5-P6-P7-STOP + * P0 (4-bit) - button positional code - 1:0x1, 2:0x2, 3:0x4, 4:0x8; + * P1 (4-bit) - batch repetition number, calculated by the formula: + * P1 = 0xF ^ P0 ^ n; where n changes from 1 to 15, then 0, and then in a circle + * key 1: {0xE,0xF,0xC,0xD,0xA,0xB,0x8,0x9,0x6,0x7,0x4,0x5,0x2,0x3,0x0,0x1}; + * key 2: {0xD,0xC,0xF,0xE,0x9,0x8,0xB,0xA,0x5,0x4,0x7,0x6,0x1,0x0,0x3,0x2}; + * key 3: {0xB,0xA,0x9,0x8,0xF,0xE,0xD,0xC,0x3,0x2,0x1,0x0,0x7,0x6,0x5,0x4}; + * key 4: {0x7,0x6,0x5,0x4,0x3,0x2,0x1,0x0,0xF,0xE,0xD,0xC,0xB,0xA,0x9,0x8}; + * P2 (4-bit) - part of the serial number, P2 = (K ^ S3) & 0xF; + * P3 (byte) - the major part of the encrypted index + * P4 (byte) - the low-order part of the encrypted index + * P5 (byte) - part of the serial number, P5 = K ^ S2; + * P6 (byte) - part of the serial number, P6 = K ^ S1; + * P7 (byte) - part of the serial number, P7 = K ^ S0; + * K (byte) - depends on P3 and P4, K = Fk(P3, P4); + * S3,S2,S1,S0 - serial number of the console 28 bit. + * + * data => 0x1c5783607f7b3 key serial cnt + * decrypt => 0x10436c6820444 => 0x1 0436c682 0444 + * + * Protocol Nice One + * Generally repeats the Nice Flor-S protocol, but there are a few changes + * Packet format first 52 bytes repeat Nice Flor-S protocol + * The additional 20 bytes contain the code of the pressed button, + * the button hold bit and the CRC of the entire message. + * START-P0-P1-P2-P3-P4-P5-P6-P7-P8-P9-P10-STOP + * P7 (byte) - if (n<4) k=0x8f : k=0x80; P7= k^n; + * P8 (byte) - if (hold bit) b=0x00 : b=0x10; P8= b^(k<<4) | 4 hi bit crc + * P10 (4-bit) - 4 lo bit crc + * key+b crc + * data => 0x1724A7D9A522F 899 D6 hold bit = 0 - just pressed the button + * data => 0x1424A7D9A522F 8AB 03 hold bit = 1 - button hold + * + * A small button hold counter (0..15) is stored between each press, + * i.e. if 1 press of the button stops counter 6, then the next press + * of the button will start from the value 7 (hold bit = 0), 8 (hold bit = 1)... + * further up to 15 with overflow + * + */ + if(!file_name) { + instance->cnt = 0; + instance->serial = 0; + instance->btn = 0; + } else { + uint64_t decrypt = subghz_protocol_nice_flor_s_decrypt(instance, file_name); + instance->cnt = decrypt & 0xFFFF; + instance->serial = (decrypt >> 16) & 0xFFFFFFF; + instance->btn = (decrypt >> 48) & 0xF; + } +} + +uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nice_flor_s_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { + if(res && + !flipper_format_write_uint32(flipper_format, "Data", (uint32_t*)&instance->data, 1)) { + FURI_LOG_E(TAG, "Unable to add Data"); + res = false; + } + } + return res; +} + +bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if((instance->generic.data_count_bit != + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) && + (instance->generic.data_count_bit != NICE_ONE_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint32_t temp = 0; + if(!flipper_format_read_uint32(flipper_format, "Data", (uint32_t*)&temp, 1)) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + instance->data = (uint64_t)temp; + } + + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + + subghz_protocol_nice_flor_s_remote_controller( + &instance->generic, instance->nice_flor_s_rainbow_table_file_name); + + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%013llX%llX\r\n" + "Sn:%05lX\r\n" + "Cnt:%04lX Btn:%02X\r\n", + NICE_ONE_NAME, + instance->generic.data_count_bit, + instance->generic.data, + instance->data, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } else { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%013llX\r\n" + "Sn:%05lX\r\n" + "Cnt:%04lX Btn:%02X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } +} diff --git a/applications/main/subghz/protocols/nice_flor_s.h b/applications/main/subghz/protocols/nice_flor_s.h new file mode 100644 index 000000000..e333fc979 --- /dev/null +++ b/applications/main/subghz/protocols/nice_flor_s.h @@ -0,0 +1,127 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME "Nice FloR-S" + +typedef struct SubGhzProtocolDecoderNiceFlorS SubGhzProtocolDecoderNiceFlorS; +typedef struct SubGhzProtocolEncoderNiceFlorS SubGhzProtocolEncoderNiceFlorS; + +extern const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nice_flor_s_encoder; +extern const SubGhzProtocol subghz_protocol_nice_flor_s; + +/** + * Allocate SubGhzProtocolEncoderNiceFlorS. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNiceFlorS* pointer to a SubGhzProtocolEncoderNiceFlorS instance + */ +void* subghz_protocol_encoder_nice_flor_s_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + */ +void subghz_protocol_encoder_nice_flor_s_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + */ +void subghz_protocol_encoder_nice_flor_s_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_nice_flor_s_yield(void* context); + +uint64_t subghz_protocol_nice_flor_s_encrypt(uint64_t data, const char* file_name); + +/** + * New remote generation. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_nice_flor_s_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * Allocate SubGhzProtocolDecoderNiceFlorS. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNiceFlorS* pointer to a SubGhzProtocolDecoderNiceFlorS instance + */ +void* subghz_protocol_decoder_nice_flor_s_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + */ +void subghz_protocol_decoder_nice_flor_s_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + */ +void subghz_protocol_decoder_nice_flor_s_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_nice_flor_s_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param output Resulting text + */ +void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/phoenix_v2.c b/applications/main/subghz/protocols/phoenix_v2.c new file mode 100644 index 000000000..b3d6f1e98 --- /dev/null +++ b/applications/main/subghz/protocols/phoenix_v2.c @@ -0,0 +1,339 @@ +#include "phoenix_v2.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolPhoenix_V2" + +//transmission only static mode + +static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = { + .te_short = 427, + .te_long = 853, + .te_delta = 100, + .min_count_bit_for_found = 52, +}; + +struct SubGhzProtocolDecoderPhoenix_V2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderPhoenix_V2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Phoenix_V2DecoderStepReset = 0, + Phoenix_V2DecoderStepFoundStartBit, + Phoenix_V2DecoderStepSaveDuration, + Phoenix_V2DecoderStepCheckDuration, +} Phoenix_V2DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_phoenix_v2_decoder = { + .alloc = subghz_protocol_decoder_phoenix_v2_alloc, + .free = subghz_protocol_decoder_phoenix_v2_free, + + .feed = subghz_protocol_decoder_phoenix_v2_feed, + .reset = subghz_protocol_decoder_phoenix_v2_reset, + + .get_hash_data = subghz_protocol_decoder_phoenix_v2_get_hash_data, + .serialize = subghz_protocol_decoder_phoenix_v2_serialize, + .deserialize = subghz_protocol_decoder_phoenix_v2_deserialize, + .get_string = subghz_protocol_decoder_phoenix_v2_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_phoenix_v2_encoder = { + .alloc = subghz_protocol_encoder_phoenix_v2_alloc, + .free = subghz_protocol_encoder_phoenix_v2_free, + + .deserialize = subghz_protocol_encoder_phoenix_v2_deserialize, + .stop = subghz_protocol_encoder_phoenix_v2_stop, + .yield = subghz_protocol_encoder_phoenix_v2_yield, +}; + +const SubGhzProtocol subghz_protocol_phoenix_v2 = { + .name = SUBGHZ_PROTOCOL_PHOENIX_V2_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_phoenix_v2_decoder, + .encoder = &subghz_protocol_phoenix_v2_encoder, +}; + +void* subghz_protocol_encoder_phoenix_v2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderPhoenix_V2* instance = malloc(sizeof(SubGhzProtocolEncoderPhoenix_V2)); + + instance->base.protocol = &subghz_protocol_phoenix_v2; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_phoenix_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderPhoenix_V2* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + * @return true On success + */ +static bool + subghz_protocol_encoder_phoenix_v2_get_upload(SubGhzProtocolEncoderPhoenix_V2* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } 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); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 6); + //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 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_phoenix_v2_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_phoenix_v2_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderPhoenix_V2* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_phoenix_v2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_phoenix_v2_stop(void* context) { + SubGhzProtocolEncoderPhoenix_V2* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_phoenix_v2_yield(void* context) { + SubGhzProtocolEncoderPhoenix_V2* 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_phoenix_v2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderPhoenix_V2* instance = malloc(sizeof(SubGhzProtocolDecoderPhoenix_V2)); + instance->base.protocol = &subghz_protocol_phoenix_v2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_phoenix_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + free(instance); +} + +void subghz_protocol_decoder_phoenix_v2_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + instance->decoder.parser_step = Phoenix_V2DecoderStepReset; +} + +void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + + switch(instance->decoder.parser_step) { + case Phoenix_V2DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_phoenix_v2_const.te_short * 60) < + subghz_protocol_phoenix_v2_const.te_delta * 30)) { + //Found Preambula + instance->decoder.parser_step = Phoenix_V2DecoderStepFoundStartBit; + } + break; + case Phoenix_V2DecoderStepFoundStartBit: + if(level && ((DURATION_DIFF(duration, (subghz_protocol_phoenix_v2_const.te_short * 6)) < + subghz_protocol_phoenix_v2_const.te_delta * 4))) { + //Found start bit + instance->decoder.parser_step = Phoenix_V2DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = Phoenix_V2DecoderStepReset; + } + break; + case Phoenix_V2DecoderStepSaveDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_phoenix_v2_const.te_short * 10 + + subghz_protocol_phoenix_v2_const.te_delta)) { + instance->decoder.parser_step = Phoenix_V2DecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit == + subghz_protocol_phoenix_v2_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Phoenix_V2DecoderStepCheckDuration; + } + } + break; + case Phoenix_V2DecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_phoenix_v2_const.te_short) < + subghz_protocol_phoenix_v2_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_phoenix_v2_const.te_long) < + subghz_protocol_phoenix_v2_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Phoenix_V2DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_phoenix_v2_const.te_long) < + subghz_protocol_phoenix_v2_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_phoenix_v2_const.te_short) < + subghz_protocol_phoenix_v2_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Phoenix_V2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Phoenix_V2DecoderStepReset; + } + } else { + instance->decoder.parser_step = Phoenix_V2DecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) { + 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->btn = (data_rev >> 32) & 0xF; +} + +uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_phoenix_v2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_phoenix_v2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderPhoenix_V2* instance = context; + subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%02lX%08lX\r\n" + "Sn:0x%07lX \r\n" + "Btn:%X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/applications/main/subghz/protocols/phoenix_v2.h b/applications/main/subghz/protocols/phoenix_v2.h new file mode 100644 index 000000000..48487535e --- /dev/null +++ b/applications/main/subghz/protocols/phoenix_v2.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_PHOENIX_V2_NAME "Phoenix_V2" + +typedef struct SubGhzProtocolDecoderPhoenix_V2 SubGhzProtocolDecoderPhoenix_V2; +typedef struct SubGhzProtocolEncoderPhoenix_V2 SubGhzProtocolEncoderPhoenix_V2; + +extern const SubGhzProtocolDecoder subghz_protocol_phoenix_v2_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_phoenix_v2_encoder; +extern const SubGhzProtocol subghz_protocol_phoenix_v2; + +/** + * Allocate SubGhzProtocolEncoderPhoenix_V2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderPhoenix_V2* pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + */ +void* subghz_protocol_encoder_phoenix_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderPhoenix_V2. + * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + */ +void subghz_protocol_encoder_phoenix_v2_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + */ +void subghz_protocol_encoder_phoenix_v2_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_phoenix_v2_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderPhoenix_V2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderPhoenix_V2* pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + */ +void* subghz_protocol_decoder_phoenix_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderPhoenix_V2. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + */ +void subghz_protocol_decoder_phoenix_v2_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderPhoenix_V2. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + */ +void subghz_protocol_decoder_phoenix_v2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderPhoenix_V2. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_phoenix_v2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderPhoenix_V2. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/power_smart.c b/applications/main/subghz/protocols/power_smart.c new file mode 100644 index 000000000..1e8d10e95 --- /dev/null +++ b/applications/main/subghz/protocols/power_smart.c @@ -0,0 +1,394 @@ +#include "power_smart.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolPowerSmart" +#define POWER_SMART_PACKET_HEADER 0xFD000000AA000000 +#define POWER_SMART_PACKET_HEADER_MASK 0xFF000000FF000000 + +#define CHANNEL_PATTERN "%c%c%c%c%c%c" +#define CNT_TO_CHANNEL(dip) \ + (dip & 0x0001 ? '*' : '-'), (dip & 0x0002 ? '*' : '-'), (dip & 0x0004 ? '*' : '-'), \ + (dip & 0x0008 ? '*' : '-'), (dip & 0x0010 ? '*' : '-'), (dip & 0x0020 ? '*' : '-') + +static const SubGhzBlockConst subghz_protocol_power_smart_const = { + .te_short = 225, + .te_long = 450, + .te_delta = 100, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderPowerSmart { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderPowerSmart { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + PowerSmartDecoderStepReset = 0, + PowerSmartDecoderFoundHeader, + PowerSmartDecoderStepDecoderData, +} PowerSmartDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_power_smart_decoder = { + .alloc = subghz_protocol_decoder_power_smart_alloc, + .free = subghz_protocol_decoder_power_smart_free, + + .feed = subghz_protocol_decoder_power_smart_feed, + .reset = subghz_protocol_decoder_power_smart_reset, + + .get_hash_data = subghz_protocol_decoder_power_smart_get_hash_data, + .serialize = subghz_protocol_decoder_power_smart_serialize, + .deserialize = subghz_protocol_decoder_power_smart_deserialize, + .get_string = subghz_protocol_decoder_power_smart_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_power_smart_encoder = { + .alloc = subghz_protocol_encoder_power_smart_alloc, + .free = subghz_protocol_encoder_power_smart_free, + + .deserialize = subghz_protocol_encoder_power_smart_deserialize, + .stop = subghz_protocol_encoder_power_smart_stop, + .yield = subghz_protocol_encoder_power_smart_yield, +}; + +const SubGhzProtocol subghz_protocol_power_smart = { + .name = SUBGHZ_PROTOCOL_POWER_SMART_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_power_smart_decoder, + .encoder = &subghz_protocol_power_smart_encoder, +}; + +void* subghz_protocol_encoder_power_smart_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderPowerSmart* instance = malloc(sizeof(SubGhzProtocolEncoderPowerSmart)); + + instance->base.protocol = &subghz_protocol_power_smart; + 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_power_smart_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderPowerSmart* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_power_smart_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_power_smart_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_power_smart_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_power_smart_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_power_smart_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 SubGhzProtocolEncoderPowerSmart instance + */ +static void + subghz_protocol_encoder_power_smart_get_upload(SubGhzProtocolEncoderPowerSmart* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + for(int i = 8; i > 0; i--) { + 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_power_smart_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_power_smart_add_duration_to_upload(result); + } + } + instance->encoder.upload[index] = subghz_protocol_encoder_power_smart_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)subghz_protocol_power_smart_const.te_long * 1111); + instance->encoder.size_upload = index; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_power_smart_remote_controller(SubGhzBlockGeneric* instance) { + /* + * Protocol: Manchester encoding, symbol rate ~2222. + * Packet Format: + * 0xFDXXXXYYAAZZZZWW where 0xFD and 0xAA sync word + * XXXX = ~ZZZZ, YY=(~WW)-1 + * Example: + * SYNC1 K1 CHANNEL DATA1 K2 DATA2 SYNC2 ~K1 ~CHANNEL ~DATA2 ~K2 (~DATA2)-1 + * 0xFD2137ACAADEC852 => 11111101 0 010000 10011011 1 10101100 10101010 1 1011110 1100100 0 01010010 + * 0xFDA137ACAA5EC852 => 11111101 1 010000 10011011 1 10101100 10101010 0 1011110 1100100 0 01010010 + * 0xFDA136ACAA5EC952 => 11111101 1 010000 10011011 0 10101100 10101010 0 1011110 1100100 1 01010010 + * + * Key: + * K1K2 + * 0 0 - key_unknown + * 0 1 - key_down + * 1 0 - key_up + * 1 1 - key_stop + * + */ + + instance->btn = ((instance->data >> 54) & 0x02) | ((instance->data >> 40) & 0x1); + instance->serial = ((instance->data >> 33) & 0x3FFF00) | ((instance->data >> 32) & 0xFF); + instance->cnt = ((instance->data >> 49) & 0x3F); +} + +bool subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderPowerSmart* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_power_smart_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_power_smart_remote_controller(&instance->generic); + subghz_protocol_encoder_power_smart_get_upload(instance); + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_power_smart_stop(void* context) { + SubGhzProtocolEncoderPowerSmart* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_power_smart_yield(void* context) { + SubGhzProtocolEncoderPowerSmart* 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_power_smart_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderPowerSmart* instance = malloc(sizeof(SubGhzProtocolDecoderPowerSmart)); + instance->base.protocol = &subghz_protocol_power_smart; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_power_smart_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + free(instance); +} + +void subghz_protocol_decoder_power_smart_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +bool subghz_protocol_power_smart_chek_valid(uint64_t packet) { + uint32_t data_1 = (uint32_t)((packet >> 40) & 0xFFFF); + uint32_t data_2 = (uint32_t)((~packet >> 8) & 0xFFFF); + uint8_t data_3 = (uint8_t)(packet >> 32) & 0xFF; + uint8_t data_4 = (uint8_t)(((~packet) & 0xFF) - 1); + return (data_1 == data_2) && (data_3 == data_4); +} + +void subghz_protocol_decoder_power_smart_feed( + void* context, + bool level, + volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + ManchesterEvent event = ManchesterEventReset; + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_power_smart_const.te_short) < + subghz_protocol_power_smart_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_power_smart_const.te_long) < + subghz_protocol_power_smart_const.te_delta * 2) { + event = ManchesterEventLongLow; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_power_smart_const.te_short) < + subghz_protocol_power_smart_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_power_smart_const.te_long) < + subghz_protocol_power_smart_const.te_delta * 2) { + event = ManchesterEventLongHigh; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + } + if((instance->decoder.decode_data & POWER_SMART_PACKET_HEADER_MASK) == + POWER_SMART_PACKET_HEADER) { + if(subghz_protocol_power_smart_chek_valid(instance->decoder.decode_data)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + subghz_protocol_power_smart_const.min_count_bit_for_found; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } +} + +static const char* subghz_protocol_power_smart_get_name_button(uint8_t btn) { + btn &= 0x3; + const char* name_btn[0x4] = {"Unknown", "Down", "Up", "Stop"}; + return name_btn[btn]; +} + +uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_power_smart_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_power_smart_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_power_smart_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderPowerSmart* instance = context; + subghz_protocol_power_smart_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%07lX \r\n" + "Btn:%s\r\n" + "Channel:" CHANNEL_PATTERN "\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, + subghz_protocol_power_smart_get_name_button(instance->generic.btn), + CNT_TO_CHANNEL(instance->generic.cnt)); +} diff --git a/applications/main/subghz/protocols/power_smart.h b/applications/main/subghz/protocols/power_smart.h new file mode 100644 index 000000000..806729f8e --- /dev/null +++ b/applications/main/subghz/protocols/power_smart.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_POWER_SMART_NAME "Power Smart" + +typedef struct SubGhzProtocolDecoderPowerSmart SubGhzProtocolDecoderPowerSmart; +typedef struct SubGhzProtocolEncoderPowerSmart SubGhzProtocolEncoderPowerSmart; + +extern const SubGhzProtocolDecoder subghz_protocol_power_smart_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_power_smart_encoder; +extern const SubGhzProtocol subghz_protocol_power_smart; + +/** + * Allocate SubGhzProtocolEncoderPowerSmart. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderPowerSmart* pointer to a SubGhzProtocolEncoderPowerSmart instance + */ +void* subghz_protocol_encoder_power_smart_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderPowerSmart. + * @param context Pointer to a SubGhzProtocolEncoderPowerSmart instance + */ +void subghz_protocol_encoder_power_smart_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderPowerSmart instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderPowerSmart instance + */ +void subghz_protocol_encoder_power_smart_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderPowerSmart instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_power_smart_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderPowerSmart. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderPowerSmart* pointer to a SubGhzProtocolDecoderPowerSmart instance + */ +void* subghz_protocol_decoder_power_smart_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderPowerSmart. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + */ +void subghz_protocol_decoder_power_smart_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderPowerSmart. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + */ +void subghz_protocol_decoder_power_smart_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_power_smart_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderPowerSmart. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_power_smart_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderPowerSmart. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance + * @param output Resulting text + */ +void subghz_protocol_decoder_power_smart_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/princeton.c b/applications/main/subghz/protocols/princeton.c new file mode 100644 index 000000000..7fc8f6524 --- /dev/null +++ b/applications/main/subghz/protocols/princeton.c @@ -0,0 +1,374 @@ +#include "princeton.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define TAG "SubGhzProtocolPrinceton" + +static const SubGhzBlockConst subghz_protocol_princeton_const = { + .te_short = 390, + .te_long = 1170, + .te_delta = 300, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderPrinceton { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderPrinceton { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + PrincetonDecoderStepReset = 0, + PrincetonDecoderStepSaveDuration, + PrincetonDecoderStepCheckDuration, +} PrincetonDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_princeton_decoder = { + .alloc = subghz_protocol_decoder_princeton_alloc, + .free = subghz_protocol_decoder_princeton_free, + + .feed = subghz_protocol_decoder_princeton_feed, + .reset = subghz_protocol_decoder_princeton_reset, + + .get_hash_data = subghz_protocol_decoder_princeton_get_hash_data, + .serialize = subghz_protocol_decoder_princeton_serialize, + .deserialize = subghz_protocol_decoder_princeton_deserialize, + .get_string = subghz_protocol_decoder_princeton_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_princeton_encoder = { + .alloc = subghz_protocol_encoder_princeton_alloc, + .free = subghz_protocol_encoder_princeton_free, + + .deserialize = subghz_protocol_encoder_princeton_deserialize, + .stop = subghz_protocol_encoder_princeton_stop, + .yield = subghz_protocol_encoder_princeton_yield, +}; + +const SubGhzProtocol subghz_protocol_princeton = { + .name = SUBGHZ_PROTOCOL_PRINCETON_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_princeton_decoder, + .encoder = &subghz_protocol_princeton_encoder, +}; + +void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderPrinceton* instance = malloc(sizeof(SubGhzProtocolEncoderPrinceton)); + + instance->base.protocol = &subghz_protocol_princeton; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_princeton_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderPrinceton* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderPrinceton instance + * @return true On success + */ +static bool + subghz_protocol_encoder_princeton_get_upload(SubGhzProtocolEncoderPrinceton* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + } + } + + //Send Stop bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send PT_GUARD + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 30); + + return true; +} + +bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderPrinceton* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_princeton_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_princeton_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_princeton_stop(void* context) { + SubGhzProtocolEncoderPrinceton* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_princeton_yield(void* context) { + SubGhzProtocolEncoderPrinceton* 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_princeton_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderPrinceton* instance = malloc(sizeof(SubGhzProtocolDecoderPrinceton)); + instance->base.protocol = &subghz_protocol_princeton; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_princeton_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + free(instance); +} + +void subghz_protocol_decoder_princeton_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + instance->decoder.parser_step = PrincetonDecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + + switch(instance->decoder.parser_step) { + case PrincetonDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_princeton_const.te_short * 36) < + subghz_protocol_princeton_const.te_delta * 36)) { + //Found Preambula + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case PrincetonDecoderStepSaveDuration: + //save duration + if(level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = PrincetonDecoderStepCheckDuration; + } + break; + case PrincetonDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_princeton_const.te_long * 2)) { + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + if(instance->decoder.decode_count_bit == + subghz_protocol_princeton_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 4 + 1); + + 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; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_princeton_const.te_short) < + subghz_protocol_princeton_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_princeton_const.te_long) < + subghz_protocol_princeton_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_princeton_const.te_long) < + subghz_protocol_princeton_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_princeton_const.te_short) < + subghz_protocol_princeton_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = PrincetonDecoderStepReset; + } + } else { + instance->decoder.parser_step = PrincetonDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_princeton_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = instance->data >> 4; + instance->btn = instance->data & 0xF; +} + +uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_princeton_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_princeton_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + subghz_protocol_princeton_check_remote_controller(&instance->generic); + uint32_t data_rev = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n" + "Sn:0x%05lX Btn:%01X\r\n" + "Te:%luus\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + data_rev, + instance->generic.serial, + instance->generic.btn, + instance->te); +} diff --git a/applications/main/subghz/protocols/princeton.h b/applications/main/subghz/protocols/princeton.h new file mode 100644 index 000000000..a2a11292e --- /dev/null +++ b/applications/main/subghz/protocols/princeton.h @@ -0,0 +1,115 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_PRINCETON_NAME "Princeton" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzProtocolDecoderPrinceton SubGhzProtocolDecoderPrinceton; +typedef struct SubGhzProtocolEncoderPrinceton SubGhzProtocolEncoderPrinceton; + +extern const SubGhzProtocolDecoder subghz_protocol_princeton_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_princeton_encoder; +extern const SubGhzProtocol subghz_protocol_princeton; + +/** + * Allocate SubGhzProtocolEncoderPrinceton. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderPrinceton* pointer to a SubGhzProtocolEncoderPrinceton instance + */ +void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderPrinceton. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + */ +void subghz_protocol_encoder_princeton_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + */ +void subghz_protocol_encoder_princeton_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_princeton_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderPrinceton. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderPrinceton* pointer to a SubGhzProtocolDecoderPrinceton instance + */ +void* subghz_protocol_decoder_princeton_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + */ +void subghz_protocol_decoder_princeton_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + */ +void subghz_protocol_decoder_princeton_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_princeton_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param output Resulting text + */ +void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* output); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/protocols/princeton_for_testing.c b/applications/main/subghz/protocols/princeton_for_testing.c new file mode 100644 index 000000000..478d14cdf --- /dev/null +++ b/applications/main/subghz/protocols/princeton_for_testing.c @@ -0,0 +1,288 @@ +#include "princeton_for_testing.h" + +#include +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define SUBGHZ_PT_SHORT 300 +#define SUBGHZ_PT_LONG (SUBGHZ_PT_SHORT * 3) +#define SUBGHZ_PT_GUARD (SUBGHZ_PT_SHORT * 30) +#define SUBGHZ_PT_COUNT_KEY_433 9 +#define SUBGHZ_PT_TIMEOUT_433 900 +#define SUBGHZ_PT_COUNT_KEY_868 9 +#define SUBGHZ_PT_TIMEOUT_868 14000 + +#define TAG "SubGhzProtocolPrinceton" + +struct SubGhzEncoderPrinceton { + uint32_t key; + uint16_t te; + size_t repeat; + size_t front; + size_t count_key; + size_t count_key_package; + uint32_t time_high; + uint32_t time_low; + uint32_t timeout; + uint32_t time_stop; +}; + +typedef enum { + PrincetonDecoderStepReset = 0, + PrincetonDecoderStepSaveDuration, + PrincetonDecoderStepCheckDuration, +} PrincetonDecoderStep; + +SubGhzEncoderPrinceton* subghz_encoder_princeton_for_testing_alloc() { + SubGhzEncoderPrinceton* instance = malloc(sizeof(SubGhzEncoderPrinceton)); + return instance; +} + +void subghz_encoder_princeton_for_testing_free(SubGhzEncoderPrinceton* instance) { + furi_assert(instance); + free(instance); +} + +void subghz_encoder_princeton_for_testing_stop( + SubGhzEncoderPrinceton* instance, + uint32_t time_stop) { + instance->time_stop = time_stop; +} + +void subghz_encoder_princeton_for_testing_set( + SubGhzEncoderPrinceton* instance, + uint32_t key, + size_t repeat, + uint32_t frequency) { + furi_assert(instance); + instance->te = SUBGHZ_PT_SHORT; + instance->key = key; + instance->repeat = repeat + 1; + instance->front = 48; + instance->time_high = 0; + instance->time_low = 0; + if(frequency < 700000000) { + instance->count_key_package = SUBGHZ_PT_COUNT_KEY_433; + instance->timeout = SUBGHZ_PT_TIMEOUT_433; + } else { + instance->count_key_package = SUBGHZ_PT_COUNT_KEY_868; + instance->timeout = SUBGHZ_PT_TIMEOUT_868; + } + + instance->count_key = instance->count_key_package + 3; + + if((furi_get_tick() - instance->time_stop) < instance->timeout) { + instance->time_stop = (instance->timeout - (furi_get_tick() - instance->time_stop)) * 1000; + } else { + instance->time_stop = 0; + } +} + +size_t subghz_encoder_princeton_for_testing_get_repeat_left(SubGhzEncoderPrinceton* instance) { + furi_assert(instance); + return instance->repeat; +} + +void subghz_encoder_princeton_for_testing_print_log(void* context) { + SubGhzEncoderPrinceton* instance = context; + float duty_cycle = + ((float)instance->time_high / (instance->time_high + instance->time_low)) * 100; + FURI_LOG_I( + TAG "Encoder", + "Radio tx_time=%luus ON=%luus, OFF=%luus, DutyCycle=%lu,%lu%%", + instance->time_high + instance->time_low, + instance->time_high, + instance->time_low, + (uint32_t)duty_cycle, + (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100UL)); +} + +LevelDuration subghz_encoder_princeton_for_testing_yield(void* context) { + SubGhzEncoderPrinceton* instance = context; + if(instance->repeat == 0) { + subghz_encoder_princeton_for_testing_print_log(instance); + return level_duration_reset(); + } + + size_t bit = instance->front / 2; + bool level = !(instance->front % 2); + + LevelDuration ret; + if(bit < 24) { + uint8_t byte = bit / 8; + uint8_t bit_in_byte = bit % 8; + bool value = (((uint8_t*)&instance->key)[2 - byte] >> (7 - bit_in_byte)) & 1; + if(value) { + ret = level_duration_make(level, level ? instance->te * 3 : instance->te); + if(level) + instance->time_high += instance->te * 3; + else + instance->time_low += instance->te; + } else { + ret = level_duration_make(level, level ? instance->te : instance->te * 3); + if(level) + instance->time_high += instance->te; + else + instance->time_low += instance->te * 3; + } + } else { + if(instance->time_stop) { + ret = level_duration_make(level, level ? instance->te : instance->time_stop); + if(level) + instance->time_high += instance->te; + else { + instance->time_low += instance->time_stop; + instance->time_stop = 0; + instance->front = 47; + } + } else { + if(--instance->count_key != 0) { + ret = level_duration_make(level, level ? instance->te : instance->te * 30); + if(level) + instance->time_high += instance->te; + else + instance->time_low += instance->te * 30; + } else { + instance->count_key = instance->count_key_package + 2; + instance->front = 48; + ret = level_duration_make(level, level ? instance->te : instance->timeout * 1000); + if(level) + instance->time_high += instance->te; + else + instance->time_low += instance->timeout * 1000; + } + } + } + + instance->front++; + if(instance->front == 50) { + instance->repeat--; + instance->front = 0; + } + return ret; +} + +struct SubGhzDecoderPrinceton { + const char* name; + uint16_t te_long; + uint16_t te_short; + uint16_t te_delta; + uint8_t code_count_bit; + uint8_t code_last_count_bit; + uint64_t code_found; + uint64_t code_last_found; + uint8_t code_min_count_bit_for_found; + uint8_t btn; + uint32_t te_last; + uint32_t serial; + uint32_t parser_step; + uint16_t cnt; + uint32_t te; + + SubGhzDecoderPrincetonCallback callback; + void* context; +}; + +SubGhzDecoderPrinceton* subghz_decoder_princeton_for_testing_alloc(void) { + SubGhzDecoderPrinceton* instance = malloc(sizeof(SubGhzDecoderPrinceton)); + + instance->te = SUBGHZ_PT_SHORT; + instance->name = "Princeton"; + instance->code_min_count_bit_for_found = 24; + instance->te_short = 400; + instance->te_long = 1200; + instance->te_delta = 250; + return instance; +} + +void subghz_decoder_princeton_for_testing_free(SubGhzDecoderPrinceton* instance) { + furi_assert(instance); + free(instance); +} + +void subghz_decoder_princeton_for_testing_set_callback( + SubGhzDecoderPrinceton* instance, + SubGhzDecoderPrincetonCallback callback, + void* context) { + instance->callback = callback; + instance->context = context; +} + +void subghz_decoder_princeton_for_testing_reset(SubGhzDecoderPrinceton* instance) { + instance->parser_step = PrincetonDecoderStepReset; +} + +static void + subghz_decoder_princeton_for_testing_add_bit(SubGhzDecoderPrinceton* instance, uint8_t bit) { + instance->code_found = instance->code_found << 1 | bit; + instance->code_count_bit++; +} + +void subghz_decoder_princeton_for_testing_parse( + SubGhzDecoderPrinceton* instance, + bool level, + uint32_t duration) { + switch(instance->parser_step) { + case PrincetonDecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, instance->te_short * 36) < instance->te_delta * 36)) { + //Found Preambula + instance->parser_step = PrincetonDecoderStepSaveDuration; + instance->code_found = 0; + instance->code_count_bit = 0; + instance->te = 0; + } + break; + case PrincetonDecoderStepSaveDuration: + //save duration + if(level) { + instance->te_last = duration; + instance->te += duration; + instance->parser_step = PrincetonDecoderStepCheckDuration; + } + break; + case PrincetonDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)instance->te_short * 10 + instance->te_delta)) { + instance->parser_step = PrincetonDecoderStepSaveDuration; + if(instance->code_count_bit == instance->code_min_count_bit_for_found) { + instance->te /= (instance->code_count_bit * 4 + 1); + + instance->code_last_found = instance->code_found; + instance->code_last_count_bit = instance->code_count_bit; + instance->serial = instance->code_found >> 4; + instance->btn = (uint8_t)instance->code_found & 0x00000F; + + if(instance->callback) instance->callback(instance, instance->context); + } + instance->code_found = 0; + instance->code_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->te_last, instance->te_short) < instance->te_delta) && + (DURATION_DIFF(duration, instance->te_long) < instance->te_delta * 3)) { + subghz_decoder_princeton_for_testing_add_bit(instance, 0); + instance->parser_step = PrincetonDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->te_last, instance->te_long) < instance->te_delta * 3) && + (DURATION_DIFF(duration, instance->te_short) < instance->te_delta)) { + subghz_decoder_princeton_for_testing_add_bit(instance, 1); + instance->parser_step = PrincetonDecoderStepSaveDuration; + } else { + instance->parser_step = PrincetonDecoderStepReset; + } + } else { + instance->parser_step = PrincetonDecoderStepReset; + } + break; + } +} diff --git a/applications/main/subghz/protocols/princeton_for_testing.h b/applications/main/subghz/protocols/princeton_for_testing.h new file mode 100644 index 000000000..07a37ec5f --- /dev/null +++ b/applications/main/subghz/protocols/princeton_for_testing.h @@ -0,0 +1,97 @@ +#pragma once + +#include "base.h" + +/** SubGhzDecoderPrinceton anonymous type */ +typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton; +/** SubGhzEncoderPrinceton anonymous type */ +typedef struct SubGhzEncoderPrinceton SubGhzEncoderPrinceton; + +typedef void (*SubGhzDecoderPrincetonCallback)(SubGhzDecoderPrinceton* parser, void* context); + +/** + * Allocate SubGhzEncoderPrinceton + * @return pointer to SubGhzEncoderPrinceton instance + */ +SubGhzEncoderPrinceton* subghz_encoder_princeton_for_testing_alloc(); + +/** + * Free SubGhzEncoderPrinceton instance + * @param instance - SubGhzEncoderPrinceton instance + */ +void subghz_encoder_princeton_for_testing_free(SubGhzEncoderPrinceton* instance); + +/** + * Forced transmission stop. + * @param instance Pointer to a SubGhzEncoderPrinceton instance + * @param time_stop Transmission stop time, ms + */ +void subghz_encoder_princeton_for_testing_stop( + SubGhzEncoderPrinceton* instance, + uint32_t time_stop); + +/** + * Set new encoder params + * @param instance - SubGhzEncoderPrinceton instance + * @param key - 24bit key + * @param repeat - how many times to repeat + * @param frequency - frequency + */ +void subghz_encoder_princeton_for_testing_set( + SubGhzEncoderPrinceton* instance, + uint32_t key, + size_t repeat, + uint32_t frequency); + +/** + * Get repeat count left + * @param instance - SubGhzEncoderPrinceton instance + * @return repeat count left + */ +size_t subghz_encoder_princeton_for_testing_get_repeat_left(SubGhzEncoderPrinceton* instance); + +/** + * Print encoder log + * @param instance - SubGhzEncoderPrinceton instance + */ +void subghz_encoder_princeton_for_testing_print_log(void* context); + +/** + * Get level duration + * @param instance - SubGhzEncoderPrinceton instance + * @return level duration + */ +LevelDuration subghz_encoder_princeton_for_testing_yield(void* context); + +/** + * Allocate SubGhzDecoderPrinceton + * @return SubGhzDecoderPrinceton* + */ +SubGhzDecoderPrinceton* subghz_decoder_princeton_for_testing_alloc(); + +/** + * Free SubGhzDecoderPrinceton + * @param instance + */ +void subghz_decoder_princeton_for_testing_free(SubGhzDecoderPrinceton* instance); + +void subghz_decoder_princeton_for_testing_set_callback( + SubGhzDecoderPrinceton* instance, + SubGhzDecoderPrincetonCallback callback, + void* context); + +/** + * Reset internal state + * @param instance - SubGhzDecoderPrinceton instance + */ +void subghz_decoder_princeton_for_testing_reset(SubGhzDecoderPrinceton* instance); + +/** + * Parse accepted duration + * @param instance - SubGhzDecoderPrinceton instance + * @param data - LevelDuration level_duration + */ +void subghz_decoder_princeton_for_testing_parse( + SubGhzDecoderPrinceton* instance, + bool level, + uint32_t duration); diff --git a/applications/main/subghz/protocols/protocol_items.c b/applications/main/subghz/protocols/protocol_items.c new file mode 100644 index 000000000..74244c5ff --- /dev/null +++ b/applications/main/subghz/protocols/protocol_items.c @@ -0,0 +1,50 @@ +#include "protocol_items.h" + +const SubGhzProtocol* subghz_protocol_registry_items[] = { + &subghz_protocol_gate_tx, + &subghz_protocol_keeloq, + &subghz_protocol_star_line, + &subghz_protocol_nice_flo, + &subghz_protocol_came, + &subghz_protocol_faac_slh, + &subghz_protocol_nice_flor_s, + &subghz_protocol_came_twee, + &subghz_protocol_came_atomo, + &subghz_protocol_nero_sketch, + &subghz_protocol_ido, + &subghz_protocol_kia, + &subghz_protocol_hormann, + &subghz_protocol_nero_radio, + &subghz_protocol_somfy_telis, + &subghz_protocol_somfy_keytis, + &subghz_protocol_scher_khan, + &subghz_protocol_princeton, + &subghz_protocol_raw, + &subghz_protocol_linear, + &subghz_protocol_secplus_v2, + &subghz_protocol_secplus_v1, + &subghz_protocol_megacode, + &subghz_protocol_holtek, + &subghz_protocol_chamb_code, + &subghz_protocol_power_smart, + &subghz_protocol_marantec, + &subghz_protocol_bett, + &subghz_protocol_doitrand, + &subghz_protocol_phoenix_v2, + &subghz_protocol_honeywell_wdb, + &subghz_protocol_magellan, + &subghz_protocol_intertechno_v3, + &subghz_protocol_clemsa, + &subghz_protocol_ansonic, + &subghz_protocol_smc5326, + &subghz_protocol_holtek_th12x, + &subghz_protocol_linear_delta3, + &subghz_protocol_dooya, + &subghz_protocol_alutech_at_4n, + &subghz_protocol_kinggates_stylo_4k, + &subghz_protocol_bin_raw, +}; + +const SubGhzProtocolRegistry subghz_protocol_registry = { + .items = subghz_protocol_registry_items, + .size = COUNT_OF(subghz_protocol_registry_items)}; diff --git a/applications/main/subghz/protocols/protocol_items.h b/applications/main/subghz/protocols/protocol_items.h new file mode 100644 index 000000000..b7e082bf5 --- /dev/null +++ b/applications/main/subghz/protocols/protocol_items.h @@ -0,0 +1,55 @@ +#pragma once +#include "../registry.h" + +#include "princeton.h" +#include "keeloq.h" +#include "star_line.h" +#include "nice_flo.h" +#include "came.h" +#include "faac_slh.h" +#include "nice_flor_s.h" +#include "came_twee.h" +#include "came_atomo.h" +#include "nero_sketch.h" +#include "ido.h" +#include "kia.h" +#include "hormann.h" +#include "nero_radio.h" +#include "somfy_telis.h" +#include "somfy_keytis.h" +#include "scher_khan.h" +#include "gate_tx.h" +#include "raw.h" +#include "linear.h" +#include "linear_delta3.h" +#include "secplus_v2.h" +#include "secplus_v1.h" +#include "megacode.h" +#include "holtek.h" +#include "chamberlain_code.h" +#include "power_smart.h" +#include "marantec.h" +#include "bett.h" +#include "doitrand.h" +#include "phoenix_v2.h" +#include "honeywell_wdb.h" +#include "magellan.h" +#include "intertechno_v3.h" +#include "clemsa.h" +#include "ansonic.h" +#include "smc5326.h" +#include "holtek_ht12x.h" +#include "dooya.h" +#include "alutech_at_4n.h" +#include "kinggates_stylo_4k.h" +#include "bin_raw.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const SubGhzProtocolRegistry subghz_protocol_registry; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/subghz/protocols/raw.c b/applications/main/subghz/protocols/raw.c new file mode 100644 index 000000000..01a229047 --- /dev/null +++ b/applications/main/subghz/protocols/raw.c @@ -0,0 +1,374 @@ +#include "raw.h" +#include +#include "../subghz_file_encoder_worker.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#include +#include + +#define TAG "SubGhzProtocolRAW" +#define SUBGHZ_DOWNLOAD_MAX_SIZE 512 + +static const SubGhzBlockConst subghz_protocol_raw_const = { + .te_short = 50, + .te_long = 32700, + .te_delta = 0, + .min_count_bit_for_found = 0, +}; + +struct SubGhzProtocolDecoderRAW { + SubGhzProtocolDecoderBase base; + + int32_t* upload_raw; + uint16_t ind_write; + Storage* storage; + FlipperFormat* flipper_file; + uint32_t file_is_open; + FuriString* file_name; + size_t sample_write; + bool last_level; + bool pause; +}; + +struct SubGhzProtocolEncoderRAW { + SubGhzProtocolEncoderBase base; + + bool is_running; + FuriString* file_name; + SubGhzFileEncoderWorker* file_worker_encoder; +}; + +typedef enum { + RAWFileIsOpenClose = 0, + RAWFileIsOpenWrite, + RAWFileIsOpenRead, +} RAWFilIsOpen; + +const SubGhzProtocolDecoder subghz_protocol_raw_decoder = { + .alloc = subghz_protocol_decoder_raw_alloc, + .free = subghz_protocol_decoder_raw_free, + + .feed = subghz_protocol_decoder_raw_feed, + .reset = subghz_protocol_decoder_raw_reset, + + .get_hash_data = NULL, + .serialize = NULL, + .deserialize = subghz_protocol_decoder_raw_deserialize, + .get_string = subghz_protocol_decoder_raw_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_raw_encoder = { + .alloc = subghz_protocol_encoder_raw_alloc, + .free = subghz_protocol_encoder_raw_free, + + .deserialize = subghz_protocol_encoder_raw_deserialize, + .stop = subghz_protocol_encoder_raw_stop, + .yield = subghz_protocol_encoder_raw_yield, +}; + +const SubGhzProtocol subghz_protocol_raw = { + .name = SUBGHZ_PROTOCOL_RAW_NAME, + .type = SubGhzProtocolTypeRAW, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_RAW | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_raw_decoder, + .encoder = &subghz_protocol_raw_encoder, +}; + +bool subghz_protocol_raw_save_to_file_init( + SubGhzProtocolDecoderRAW* instance, + const char* dev_name, + SubGhzRadioPreset* preset) { + furi_assert(instance); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->flipper_file = flipper_format_file_alloc(instance->storage); + + FuriString* temp_str; + temp_str = furi_string_alloc(); + bool init = false; + + do { + // Create subghz folder directory if necessary + if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { + break; + } + // Create saved directory if necessary + if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { + break; + } + + furi_string_set(instance->file_name, dev_name); + // First remove subghz device file if it was saved + furi_string_printf(temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); + + if(!storage_simply_remove(instance->storage, furi_string_get_cstr(temp_str))) { + break; + } + + // Open file + if(!flipper_format_file_open_always( + instance->flipper_file, furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", furi_string_get_cstr(temp_str)); + break; + } + + if(!flipper_format_write_header_cstr( + instance->flipper_file, SUBGHZ_RAW_FILE_TYPE, SUBGHZ_RAW_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32( + instance->flipper_file, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + instance->flipper_file, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + instance->flipper_file, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + instance->flipper_file, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr( + instance->flipper_file, "Protocol", instance->base.protocol->name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + instance->upload_raw = malloc(SUBGHZ_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); + instance->file_is_open = RAWFileIsOpenWrite; + instance->sample_write = 0; + instance->last_level = false; + instance->pause = false; + init = true; + } while(0); + + furi_string_free(temp_str); + + return init; +} + +static bool subghz_protocol_raw_save_to_file_write(SubGhzProtocolDecoderRAW* instance) { + furi_assert(instance); + + bool is_write = false; + if(instance->file_is_open == RAWFileIsOpenWrite) { + if(!flipper_format_write_int32( + instance->flipper_file, "RAW_Data", instance->upload_raw, instance->ind_write)) { + FURI_LOG_E(TAG, "Unable to add RAW_Data"); + } else { + instance->sample_write += instance->ind_write; + instance->ind_write = 0; + is_write = true; + } + } + return is_write; +} + +void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance) { + furi_assert(instance); + + if(instance->file_is_open == RAWFileIsOpenWrite && instance->ind_write) + subghz_protocol_raw_save_to_file_write(instance); + if(instance->file_is_open != RAWFileIsOpenClose) { + free(instance->upload_raw); + instance->upload_raw = NULL; + flipper_format_file_close(instance->flipper_file); + flipper_format_free(instance->flipper_file); + furi_record_close(RECORD_STORAGE); + } + + instance->file_is_open = RAWFileIsOpenClose; +} + +void subghz_protocol_raw_save_to_file_pause(SubGhzProtocolDecoderRAW* instance, bool pause) { + furi_assert(instance); + + if(instance->pause != pause) { + instance->pause = pause; + } +} + +size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance) { + return instance->sample_write + instance->ind_write; +} + +void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRAW* instance = malloc(sizeof(SubGhzProtocolDecoderRAW)); + instance->base.protocol = &subghz_protocol_raw; + instance->upload_raw = NULL; + instance->ind_write = 0; + instance->last_level = false; + instance->file_is_open = RAWFileIsOpenClose; + instance->file_name = furi_string_alloc(); + + return instance; +} + +void subghz_protocol_decoder_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRAW* instance = context; + furi_string_free(instance->file_name); + free(instance); +} + +void subghz_protocol_decoder_raw_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRAW* instance = context; + instance->ind_write = 0; + instance->last_level = false; +} + +void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRAW* instance = context; + + if(!instance->pause && (instance->upload_raw != NULL)) { + if(duration > subghz_protocol_raw_const.te_short) { + if(instance->last_level != level) { + instance->last_level = (level ? true : false); + instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); + } + } + + if(instance->ind_write == SUBGHZ_DOWNLOAD_MAX_SIZE) { + subghz_protocol_raw_save_to_file_write(instance); + } + } +} + +bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + UNUSED(context); + UNUSED(flipper_format); + //ToDo stub, for backwards compatibility + return true; +} + +void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { + furi_assert(context); + //SubGhzProtocolDecoderRAW* instance = context; + UNUSED(context); + //ToDo no use + furi_string_cat_printf(output, "RAW Data"); +} + +void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRAW* instance = malloc(sizeof(SubGhzProtocolEncoderRAW)); + + instance->base.protocol = &subghz_protocol_raw; + instance->file_name = furi_string_alloc(); + instance->is_running = false; + return instance; +} + +void subghz_protocol_encoder_raw_stop(void* context) { + SubGhzProtocolEncoderRAW* instance = context; + instance->is_running = false; + if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { + subghz_file_encoder_worker_stop(instance->file_worker_encoder); + subghz_file_encoder_worker_free(instance->file_worker_encoder); + } +} + +void subghz_protocol_encoder_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRAW* instance = context; + subghz_protocol_encoder_raw_stop(instance); + furi_string_free(instance->file_name); + free(instance); +} + +void subghz_protocol_raw_file_encoder_worker_set_callback_end( + SubGhzProtocolEncoderRAW* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback_end, + void* context_end) { + furi_assert(instance); + furi_assert(callback_end); + subghz_file_encoder_worker_callback_end( + instance->file_worker_encoder, callback_end, context_end); +} + +static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* instance) { + furi_assert(instance); + + instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); + if(subghz_file_encoder_worker_start( + instance->file_worker_encoder, furi_string_get_cstr(instance->file_name))) { + //the worker needs a file in order to open and read part of the file + furi_delay_ms(100); + instance->is_running = true; + } else { + subghz_protocol_encoder_raw_stop(instance); + } + return instance->is_running; +} + +void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path) { + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", "RAW")) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + if(!flipper_format_write_string_cstr(flipper_format, "File_name", file_path)) { + FURI_LOG_E(TAG, "Unable to add File_name"); + break; + } + } while(false); +} + +bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRAW* instance = context; + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(flipper_format, "File_name", temp_str)) { + FURI_LOG_E(TAG, "Missing File_name"); + break; + } + furi_string_set(instance->file_name, temp_str); + + res = subghz_protocol_encoder_raw_worker_init(instance); + } while(false); + furi_string_free(temp_str); + return res; +} + +LevelDuration subghz_protocol_encoder_raw_yield(void* context) { + SubGhzProtocolEncoderRAW* instance = context; + + if(!instance->is_running) return level_duration_reset(); + return subghz_file_encoder_worker_get_level_duration(instance->file_worker_encoder); +} diff --git a/applications/main/subghz/protocols/raw.h b/applications/main/subghz/protocols/raw.h new file mode 100644 index 000000000..44c7faec5 --- /dev/null +++ b/applications/main/subghz/protocols/raw.h @@ -0,0 +1,148 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_RAW_NAME "RAW" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*SubGhzProtocolEncoderRAWCallbackEnd)(void* context); + +typedef struct SubGhzProtocolDecoderRAW SubGhzProtocolDecoderRAW; +typedef struct SubGhzProtocolEncoderRAW SubGhzProtocolEncoderRAW; + +extern const SubGhzProtocolDecoder subghz_protocol_raw_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_raw_encoder; +extern const SubGhzProtocol subghz_protocol_raw; + +/** + * Open file for writing + * @param instance Pointer to a SubGhzProtocolDecoderRAW instance + * @param dev_name File name + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_raw_save_to_file_init( + SubGhzProtocolDecoderRAW* instance, + const char* dev_name, + SubGhzRadioPreset* preset); + +/** + * Stop writing file to flash + * @param instance Pointer to a SubGhzProtocolDecoderRAW instance + */ +void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance); + +/** + * Get the number of samples received SubGhzProtocolDecoderRAW. + * @param instance Pointer to a SubGhzProtocolDecoderRAW instance + * @return count of samples + */ +size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance); + +/** + * Allocate SubGhzProtocolDecoderRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRAW* pointer to a SubGhzProtocolDecoderRAW instance + */ +void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRAW. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + */ +void subghz_protocol_decoder_raw_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRAW. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + */ +void subghz_protocol_decoder_raw_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration); + +/** + * Deserialize data SubGhzProtocolDecoderRAW. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + * @param output Resulting text + */ +void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output); + +/** + * Allocate SubGhzProtocolEncoderRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRAW* pointer to a SubGhzProtocolEncoderRAW instance + */ +void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRAW. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + */ +void subghz_protocol_encoder_raw_free(void* context); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + */ +void subghz_protocol_encoder_raw_stop(void* context); + +/** + * pause writing to flash. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + * @param pause pause writing + */ +void subghz_protocol_raw_save_to_file_pause(SubGhzProtocolDecoderRAW* instance, bool pause); + +/** + * Set callback on completion of file transfer. + * @param instance Pointer to a SubGhzProtocolEncoderRAW instance + * @param callback_end Callback, SubGhzProtocolEncoderRAWCallbackEnd + * @param context_end Context + */ +void subghz_protocol_raw_file_encoder_worker_set_callback_end( + SubGhzProtocolEncoderRAW* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback_end, + void* context_end); + +/** + * File generation for RAW work. + * @param flipper_format Pointer to a FlipperFormat instance + * @param file_path File path + */ +void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_raw_yield(void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/protocols/scher_khan.c b/applications/main/subghz/protocols/scher_khan.c new file mode 100644 index 000000000..a5de7d04b --- /dev/null +++ b/applications/main/subghz/protocols/scher_khan.c @@ -0,0 +1,288 @@ +#include "scher_khan.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +//https://phreakerclub.com/72 +//https://phreakerclub.com/forum/showthread.php?t=7&page=2 +//https://phreakerclub.com/forum/showthread.php?t=274&highlight=magicar +//!!! https://phreakerclub.com/forum/showthread.php?t=489&highlight=magicar&page=5 + +#define TAG "SubGhzProtocolScherKhan" + +static const SubGhzBlockConst subghz_protocol_scher_khan_const = { + .te_short = 750, + .te_long = 1100, + .te_delta = 150, + .min_count_bit_for_found = 35, +}; + +struct SubGhzProtocolDecoderScherKhan { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + const char* protocol_name; +}; + +struct SubGhzProtocolEncoderScherKhan { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ScherKhanDecoderStepReset = 0, + ScherKhanDecoderStepCheckPreambula, + ScherKhanDecoderStepSaveDuration, + ScherKhanDecoderStepCheckDuration, +} ScherKhanDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder = { + .alloc = subghz_protocol_decoder_scher_khan_alloc, + .free = subghz_protocol_decoder_scher_khan_free, + + .feed = subghz_protocol_decoder_scher_khan_feed, + .reset = subghz_protocol_decoder_scher_khan_reset, + + .get_hash_data = subghz_protocol_decoder_scher_khan_get_hash_data, + .serialize = subghz_protocol_decoder_scher_khan_serialize, + .deserialize = subghz_protocol_decoder_scher_khan_deserialize, + .get_string = subghz_protocol_decoder_scher_khan_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_scher_khan = { + .name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Save, + + .decoder = &subghz_protocol_scher_khan_decoder, + .encoder = &subghz_protocol_scher_khan_encoder, +}; + +void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderScherKhan* instance = malloc(sizeof(SubGhzProtocolDecoderScherKhan)); + instance->base.protocol = &subghz_protocol_scher_khan; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_scher_khan_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + free(instance); +} + +void subghz_protocol_decoder_scher_khan_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + instance->decoder.parser_step = ScherKhanDecoderStepReset; +} + +void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + + switch(instance->decoder.parser_step) { + case ScherKhanDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta)) { + instance->decoder.parser_step = ScherKhanDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case ScherKhanDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else if( + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta) { + // Found start bit + if(instance->header_count >= 2) { + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + case ScherKhanDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_scher_khan_const.te_delta * 2UL + + subghz_protocol_scher_khan_const.te_long)) { + //Found stop bit + instance->decoder.parser_step = ScherKhanDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_scher_khan_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; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ScherKhanDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + case ScherKhanDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_long) < + subghz_protocol_scher_khan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_long) < + subghz_protocol_scher_khan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param protocol_name + */ +static void subghz_protocol_scher_khan_check_remote_controller( + SubGhzBlockGeneric* instance, + const char** protocol_name) { + /* + * MAGICAR 51 bit 00000001A99121DE83C3 MAGIC CODE, Dynamic + * 0E8C1619E830C -> 000011101000110000010110 0001 1001 1110 1000001100001100 + * 0E8C1629D830D -> 000011101000110000010110 0010 1001 1101 1000001100001101 + * 0E8C1649B830E -> 000011101000110000010110 0100 1001 1011 1000001100001110 + * 0E8C16897830F -> 000011101000110000010110 1000 1001 0111 1000001100001111 + * Serial Key Ser ~Key CNT + */ + + switch(instance->data_count_bit) { + // case 35: //MAGIC CODE, Static + // instance->protocol_name = "MAGIC CODE, Static"; + // 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; + + default: + *protocol_name = "Unknown"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + } +} + +uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_scher_khan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + + subghz_protocol_scher_khan_check_remote_controller( + &instance->generic, &instance->protocol_name); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:%07lX Btn:%X Cnt:%04lX\r\n" + "Pt: %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, + instance->generic.btn, + instance->generic.cnt, + instance->protocol_name); +} diff --git a/applications/main/subghz/protocols/scher_khan.h b/applications/main/subghz/protocols/scher_khan.h new file mode 100644 index 000000000..b7e84ea1f --- /dev/null +++ b/applications/main/subghz/protocols/scher_khan.h @@ -0,0 +1,73 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SCHER_KHAN_NAME "Scher-Khan" + +typedef struct SubGhzProtocolDecoderScherKhan SubGhzProtocolDecoderScherKhan; +typedef struct SubGhzProtocolEncoderScherKhan SubGhzProtocolEncoderScherKhan; + +extern const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder; +extern const SubGhzProtocol subghz_protocol_scher_khan; + +/** + * Allocate SubGhzProtocolDecoderScherKhan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderScherKhan* pointer to a SubGhzProtocolDecoderScherKhan instance + */ +void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + */ +void subghz_protocol_decoder_scher_khan_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + */ +void subghz_protocol_decoder_scher_khan_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_scher_khan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param output Resulting text + */ +void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/secplus_v1.c b/applications/main/subghz/protocols/secplus_v1.c new file mode 100644 index 000000000..3ef95db36 --- /dev/null +++ b/applications/main/subghz/protocols/secplus_v1.c @@ -0,0 +1,634 @@ +#include "secplus_v1.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* +* Help +* https://github.com/argilo/secplus +* https://github.com/merbanan/rtl_433/blob/master/src/devices/secplus_v1.c +*/ + +#define TAG "SubGhzProtocoSecPlus_v1" + +#define SECPLUS_V1_BIT_ERR -1 //0b0000 +#define SECPLUS_V1_BIT_0 0 //0b0001 +#define SECPLUS_V1_BIT_1 1 //0b0011 +#define SECPLUS_V1_BIT_2 2 //0b0111 + +#define SECPLUS_V1_PACKET_1_HEADER 0x00 +#define SECPLUS_V1_PACKET_2_HEADER 0x02 +#define SECPLUS_V1_PACKET_1_INDEX_BASE 0 +#define SECPLUS_V1_PACKET_2_INDEX_BASE 21 +#define SECPLUS_V1_PACKET_1_ACCEPTED (1 << 0) +#define SECPLUS_V1_PACKET_2_ACCEPTED (1 << 1) + +static const SubGhzBlockConst subghz_protocol_secplus_v1_const = { + .te_short = 500, + .te_long = 1500, + .te_delta = 100, + .min_count_bit_for_found = 21, +}; + +struct SubGhzProtocolDecoderSecPlus_v1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint8_t packet_accepted; + uint8_t base_packet_index; + uint8_t data_array[44]; +}; + +struct SubGhzProtocolEncoderSecPlus_v1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint8_t data_array[44]; +}; + +typedef enum { + SecPlus_v1DecoderStepReset = 0, + SecPlus_v1DecoderStepSearchStartBit, + SecPlus_v1DecoderStepSaveDuration, + SecPlus_v1DecoderStepDecoderData, +} SecPlus_v1DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder = { + .alloc = subghz_protocol_decoder_secplus_v1_alloc, + .free = subghz_protocol_decoder_secplus_v1_free, + + .feed = subghz_protocol_decoder_secplus_v1_feed, + .reset = subghz_protocol_decoder_secplus_v1_reset, + + .get_hash_data = subghz_protocol_decoder_secplus_v1_get_hash_data, + .serialize = subghz_protocol_decoder_secplus_v1_serialize, + .deserialize = subghz_protocol_decoder_secplus_v1_deserialize, + .get_string = subghz_protocol_decoder_secplus_v1_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder = { + .alloc = subghz_protocol_encoder_secplus_v1_alloc, + .free = subghz_protocol_encoder_secplus_v1_free, + + .deserialize = subghz_protocol_encoder_secplus_v1_deserialize, + .stop = subghz_protocol_encoder_secplus_v1_stop, + .yield = subghz_protocol_encoder_secplus_v1_yield, +}; + +const SubGhzProtocol subghz_protocol_secplus_v1 = { + .name = SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send | SubGhzProtocolFlag_Save, + + .decoder = &subghz_protocol_secplus_v1_decoder, + .encoder = &subghz_protocol_secplus_v1_encoder, +}; + +void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v1)); + + instance->base.protocol = &subghz_protocol_secplus_v1; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_secplus_v1_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v1* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @return true On success + */ +static bool + subghz_protocol_encoder_secplus_v1_get_upload(SubGhzProtocolEncoderSecPlus_v1* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Encoder size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header packet 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * (116 + 3)); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + + //Send data packet 1 + for(uint8_t i = SECPLUS_V1_PACKET_1_INDEX_BASE + 1; i < SECPLUS_V1_PACKET_1_INDEX_BASE + 21; + i++) { + switch(instance->data_array[i]) { + case SECPLUS_V1_BIT_0: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + break; + case SECPLUS_V1_BIT_1: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + break; + case SECPLUS_V1_BIT_2: + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + break; + + default: + FURI_LOG_E(TAG, "Encoder error, wrong bit type"); + return false; + break; + } + } + + //Send header packet 2 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * (116)); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + + //Send data packet 2 + for(uint8_t i = SECPLUS_V1_PACKET_2_INDEX_BASE + 1; i < SECPLUS_V1_PACKET_2_INDEX_BASE + 21; + i++) { + switch(instance->data_array[i]) { + case SECPLUS_V1_BIT_0: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + break; + case SECPLUS_V1_BIT_1: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + break; + case SECPLUS_V1_BIT_2: + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + break; + + default: + FURI_LOG_E(TAG, "Encoder error, wrong bit type."); + return false; + break; + } + } + + return true; +} + +/** + * Security+ 1.0 message encoding + * @param instance SubGhzProtocolEncoderSecPlus_v1* + */ + +static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* instance) { + uint32_t fixed = (instance->generic.data >> 32) & 0xFFFFFFFF; + uint32_t rolling = instance->generic.data & 0xFFFFFFFF; + + uint8_t rolling_array[20] = {0}; + uint8_t fixed_array[20] = {0}; + uint32_t acc = 0; + + //increment the counter + rolling += 2; + + //update data + instance->generic.data &= 0xFFFFFFFF00000000; + instance->generic.data |= rolling; + + if(rolling == 0xFFFFFFFF) { + rolling = 0xE6000000; + } + if(fixed > 0xCFD41B90) { + FURI_LOG_E("TAG", "Encode wrong fixed data"); + return false; + } + + rolling = subghz_protocol_blocks_reverse_key(rolling, 32); + + for(int i = 19; i > -1; i--) { + rolling_array[i] = rolling % 3; + rolling /= 3; + fixed_array[i] = fixed % 3; + fixed /= 3; + } + + instance->data_array[SECPLUS_V1_PACKET_1_INDEX_BASE] = SECPLUS_V1_PACKET_1_HEADER; + instance->data_array[SECPLUS_V1_PACKET_2_INDEX_BASE] = SECPLUS_V1_PACKET_2_HEADER; + + //encode packet 1 + for(uint8_t i = 1; i < 11; i++) { + acc += rolling_array[i - 1]; + instance->data_array[i * 2 - 1] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + instance->data_array[i * 2] = acc % 3; + } + + acc = 0; + //encode packet 2 + for(uint8_t i = 11; i < 21; i++) { + acc += rolling_array[i - 1]; + instance->data_array[i * 2] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + instance->data_array[i * 2 + 1] = acc % 3; + } + + return true; +} + +bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v1* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + 2 * subghz_protocol_secplus_v1_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_secplus_v1_encode(instance)) { + break; + } + if(!subghz_protocol_encoder_secplus_v1_get_upload(instance)) { + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_secplus_v1_stop(void* context) { + SubGhzProtocolEncoderSecPlus_v1* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context) { + SubGhzProtocolEncoderSecPlus_v1* 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_secplus_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v1)); + instance->base.protocol = &subghz_protocol_secplus_v1; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_secplus_v1_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + free(instance); +} + +void subghz_protocol_decoder_secplus_v1_reset(void* context) { + furi_assert(context); + // SubGhzProtocolDecoderSecPlus_v1* instance = context; + // does not reset the decoder because you need to get 2 parts of the package +} + +/** + * Security+ 1.0 message decoding + * @param instance SubGhzProtocolDecoderSecPlus_v1* + */ + +static void subghz_protocol_secplus_v1_decode(SubGhzProtocolDecoderSecPlus_v1* instance) { + uint32_t rolling = 0; + uint32_t fixed = 0; + uint32_t acc = 0; + uint8_t digit = 0; + + //decode packet 1 + for(uint8_t i = 1; i < 21; i += 2) { + digit = instance->data_array[i]; + rolling = (rolling * 3) + digit; + acc += digit; + + digit = (60 + instance->data_array[i + 1] - acc) % 3; + fixed = (fixed * 3) + digit; + acc += digit; + } + + acc = 0; + //decode packet 2 + for(uint8_t i = 22; i < 42; i += 2) { + digit = instance->data_array[i]; + rolling = (rolling * 3) + digit; + acc += digit; + + digit = (60 + instance->data_array[i + 1] - acc) % 3; + fixed = (fixed * 3) + digit; + acc += digit; + } + + rolling = subghz_protocol_blocks_reverse_key(rolling, 32); + instance->generic.data = (uint64_t)fixed << 32 | rolling; + + instance->generic.data_count_bit = + subghz_protocol_secplus_v1_const.min_count_bit_for_found * 2; +} + +void subghz_protocol_decoder_secplus_v1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + + switch(instance->decoder.parser_step) { + case SecPlus_v1DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 120) < + subghz_protocol_secplus_v1_const.te_delta * 120)) { + //Found header Security+ 1.0 + instance->decoder.parser_step = SecPlus_v1DecoderStepSearchStartBit; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->packet_accepted = 0; + memset(instance->data_array, 0, sizeof(instance->data_array)); + } + break; + case SecPlus_v1DecoderStepSearchStartBit: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short) < + subghz_protocol_secplus_v1_const.te_delta) { + instance->base_packet_index = SECPLUS_V1_PACKET_1_INDEX_BASE; + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_long) < + subghz_protocol_secplus_v1_const.te_delta) { + instance->base_packet_index = SECPLUS_V1_PACKET_2_INDEX_BASE; + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_2; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + break; + case SecPlus_v1DecoderStepSaveDuration: + if(!level) { //save interval + if(DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 120) < + subghz_protocol_secplus_v1_const.te_delta * 120) { + if(instance->decoder.decode_count_bit == + subghz_protocol_secplus_v1_const.min_count_bit_for_found) { + if(instance->base_packet_index == SECPLUS_V1_PACKET_1_INDEX_BASE) + instance->packet_accepted |= SECPLUS_V1_PACKET_1_ACCEPTED; + if(instance->base_packet_index == SECPLUS_V1_PACKET_2_INDEX_BASE) + instance->packet_accepted |= SECPLUS_V1_PACKET_2_ACCEPTED; + + if(instance->packet_accepted == + (SECPLUS_V1_PACKET_1_ACCEPTED | SECPLUS_V1_PACKET_2_ACCEPTED)) { + subghz_protocol_secplus_v1_decode(instance); + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + } + instance->decoder.parser_step = SecPlus_v1DecoderStepSearchStartBit; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = SecPlus_v1DecoderStepDecoderData; + } + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + break; + case SecPlus_v1DecoderStepDecoderData: + if(level && (instance->decoder.decode_count_bit <= + subghz_protocol_secplus_v1_const.min_count_bit_for_found)) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_secplus_v1_const.te_short * 3) < + subghz_protocol_secplus_v1_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short) < + subghz_protocol_secplus_v1_const.te_delta)) { + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_secplus_v1_const.te_short * 2) < + subghz_protocol_secplus_v1_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 2) < + subghz_protocol_secplus_v1_const.te_delta * 2)) { + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_1; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_secplus_v1_const.te_short) < + subghz_protocol_secplus_v1_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 3) < + subghz_protocol_secplus_v1_const.te_delta * 3)) { + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_2; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_secplus_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + 2 * subghz_protocol_secplus_v1_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed) { + //uint8_t id0 = (fixed / 3) % 3; + uint8_t id1 = (fixed / 9) % 3; + uint8_t btn = fixed % 3; + + do { + if(id1 == 0) return false; + if(!(btn == 0 || btn == 1 || btn == 2)) return false; //-V560 + } while(false); + return true; +} + +void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + + uint32_t fixed = (instance->generic.data >> 32) & 0xFFFFFFFF; + instance->generic.cnt = instance->generic.data & 0xFFFFFFFF; + + instance->generic.btn = fixed % 3; + uint8_t id0 = (fixed / 3) % 3; + uint8_t id1 = (fixed / 9) % 3; + uint16_t pin = 0; + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "id1:%d id0:%d", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + id1, + id0); + + if(id1 == 0) { + // (fixed // 3**3) % (3**7) 3^3=27 3^73=72187 + + instance->generic.serial = (fixed / 27) % 2187; + // pin = (fixed // 3**10) % (3**9) 3^10=59049 3^9=19683 + pin = (fixed / 59049) % 19683; + + if(pin <= 9999) { + furi_string_cat_printf(output, " pin:%d", pin); + } else if(pin <= 11029) { + furi_string_cat_printf(output, " pin:enter"); + } + + int pin_suffix = 0; + // pin_suffix = (fixed // 3**19) % 3 3^19=1162261467 + pin_suffix = (fixed / 1162261467) % 3; + + if(pin_suffix == 1) { + furi_string_cat_printf(output, " #\r\n"); + } else if(pin_suffix == 2) { + furi_string_cat_printf(output, " *\r\n"); + } else { + furi_string_cat_printf(output, "\r\n"); + } + furi_string_cat_printf( + output, + "Sn:0x%08lX\r\n" + "Cnt:0x%03lX " + "Sw_id:0x%X\r\n", + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } else { + //id = fixed / 27; + instance->generic.serial = fixed / 27; + if(instance->generic.btn == 1) { + furi_string_cat_printf(output, " Btn:left\r\n"); + } else if(instance->generic.btn == 0) { + furi_string_cat_printf(output, " Btn:middle\r\n"); + } else if(instance->generic.btn == 2) { //-V547 + furi_string_cat_printf(output, " Btn:right\r\n"); + } + + furi_string_cat_printf( + output, + "Sn:0x%08lX\r\n" + "Cnt:0x%03lX " + "Sw_id:0x%X\r\n", + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } +} diff --git a/applications/main/subghz/protocols/secplus_v1.h b/applications/main/subghz/protocols/secplus_v1.h new file mode 100644 index 000000000..99480b62b --- /dev/null +++ b/applications/main/subghz/protocols/secplus_v1.h @@ -0,0 +1,113 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_SECPLUS_V1_NAME "Security+ 1.0" + +typedef struct SubGhzProtocolDecoderSecPlus_v1 SubGhzProtocolDecoderSecPlus_v1; +typedef struct SubGhzProtocolEncoderSecPlus_v1 SubGhzProtocolEncoderSecPlus_v1; + +extern const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder; +extern const SubGhzProtocol subghz_protocol_secplus_v1; + +/** + * Allocate SubGhzProtocolEncoderSecPlus_v1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSecPlus_v1* pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void subghz_protocol_encoder_secplus_v1_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void subghz_protocol_encoder_secplus_v1_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderSecPlus_v1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSecPlus_v1* pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + */ +void* subghz_protocol_decoder_secplus_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + */ +void subghz_protocol_decoder_secplus_v1_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + */ +void subghz_protocol_decoder_secplus_v1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_secplus_v1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. + * @param fixed fixed parts + * @return true On success + */ +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/secplus_v2.c b/applications/main/subghz/protocols/secplus_v2.c new file mode 100644 index 000000000..bcef90dad --- /dev/null +++ b/applications/main/subghz/protocols/secplus_v2.c @@ -0,0 +1,833 @@ +#include "secplus_v2.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* +* Help +* https://github.com/argilo/secplus +* https://github.com/merbanan/rtl_433/blob/master/src/devices/secplus_v2.c +*/ + +#define TAG "SubGhzProtocoSecPlus_v2" + +#define SECPLUS_V2_HEADER 0x3C0000000000 +#define SECPLUS_V2_HEADER_MASK 0xFFFF3C0000000000 +#define SECPLUS_V2_PACKET_1 0x000000000000 +#define SECPLUS_V2_PACKET_2 0x010000000000 +#define SECPLUS_V2_PACKET_MASK 0x30000000000 + +static const SubGhzBlockConst subghz_protocol_secplus_v2_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 110, + .min_count_bit_for_found = 62, +}; + +struct SubGhzProtocolDecoderSecPlus_v2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + ManchesterState manchester_saved_state; + uint64_t secplus_packet_1; +}; + +struct SubGhzProtocolEncoderSecPlus_v2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + uint64_t secplus_packet_1; +}; + +typedef enum { + SecPlus_v2DecoderStepReset = 0, + SecPlus_v2DecoderStepDecoderData, +} SecPlus_v2DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder = { + .alloc = subghz_protocol_decoder_secplus_v2_alloc, + .free = subghz_protocol_decoder_secplus_v2_free, + + .feed = subghz_protocol_decoder_secplus_v2_feed, + .reset = subghz_protocol_decoder_secplus_v2_reset, + + .get_hash_data = subghz_protocol_decoder_secplus_v2_get_hash_data, + .serialize = subghz_protocol_decoder_secplus_v2_serialize, + .deserialize = subghz_protocol_decoder_secplus_v2_deserialize, + .get_string = subghz_protocol_decoder_secplus_v2_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder = { + .alloc = subghz_protocol_encoder_secplus_v2_alloc, + .free = subghz_protocol_encoder_secplus_v2_free, + + .deserialize = subghz_protocol_encoder_secplus_v2_deserialize, + .stop = subghz_protocol_encoder_secplus_v2_stop, + .yield = subghz_protocol_encoder_secplus_v2_yield, +}; + +const SubGhzProtocol subghz_protocol_secplus_v2 = { + .name = SUBGHZ_PROTOCOL_SECPLUS_V2_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_secplus_v2_decoder, + .encoder = &subghz_protocol_secplus_v2_encoder, +}; + +void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v2)); + + instance->base.protocol = &subghz_protocol_secplus_v2; + 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_secplus_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_secplus_v2_mix_invet(uint8_t invert, uint16_t p[]) { + // selectively invert buffers + switch(invert) { + case 0x00: // 0b0000 (True, True, False), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + break; + case 0x01: // 0b0001 (False, True, False), + p[1] = ~p[1] & 0x03FF; + break; + case 0x02: // 0b0010 (False, False, True), + p[2] = ~p[2] & 0x03FF; + break; + case 0x04: // 0b0100 (True, True, True), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x05: // 0b0101 (True, False, True), + case 0x0a: // 0b1010 (True, False, True), + p[0] = ~p[0] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x06: // 0b0110 (False, True, True), + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x08: // 0b1000 (True, False, False), + p[0] = ~p[0] & 0x03FF; + break; + case 0x09: // 0b1001 (False, False, False), + break; + default: + FURI_LOG_E(TAG, "Invert FAIL"); + return false; + } + return true; +} + +static bool subghz_protocol_secplus_v2_mix_order_decode(uint8_t order, uint16_t p[]) { + uint16_t a = p[0], b = p[1], c = p[2]; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + p[2] = a; + // p[1]: no change + p[0] = c; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + p[1] = a; + p[2] = b; + p[0] = c; + break; + case 0x01: // 0b0001 2, 0, 1], + p[2] = a; + p[0] = b; + p[1] = c; + break; + case 0x00: // 0b0000 0, 2, 1], + // p[0]: no change + p[2] = b; + p[1] = c; + break; + case 0x05: // 0b0101 1, 0, 2], + p[1] = a; + p[0] = b; + // p[2]: no change + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + // no reordering + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + return true; +} + +static bool subghz_protocol_secplus_v2_mix_order_encode(uint8_t order, uint16_t p[]) { + uint16_t a, b, c; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + a = p[2]; + b = p[1]; + c = p[0]; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + a = p[1]; + b = p[2]; + c = p[0]; + break; + case 0x01: // 0b0001 2, 0, 1], + a = p[2]; + b = p[0]; + c = p[1]; + break; + case 0x00: // 0b0000 0, 2, 1], + a = p[0]; + b = p[2]; + c = p[1]; + break; + case 0x05: // 0b0101 1, 0, 2], + a = p[1]; + b = p[0]; + c = p[2]; + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + a = p[0]; + b = p[1]; + c = p[2]; + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + + p[0] = a; + p[1] = b; + p[2] = c; + return true; +} + +/** + * Security+ 2.0 half-message decoding + * @param data data + * @param roll_array[] return roll_array part + * @param fixed[] return fixed part + * @return true On success + */ + +static bool + subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { + uint8_t order = (data >> 34) & 0x0f; + uint8_t invert = (data >> 30) & 0x0f; + uint16_t p[3] = {0}; + + for(int i = 29; i >= 0; i -= 3) { + p[0] = p[0] << 1 | bit_read(data, i); + p[1] = p[1] << 1 | bit_read(data, i - 1); + p[2] = p[2] << 1 | bit_read(data, i - 2); + } + + if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return false; + if(!subghz_protocol_secplus_v2_mix_order_decode(order, p)) return false; + + data = order << 4 | invert; + int k = 0; + for(int i = 6; i >= 0; i -= 2) { + roll_array[k] = (data >> i) & 0x03; + if(roll_array[k++] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + for(int i = 8; i >= 0; i -= 2) { + roll_array[k] = (p[2] >> i) & 0x03; + if(roll_array[k++] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + fixed[0] = p[0] << 10 | p[1]; + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param packet_1 first part of the message + */ +static void + subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { + uint32_t fixed_1[1]; + uint8_t roll_1[9] = {0}; + uint32_t fixed_2[1]; + uint8_t roll_2[9] = {0}; + uint8_t rolling_digits[18] = {0}; + + if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && + subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { + rolling_digits[0] = roll_2[8]; + rolling_digits[1] = roll_1[8]; + + rolling_digits[2] = roll_2[4]; + rolling_digits[3] = roll_2[5]; + rolling_digits[4] = roll_2[6]; + rolling_digits[5] = roll_2[7]; + + rolling_digits[6] = roll_1[4]; + rolling_digits[7] = roll_1[5]; + rolling_digits[8] = roll_1[6]; + rolling_digits[9] = roll_1[7]; + + rolling_digits[10] = roll_2[0]; + rolling_digits[11] = roll_2[1]; + rolling_digits[12] = roll_2[2]; + rolling_digits[13] = roll_2[3]; + + rolling_digits[14] = roll_1[0]; + rolling_digits[15] = roll_1[1]; + rolling_digits[16] = roll_1[2]; + rolling_digits[17] = roll_1[3]; + + uint32_t rolling = 0; + for(int i = 0; i < 18; i++) { + rolling = (rolling * 3) + rolling_digits[i]; + } + // Max value = 2^28 (268435456) + if(rolling >= 0x10000000) { + FURI_LOG_E(TAG, "Rolling FAIL"); + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } else { + instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); + instance->btn = fixed_1[0] >> 12; + instance->serial = fixed_1[0] << 20 | fixed_2[0]; + } + } else { + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } +} + +/** + * Security+ 2.0 half-message encoding + * @param roll_array[] roll_array part + * @param fixed[] fixed part + * @return return data + */ + +static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uint32_t fixed) { + uint64_t data = 0; + uint16_t p[3] = {(fixed >> 10) & 0x3FF, fixed & 0x3FF, 0}; + uint8_t order = roll_array[0] << 2 | roll_array[1]; + uint8_t invert = roll_array[2] << 2 | roll_array[3]; + p[2] = (uint16_t)roll_array[4] << 8 | roll_array[5] << 6 | roll_array[6] << 4 | + roll_array[7] << 2 | roll_array[8]; + + if(!subghz_protocol_secplus_v2_mix_order_encode(order, p)) return 0; + if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return 0; + + for(int i = 0; i < 10; i++) { + data <<= 3; + data |= bit_read(p[0], 9 - i) << 2 | bit_read(p[1], 9 - i) << 1 | bit_read(p[2], 9 - i); + } + data |= ((uint64_t)order) << 34 | ((uint64_t)invert) << 30; + + return data; +} + +/** + * Security+ 2.0 message encoding + * @param instance SubGhzProtocolEncoderSecPlus_v2* + */ + +static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* instance) { + uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; + uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; + uint8_t rolling_digits[18] = {0}; + uint8_t roll_1[9] = {0}; + uint8_t roll_2[9] = {0}; + + instance->generic.cnt++; + //ToDo it is not known what value the counter starts + if(instance->generic.cnt > 0xFFFFFFF) instance->generic.cnt = 0xE500000; + uint32_t rolling = subghz_protocol_blocks_reverse_key(instance->generic.cnt, 28); + + for(int8_t i = 17; i > -1; i--) { + rolling_digits[i] = rolling % 3; + rolling /= 3; + } + + roll_2[8] = rolling_digits[0]; + roll_1[8] = rolling_digits[1]; + + roll_2[4] = rolling_digits[2]; + roll_2[5] = rolling_digits[3]; + roll_2[6] = rolling_digits[4]; + roll_2[7] = rolling_digits[5]; + + roll_1[4] = rolling_digits[6]; + roll_1[5] = rolling_digits[7]; + roll_1[6] = rolling_digits[8]; + roll_1[7] = rolling_digits[9]; + + roll_2[0] = rolling_digits[10]; + roll_2[1] = rolling_digits[11]; + roll_2[2] = rolling_digits[12]; + roll_2[3] = rolling_digits[13]; + + roll_1[0] = rolling_digits[14]; + roll_1[1] = rolling_digits[15]; + roll_1[2] = rolling_digits[16]; + roll_1[3] = rolling_digits[17]; + + instance->secplus_packet_1 = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_1 | + subghz_protocol_secplus_v2_encode_half(roll_1, fixed_1[0]); + instance->generic.data = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_2 | + subghz_protocol_secplus_v2_encode_half(roll_2, fixed_2[0]); +} + +static LevelDuration + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_secplus_v2_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_secplus_v2_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_secplus_v2_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_secplus_v2_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 SubGhzProtocolEncoderSecPlus_v2 instance + */ +static void + subghz_protocol_encoder_secplus_v2_get_upload(SubGhzProtocolEncoderSecPlus_v2* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + //Send data packet 1 + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_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)subghz_protocol_secplus_v2_const.te_long * 136); + + //Send data packet 2 + manchester_encoder_reset(&enc_state); + 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_secplus_v2_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_secplus_v2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_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)subghz_protocol_secplus_v2_const.te_long * 136); + + instance->encoder.size_upload = index; +} + +bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_secplus_v2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Secplus_packet_1"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; + } + + subghz_protocol_secplus_v2_remote_controller( + &instance->generic, instance->secplus_packet_1); + subghz_protocol_secplus_v2_encode(instance); + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_secplus_v2_get_upload(instance); + + //update data + 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; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> (i * 8)) & 0xFF; + } + if(!flipper_format_update_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + break; + } + + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_secplus_v2_stop(void* context) { + SubGhzProtocolEncoderSecPlus_v2* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) { + SubGhzProtocolEncoderSecPlus_v2* 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; +} + +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.btn = btn; + instance->generic.data_count_bit = + (uint8_t)subghz_protocol_secplus_v2_const.min_count_bit_for_found; + subghz_protocol_secplus_v2_encode(instance); + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + 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->secplus_packet_1 >> (i * 8)) & 0xFF; + } + + if(res && + !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + res = false; + } + return res; +} + +void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v2)); + instance->base.protocol = &subghz_protocol_secplus_v2; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_secplus_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + free(instance); +} + +void subghz_protocol_decoder_secplus_v2_reset(void* context) { + furi_assert(context); + // SubGhzProtocolDecoderSecPlus_v2* instance = context; + // does not reset the decoder because you need to get 2 parts of the package +} + +static bool subghz_protocol_secplus_v2_check_packet(SubGhzProtocolDecoderSecPlus_v2* instance) { + if((instance->decoder.decode_data & SECPLUS_V2_HEADER_MASK) == SECPLUS_V2_HEADER) { + if((instance->decoder.decode_data & SECPLUS_V2_PACKET_MASK) == SECPLUS_V2_PACKET_1) { + instance->secplus_packet_1 = instance->decoder.decode_data; + } else if( + ((instance->decoder.decode_data & SECPLUS_V2_PACKET_MASK) == SECPLUS_V2_PACKET_2) && + (instance->secplus_packet_1)) { + return true; + } + } + return false; +} + +void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case SecPlus_v2DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_long * 130) < + subghz_protocol_secplus_v2_const.te_delta * 100)) { + //Found header Security+ 2.0 + instance->decoder.parser_step = SecPlus_v2DecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->secplus_packet_1 = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } + break; + case SecPlus_v2DecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_short) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_long) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_secplus_v2_const.te_long * 2UL + + subghz_protocol_secplus_v2_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_secplus_v2_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(subghz_protocol_secplus_v2_check_packet(instance)) { + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = SecPlus_v2DecoderStepReset; + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = SecPlus_v2DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_short) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_long) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = SecPlus_v2DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_secplus_v2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + 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->secplus_packet_1 >> (i * 8)) & 0xFF; + } + + if(res && + !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_secplus_v2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Secplus_packet_1"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + subghz_protocol_secplus_v2_remote_controller(&instance->generic, instance->secplus_packet_1); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Pk1:0x%lX%08lX\r\n" + "Pk2:0x%lX%08lX\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%03lX\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->secplus_packet_1 >> 32), + (uint32_t)instance->secplus_packet_1, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/applications/main/subghz/protocols/secplus_v2.h b/applications/main/subghz/protocols/secplus_v2.h new file mode 100644 index 000000000..bea8cdb5d --- /dev/null +++ b/applications/main/subghz/protocols/secplus_v2.h @@ -0,0 +1,125 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_SECPLUS_V2_NAME "Security+ 2.0" + +typedef struct SubGhzProtocolDecoderSecPlus_v2 SubGhzProtocolDecoderSecPlus_v2; +typedef struct SubGhzProtocolEncoderSecPlus_v2 SubGhzProtocolEncoderSecPlus_v2; + +extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder; +extern const SubGhzProtocol subghz_protocol_secplus_v2; + +/** + * Allocate SubGhzProtocolEncoderSecPlus_v2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSecPlus_v2* pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void subghz_protocol_encoder_secplus_v2_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void subghz_protocol_encoder_secplus_v2_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 32 bit + * @param btn Button number, 8 bit + * @param cnt Container value, 28 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + SubGhzRadioPreset* preset); + +/** + * Allocate SubGhzProtocolDecoderSecPlus_v2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSecPlus_v2* pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + */ +void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + */ +void subghz_protocol_decoder_secplus_v2_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + */ +void subghz_protocol_decoder_secplus_v2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/smc5326.c b/applications/main/subghz/protocols/smc5326.c new file mode 100644 index 000000000..9c9b5d4fd --- /dev/null +++ b/applications/main/subghz/protocols/smc5326.c @@ -0,0 +1,387 @@ +#include "smc5326.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://datasheetspdf.com/pdf-file/532079/Aslic/AX5326-4/1 + * + */ + +#define TAG "SubGhzProtocolSMC5326" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_smc5326_const = { + .te_short = 300, + .te_long = 900, + .te_delta = 200, + .min_count_bit_for_found = 25, +}; + +struct SubGhzProtocolDecoderSMC5326 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderSMC5326 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + SMC5326DecoderStepReset = 0, + SMC5326DecoderStepSaveDuration, + SMC5326DecoderStepCheckDuration, +} SMC5326DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_smc5326_decoder = { + .alloc = subghz_protocol_decoder_smc5326_alloc, + .free = subghz_protocol_decoder_smc5326_free, + + .feed = subghz_protocol_decoder_smc5326_feed, + .reset = subghz_protocol_decoder_smc5326_reset, + + .get_hash_data = subghz_protocol_decoder_smc5326_get_hash_data, + .serialize = subghz_protocol_decoder_smc5326_serialize, + .deserialize = subghz_protocol_decoder_smc5326_deserialize, + .get_string = subghz_protocol_decoder_smc5326_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_smc5326_encoder = { + .alloc = subghz_protocol_encoder_smc5326_alloc, + .free = subghz_protocol_encoder_smc5326_free, + + .deserialize = subghz_protocol_encoder_smc5326_deserialize, + .stop = subghz_protocol_encoder_smc5326_stop, + .yield = subghz_protocol_encoder_smc5326_yield, +}; + +const SubGhzProtocol subghz_protocol_smc5326 = { + .name = SUBGHZ_PROTOCOL_SMC5326_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_smc5326_decoder, + .encoder = &subghz_protocol_smc5326_encoder, +}; + +void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSMC5326* instance = malloc(sizeof(SubGhzProtocolEncoderSMC5326)); + + instance->base.protocol = &subghz_protocol_smc5326; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_smc5326_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSMC5326* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @return true On success + */ +static bool subghz_protocol_encoder_smc5326_get_upload(SubGhzProtocolEncoderSMC5326* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + } + } + + //Send Stop bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send PT_GUARD + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 25); + + return true; +} + +bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSMC5326* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_smc5326_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_smc5326_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_smc5326_stop(void* context) { + SubGhzProtocolEncoderSMC5326* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_smc5326_yield(void* context) { + SubGhzProtocolEncoderSMC5326* 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_smc5326_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSMC5326* instance = malloc(sizeof(SubGhzProtocolDecoderSMC5326)); + instance->base.protocol = &subghz_protocol_smc5326; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_smc5326_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + free(instance); +} + +void subghz_protocol_decoder_smc5326_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + instance->decoder.parser_step = SMC5326DecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + + switch(instance->decoder.parser_step) { + case SMC5326DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_short * 24) < + subghz_protocol_smc5326_const.te_delta * 12)) { + //Found Preambula + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case SMC5326DecoderStepSaveDuration: + //save duration + if(level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = SMC5326DecoderStepCheckDuration; + } + break; + case SMC5326DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_smc5326_const.te_long * 2)) { + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + if(instance->decoder.decode_count_bit == + subghz_protocol_smc5326_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 4 + 1); + + 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; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_smc5326_const.te_short) < + subghz_protocol_smc5326_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_long) < + subghz_protocol_smc5326_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_smc5326_const.te_long) < + subghz_protocol_smc5326_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_short) < + subghz_protocol_smc5326_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SMC5326DecoderStepReset; + } + } else { + instance->decoder.parser_step = SMC5326DecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_smc5326_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_smc5326_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + res = true; + } while(false); + + return res; +} + +static void subghz_protocol_smc5326_get_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( + output, + "%s%s%s%s\r\n", + (((event >> 6) & 0x3) == 0x3 ? "B1 " : ""), + (((event >> 4) & 0x3) == 0x3 ? "B2 " : ""), + (((event >> 2) & 0x3) == 0x3 ? "B3 " : ""), + (((event >> 0) & 0x3) == 0x3 ? "B4 " : "")); +} + +void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + uint32_t data = (uint32_t)((instance->generic.data >> 9) & 0xFFFF); + + furi_string_cat_printf( + output, + "%s %ubit\r\n" + "Key:%07lX Te:%luus\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN " ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0x1FFFFFF), + instance->te, + SHOW_DIP_P(data, DIP_P), + SHOW_DIP_P(data, DIP_O)); + subghz_protocol_smc5326_get_event_serialize(instance->generic.data >> 1, output); + furi_string_cat_printf(output, " -: " DIP_PATTERN "\r\n", SHOW_DIP_P(data, DIP_N)); +} diff --git a/applications/main/subghz/protocols/smc5326.h b/applications/main/subghz/protocols/smc5326.h new file mode 100644 index 000000000..ddc954bd5 --- /dev/null +++ b/applications/main/subghz/protocols/smc5326.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SMC5326_NAME "SMC5326" + +typedef struct SubGhzProtocolDecoderSMC5326 SubGhzProtocolDecoderSMC5326; +typedef struct SubGhzProtocolEncoderSMC5326 SubGhzProtocolEncoderSMC5326; + +extern const SubGhzProtocolDecoder subghz_protocol_smc5326_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_smc5326_encoder; +extern const SubGhzProtocol subghz_protocol_smc5326; + +/** + * Allocate SubGhzProtocolEncoderSMC5326. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSMC5326* pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSMC5326. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void subghz_protocol_encoder_smc5326_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void subghz_protocol_encoder_smc5326_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_smc5326_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderSMC5326. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSMC5326* pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void* subghz_protocol_decoder_smc5326_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void subghz_protocol_decoder_smc5326_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void subghz_protocol_decoder_smc5326_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_smc5326_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/somfy_keytis.c b/applications/main/subghz/protocols/somfy_keytis.c new file mode 100644 index 000000000..ab9202cc3 --- /dev/null +++ b/applications/main/subghz/protocols/somfy_keytis.c @@ -0,0 +1,797 @@ +#include "somfy_keytis.h" +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolSomfyKeytis" + +static const SubGhzBlockConst subghz_protocol_somfy_keytis_const = { + .te_short = 640, + .te_long = 1280, + .te_delta = 250, + .min_count_bit_for_found = 80, +}; + +struct SubGhzProtocolDecoderSomfyKeytis { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + ManchesterState manchester_saved_state; + uint32_t press_duration_counter; +}; + +struct SubGhzProtocolEncoderSomfyKeytis { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + SomfyKeytisDecoderStepReset = 0, + SomfyKeytisDecoderStepCheckPreambula, + SomfyKeytisDecoderStepFoundPreambula, + SomfyKeytisDecoderStepStartDecode, + SomfyKeytisDecoderStepDecoderData, +} SomfyKeytisDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_somfy_keytis_decoder = { + .alloc = subghz_protocol_decoder_somfy_keytis_alloc, + .free = subghz_protocol_decoder_somfy_keytis_free, + + .feed = subghz_protocol_decoder_somfy_keytis_feed, + .reset = subghz_protocol_decoder_somfy_keytis_reset, + + .get_hash_data = subghz_protocol_decoder_somfy_keytis_get_hash_data, + .serialize = subghz_protocol_decoder_somfy_keytis_serialize, + .deserialize = subghz_protocol_decoder_somfy_keytis_deserialize, + .get_string = subghz_protocol_decoder_somfy_keytis_get_string, +}; + +const SubGhzProtocol subghz_protocol_somfy_keytis = { + .name = SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_somfy_keytis_decoder, + .encoder = &subghz_protocol_somfy_keytis_encoder, +}; + +const SubGhzProtocolEncoder subghz_protocol_somfy_keytis_encoder = { + .alloc = subghz_protocol_encoder_somfy_keytis_alloc, + .free = subghz_protocol_encoder_somfy_keytis_free, + + .deserialize = subghz_protocol_encoder_somfy_keytis_deserialize, + .stop = subghz_protocol_encoder_somfy_keytis_stop, + .yield = subghz_protocol_encoder_somfy_keytis_yield, +}; + +void* subghz_protocol_encoder_somfy_keytis_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSomfyKeytis* instance = malloc(sizeof(SubGhzProtocolEncoderSomfyKeytis)); + + instance->base.protocol = &subghz_protocol_somfy_keytis; + 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_decoder_somfy_keytis_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSomfyKeytis* instance = malloc(sizeof(SubGhzProtocolDecoderSomfyKeytis)); + instance->base.protocol = &subghz_protocol_somfy_keytis; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_encoder_somfy_keytis_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSomfyKeytis* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void subghz_protocol_decoder_somfy_keytis_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + free(instance); +} + +void subghz_protocol_decoder_somfy_keytis_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +static bool + subghz_protocol_somfy_keytis_gen_data(SubGhzProtocolEncoderSomfyKeytis* instance, uint8_t btn) { + UNUSED(btn); + uint64_t data = instance->generic.data ^ (instance->generic.data >> 8); + instance->generic.btn = (data >> 48) & 0xF; + instance->generic.cnt = (data >> 24) & 0xFFFF; + instance->generic.serial = data & 0xFFFFFF; + + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + + uint8_t frame[10]; + frame[0] = (0xA << 4) | instance->generic.btn; + frame[1] = 0xF << 4; + frame[2] = instance->generic.cnt >> 8; + frame[3] = instance->generic.cnt; + frame[4] = instance->generic.serial >> 16; + frame[5] = instance->generic.serial >> 8; + frame[6] = instance->generic.serial; + frame[7] = 0xC4; + frame[8] = 0x00; + frame[9] = 0x19; + + uint8_t checksum = 0; + for(uint8_t i = 0; i < 7; i++) { + checksum = checksum ^ frame[i] ^ (frame[i] >> 4); + } + checksum &= 0xF; + + frame[1] |= checksum; + + for(uint8_t i = 1; i < 7; i++) { + frame[i] ^= frame[i - 1]; + } + data = 0; + for(uint8_t i = 0; i < 7; ++i) { + data <<= 8; + data |= frame[i]; + } + instance->generic.data = data; + data = 0; + for(uint8_t i = 7; i < 10; ++i) { + data <<= 8; + data |= frame[i]; + } + instance->generic.data_2 = data; + return true; +} + +bool subghz_protocol_somfy_keytis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderSomfyKeytis* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.data_count_bit = 80; + bool res = subghz_protocol_somfy_keytis_gen_data(instance, btn); + if(res) { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ +static bool subghz_protocol_encoder_somfy_keytis_get_upload( + SubGhzProtocolEncoderSomfyKeytis* instance, + uint8_t btn) { + furi_assert(instance); + + //gen new key + if(subghz_protocol_somfy_keytis_gen_data(instance, btn)) { + //ToDo if you need to add a callback to automatically update the data on the display + } else { + return false; + } + + size_t index = 0; + + //Send header + //Wake up + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)9415); // 1 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)89565); // 0 + //Hardware sync + for(uint8_t i = 0; i < 12; ++i) { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 4); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 4); // 0 + } + //Software sync + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)4550); // 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + + //Send key data MSB manchester + + for(uint8_t i = instance->generic.data_count_bit - 24; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration *= 2; // 00 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } + + } else { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_HIGH) { + instance->encoder.upload[index - 1].duration *= 2; // 11 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } else { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } + } + } + + for(uint8_t i = 24; i > 0; i--) { + if(bit_read(instance->generic.data_2, i - 1)) { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration *= 2; // 00 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } + + } else { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_HIGH) { + instance->encoder.upload[index - 1].duration *= 2; // 11 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } else { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } + } + } + + //Inter-frame silence + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration += + (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 3; + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 3); + } + + for(uint8_t i = 0; i < 2; ++i) { + //Hardware sync + for(uint8_t i = 0; i < 6; ++i) { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 4); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 4); // 0 + } + //Software sync + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)4550); // 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + + //Send key data MSB manchester + + for(uint8_t i = instance->generic.data_count_bit - 24; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration *= 2; // 00 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } + + } else { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_HIGH) { + instance->encoder.upload[index - 1].duration *= 2; // 11 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } else { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } + } + } + + for(uint8_t i = 24; i > 0; i--) { + if(bit_read(instance->generic.data_2, i - 1)) { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration *= 2; // 00 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + } + + } else { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_HIGH) { + instance->encoder.upload[index - 1].duration *= 2; // 11 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } else { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short); // 0 + } + } + } + //Inter-frame silence + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration += + (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 3; + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 3); + } + } + + //Inter-frame silence + instance->encoder.upload[index - 1].duration += + (uint32_t)30415 - (uint32_t)subghz_protocol_somfy_keytis_const.te_short * 3; + + size_t size_upload = index; + + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + return true; +} + +bool subghz_protocol_encoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSomfyKeytis* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_somfy_keytis_get_upload(instance, instance->generic.btn); + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_somfy_keytis_stop(void* context) { + SubGhzProtocolEncoderSomfyKeytis* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_somfy_keytis_yield(void* context) { + SubGhzProtocolEncoderSomfyKeytis* 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; +} + +/** + * Сhecksum calculation. + * @param data Вata for checksum calculation + * @return CRC + */ +static uint8_t subghz_protocol_somfy_keytis_crc(uint64_t data) { + uint8_t crc = 0; + data &= 0xFFF0FFFFFFFFFF; + for(uint8_t i = 0; i < 56; i += 8) { + crc = crc ^ data >> i ^ (data >> (i + 4)); + } + return crc & 0xf; +} + +void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case SomfyKeytisDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 4) < + subghz_protocol_somfy_keytis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyKeytisDecoderStepFoundPreambula; + instance->header_count++; + } + break; + case SomfyKeytisDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 4) < + subghz_protocol_somfy_keytis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyKeytisDecoderStepCheckPreambula; + } else { + instance->header_count = 0; + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } + break; + case SomfyKeytisDecoderStepCheckPreambula: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 4) < + subghz_protocol_somfy_keytis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyKeytisDecoderStepFoundPreambula; + instance->header_count++; + } else if( + (instance->header_count > 1) && + (DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 7) < + subghz_protocol_somfy_keytis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyKeytisDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->press_duration_counter = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + } + } + + break; + + case SomfyKeytisDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_long) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_somfy_keytis_const.te_long + + subghz_protocol_somfy_keytis_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_somfy_keytis_const.min_count_bit_for_found) { + //check crc + uint64_t data_tmp = instance->generic.data ^ (instance->generic.data >> 8); + if(((data_tmp >> 40) & 0xF) == subghz_protocol_somfy_keytis_crc(data_tmp)) { + 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); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } else { + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_long) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + if(instance->decoder.decode_count_bit < 56) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + } else { + instance->press_duration_counter = (instance->press_duration_counter << 1) | + data; + } + + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_somfy_keytis_check_remote_controller(SubGhzBlockGeneric* instance) { + //https://pushstack.wordpress.com/somfy-rts-protocol/ + /* + * 604 us + * / + * | 2416us | 2416us | 2416us | 2416us | 4550 us | | + * + * +--------+ +--------+ +---...---+ + * + +--------+ +--------+ +--+XXXX...XXX+ + * + * | hw. sync. | soft. | | + * | | sync. | data | + * + * + * encrypt | decrypt + * + * package 80 bit pdc key btn crc cnt serial + * + * 0xA453537C4B9855 C40019 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 C80026 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 CC0033 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 D00049 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 D4005C => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 D80063 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 DC0076 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 E00086 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 E40093 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 E800AC => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 EC00B9 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 F000C3 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 F400D6 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 F800E9 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC00FC => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC0102 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC0113 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC0120 => 0xA 4 F 7 002F 37D3CD + * .......... + * 0xA453537C4B9855 FC048F => 0xA 4 F 7 002F 37D3CD + * + * Pdc: "Press Duration Counter" the total delay of the button is sent 72 parcels, + * pdc cnt4b cnt8b pdc_crc + * C40019 => 11 0001 00 0000 00000001 1001 + * C80026 => 11 0010 00 0000 00000010 0110 + * CC0033 => 11 0011 00 0000 00000011 0011 + * D00049 => 11 0100 00 0000 00000100 1001 + * D4005C => 11 0101 00 0000 00000101 1100 + * D80063 => 11 0110 00 0000 00000110 0011 + * DC0076 => 11 0111 00 0000 00000111 0110 + * E00086 => 11 1000 00 0000 00001000 0110 + * E40093 => 11 1001 00 0000 00001001 0011 + * E800AC => 11 1010 00 0000 00001010 1100 + * EC00B9 => 11 1011 00 0000 00001011 1001 + * F000C3 => 11 1100 00 0000 00001100 0011 + * F400D6 => 11 1101 00 0000 00001101 0110 + * F800E9 => 11 1110 00 0000 00001110 1001 + * FC00FC => 11 1111 00 0000 00001111 1100 + * FC0102 => 11 1111 00 0000 00010000 0010 + * FC0113 => 11 1111 00 0000 00010001 0011 + * FC0120 => 11 1111 00 0000 00010010 0000 + * + * Cnt4b: 4-bit counter changes from 1 to 15 then always equals 15 + * Cnt8b: 8-bit counter changes from 1 to 72 (0x48) + * Ppdc_crc: + * uint8_t crc=0; + * for(i=4; i<24; i+=4){ + * crc ^=(pdc>>i); + * } + * return crc; + * example: crc = 1^0^0^4^C = 9 + * 11, 00, 0000: const + * + * Key: “Encryption Key”, Most significant 4-bit are always 0xA, Least Significant bits is + * a linear counter. In the Smoove Origin this counter is increased together with the + * rolling code. But leaving this on a constant value also works. Gerardwr notes that + * for some other types of remotes the MSB is not constant. + * Btn: 4-bit Control codes, this indicates the button that is pressed + * CRC: 4-bit Checksum. + * Ctn: 16-bit rolling code (big-endian) increased with every button press. + * Serial: 24-bit identifier of sending device (little-endian) + * + * + * Decrypt + * + * uint8_t frame[7]; + * for (i=1; i < 7; i++) { + * frame[i] = frame[i] ^ frame[i-1]; + * } + * or + * uint64 Decrypt = frame ^ (frame>>8); + * + * CRC + * + * uint8_t frame[7]; + * for (i=0; i < 7; i++) { + * crc = crc ^ frame[i] ^ (frame[i] >> 4); + * } + * crc = crc & 0xf; + * + */ + + uint64_t data = instance->data ^ (instance->data >> 8); + instance->btn = (data >> 48) & 0xF; + instance->cnt = (data >> 24) & 0xFFFF; + instance->serial = data & 0xFFFFFF; +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_somfy_keytis_get_name_button(uint8_t btn) { + const char* name_btn[0x10] = { + "Unknown", + "0x01", + "0x02", + "Prog", + "Key_1", + "0x05", + "0x06", + "0x07", + "0x08", + "0x09", + "0x0A", + "0x0B", + "0x0C", + "0x0D", + "0x0E", + "0x0F"}; + return btn <= 0xf ? name_btn[btn] : name_btn[0]; +} + +uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_somfy_keytis_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32( + flipper_format, "Duration_Counter", &instance->press_duration_counter, 1)) { + FURI_LOG_E(TAG, "Unable to add Duration_Counter"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_somfy_keytis_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32( + flipper_format, + "Duration_Counter", + (uint32_t*)&instance->press_duration_counter, + 1)) { + FURI_LOG_E(TAG, "Missing Duration_Counter"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + + subghz_protocol_somfy_keytis_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "%lX%08lX%06lX\r\n" + "Sn:0x%06lX \r\n" + "Cnt:0x%04lX\r\n" + "Btn:%s\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->press_duration_counter, + instance->generic.serial, + instance->generic.cnt, + subghz_protocol_somfy_keytis_get_name_button(instance->generic.btn)); +} diff --git a/applications/main/subghz/protocols/somfy_keytis.h b/applications/main/subghz/protocols/somfy_keytis.h new file mode 100644 index 000000000..b08da3641 --- /dev/null +++ b/applications/main/subghz/protocols/somfy_keytis.h @@ -0,0 +1,125 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME "Somfy Keytis" + +typedef struct SubGhzProtocolDecoderSomfyKeytis SubGhzProtocolDecoderSomfyKeytis; +typedef struct SubGhzProtocolEncoderSomfyKeytis SubGhzProtocolEncoderSomfyKeytis; + +extern const SubGhzProtocolDecoder subghz_protocol_somfy_keytis_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_somfy_keytis_encoder; +extern const SubGhzProtocol subghz_protocol_somfy_keytis; + +/** + * Allocate SubGhzProtocolEncoderSomfyKeytis. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSomfyKeytis* pointer to a SubGhzProtocolEncoderSomfyKeytis instance + */ +void* subghz_protocol_encoder_somfy_keytis_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance + */ +void subghz_protocol_encoder_somfy_keytis_free(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_somfy_keytis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance + */ +void subghz_protocol_encoder_somfy_keytis_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSomfyKeytis instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_somfy_keytis_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderSomfyKeytis. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSomfyKeytis* pointer to a SubGhzProtocolDecoderSomfyKeytis instance + */ +void* subghz_protocol_decoder_somfy_keytis_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + */ +void subghz_protocol_decoder_somfy_keytis_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + */ +void subghz_protocol_decoder_somfy_keytis_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_somfy_keytis_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param output Resulting text + */ +void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/somfy_telis.c b/applications/main/subghz/protocols/somfy_telis.c new file mode 100644 index 000000000..96997c581 --- /dev/null +++ b/applications/main/subghz/protocols/somfy_telis.c @@ -0,0 +1,663 @@ +#include "somfy_telis.h" +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolSomfyTelis" + +static const SubGhzBlockConst subghz_protocol_somfy_telis_const = { + .te_short = 640, + .te_long = 1280, + .te_delta = 250, + .min_count_bit_for_found = 56, +}; + +struct SubGhzProtocolDecoderSomfyTelis { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderSomfyTelis { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + SomfyTelisDecoderStepReset = 0, + SomfyTelisDecoderStepCheckPreambula, + SomfyTelisDecoderStepFoundPreambula, + SomfyTelisDecoderStepStartDecode, + SomfyTelisDecoderStepDecoderData, +} SomfyTelisDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_somfy_telis_decoder = { + .alloc = subghz_protocol_decoder_somfy_telis_alloc, + .free = subghz_protocol_decoder_somfy_telis_free, + + .feed = subghz_protocol_decoder_somfy_telis_feed, + .reset = subghz_protocol_decoder_somfy_telis_reset, + + .get_hash_data = subghz_protocol_decoder_somfy_telis_get_hash_data, + .serialize = subghz_protocol_decoder_somfy_telis_serialize, + .deserialize = subghz_protocol_decoder_somfy_telis_deserialize, + .get_string = subghz_protocol_decoder_somfy_telis_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_somfy_telis_encoder = { + .alloc = subghz_protocol_encoder_somfy_telis_alloc, + .free = subghz_protocol_encoder_somfy_telis_free, + + .deserialize = subghz_protocol_encoder_somfy_telis_deserialize, + .stop = subghz_protocol_encoder_somfy_telis_stop, + .yield = subghz_protocol_encoder_somfy_telis_yield, +}; + +const SubGhzProtocol subghz_protocol_somfy_telis = { + .name = SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_somfy_telis_decoder, + .encoder = &subghz_protocol_somfy_telis_encoder, +}; + +void* subghz_protocol_encoder_somfy_telis_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSomfyTelis* instance = malloc(sizeof(SubGhzProtocolEncoderSomfyTelis)); + + instance->base.protocol = &subghz_protocol_somfy_telis; + 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_somfy_telis_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSomfyTelis* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool + subghz_protocol_somfy_telis_gen_data(SubGhzProtocolEncoderSomfyTelis* instance, uint8_t btn) { + UNUSED(btn); + uint64_t data = instance->generic.data ^ (instance->generic.data >> 8); + instance->generic.btn = (data >> 44) & 0xF; // ctrl + instance->generic.cnt = (data >> 24) & 0xFFFF; // rolling code + instance->generic.serial = data & 0xFFFFFF; // address + + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + + uint8_t frame[7]; + frame[0] = data >> 48; + frame[1] = instance->generic.btn << 4; + frame[2] = instance->generic.cnt >> 8; + frame[3] = instance->generic.cnt; + frame[4] = instance->generic.serial >> 16; + frame[5] = instance->generic.serial >> 8; + frame[6] = instance->generic.serial; + + uint8_t checksum = 0; + for(uint8_t i = 0; i < 7; i++) { + checksum = checksum ^ frame[i] ^ (frame[i] >> 4); + } + checksum &= 0xF; + + frame[1] |= checksum; + + for(uint8_t i = 1; i < 7; i++) { + frame[i] ^= frame[i - 1]; + } + data = 0; + for(uint8_t i = 0; i < 7; ++i) { + data <<= 8; + data |= frame[i]; + } + instance->generic.data = data; + return true; +} + +bool subghz_protocol_somfy_telis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderSomfyTelis* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.data_count_bit = 56; + bool res = subghz_protocol_somfy_telis_gen_data(instance, btn); + if(res) { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ +static bool subghz_protocol_encoder_somfy_telis_get_upload( + SubGhzProtocolEncoderSomfyTelis* instance, + uint8_t btn) { + furi_assert(instance); + + //gen new key + if(subghz_protocol_somfy_telis_gen_data(instance, btn)) { + //ToDo if you need to add a callback to automatically update the data on the display + } else { + return false; + } + + size_t index = 0; + + //Send header + //Wake up + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)9415); // 1 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)89565); // 0 + //Hardware sync + for(uint8_t i = 0; i < 2; ++i) { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short * 4); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short * 4); // 0 + } + //Software sync + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)4550); // 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + + //Send key data MSB manchester + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration *= 2; // 00 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 1 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 1 + } + + } else { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_HIGH) { + instance->encoder.upload[index - 1].duration *= 2; // 11 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + } else { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + } + } + } + + //Inter-frame silence + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration += (uint32_t)30415; + } else { + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)30415); + } + + //Retransmission + for(uint8_t i = 0; i < 2; i++) { + //Hardware sync + for(uint8_t i = 0; i < 7; ++i) { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short * 4); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short * 4); // 0 + } + //Software sync + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)4550); // 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + + //Send key data MSB manchester + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration *= 2; // 00 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 1 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 1 + } + + } else { + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_HIGH) { + instance->encoder.upload[index - 1].duration *= 2; // 11 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + } else { + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_somfy_telis_const.te_short); // 0 + } + } + } + + //Inter-frame silence + if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { + instance->encoder.upload[index - 1].duration += (uint32_t)30415; + } else { + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)30415); + } + } + + size_t size_upload = index; + + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + return true; +} + +bool subghz_protocol_encoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSomfyTelis* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_somfy_telis_get_upload(instance, instance->generic.btn); + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_somfy_telis_stop(void* context) { + SubGhzProtocolEncoderSomfyTelis* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_somfy_telis_yield(void* context) { + SubGhzProtocolEncoderSomfyTelis* 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_somfy_telis_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSomfyTelis* instance = malloc(sizeof(SubGhzProtocolDecoderSomfyTelis)); + instance->base.protocol = &subghz_protocol_somfy_telis; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_somfy_telis_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + free(instance); +} + +void subghz_protocol_decoder_somfy_telis_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +/** + * Сhecksum calculation. + * @param data Вata for checksum calculation + * @return CRC + */ +static uint8_t subghz_protocol_somfy_telis_crc(uint64_t data) { + uint8_t crc = 0; + data &= 0xFFF0FFFFFFFFFF; + for(uint8_t i = 0; i < 56; i += 8) { + crc = crc ^ data >> i ^ (data >> (i + 4)); + } + return crc & 0xf; +} + +void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case SomfyTelisDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 4) < + subghz_protocol_somfy_telis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyTelisDecoderStepFoundPreambula; + instance->header_count++; + } + break; + case SomfyTelisDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 4) < + subghz_protocol_somfy_telis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyTelisDecoderStepCheckPreambula; + } else { + instance->header_count = 0; + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } + break; + case SomfyTelisDecoderStepCheckPreambula: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 4) < + subghz_protocol_somfy_telis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyTelisDecoderStepFoundPreambula; + instance->header_count++; + } else if( + (instance->header_count > 1) && + (DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 7) < + subghz_protocol_somfy_telis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyTelisDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + } + } + + break; + + case SomfyTelisDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_long) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_somfy_telis_const.te_long + + subghz_protocol_somfy_telis_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_somfy_telis_const.min_count_bit_for_found) { + //check crc + uint64_t data_tmp = instance->decoder.decode_data ^ + (instance->decoder.decode_data >> 8); + if(((data_tmp >> 40) & 0xF) == subghz_protocol_somfy_telis_crc(data_tmp)) { + 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); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } else { + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_long) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { + //https://pushstack.wordpress.com/somfy-rts-protocol/ + /* + * 604 us + * / + * | 2416us | 2416us | 2416us | 2416us | 4550 us | | 67648 us | 30415 us | + * + * +--------+ +--------+ +---...---+ + * + +--------+ +--------+ +--+XXXX...XXX+-----...----- + * + * | hw. sync. | soft. | | Inter-frame + * | | sync. | data | gap + * + * + * encrypt | decrypt + * + * package 56 bit cnt key btn|crc cnt serial + * 0xA7232323312222 - 0 => A7 8 0 | 00 00 | 12 13 00 + * 0xA7222223312222 - 1 => A7 8 5 | 00 01 | 12 13 00 + * 0xA7212123312222 - 2 => A7 8 6 | 00 02 | 12 13 00 + * + * Key: “Encryption Key”, Most significant 4-bit are always 0xA, Least Significant bits is + * a linear counter. In the Smoove Origin this counter is increased together with the + * rolling code. But leaving this on a constant value also works. Gerardwr notes that + * for some other types of remotes the MSB is not constant. + * Btn: 4-bit Control codes, this indicates the button that is pressed + * CRC: 4-bit Checksum. + * Ctn: 16-bit rolling code (big-endian) increased with every button press. + * Serial: 24-bit identifier of sending device (little-endian) + * + * + * Decrypt + * + * uint8_t frame[7]; + * for (i=1; i < 7; i++) { + * frame[i] = frame[i] ^ frame[i-1]; + * } + * or + * uint64 Decrypt = frame ^ (frame>>8); + * + * Btn + * + * Value Button(s) Description + * 0x1 My Stop or move to favourite position + * 0x2 Up Move up + * 0x3 My + Up Set upper motor limit in initial programming mode + * 0x4 Down Move down + * 0x5 My + Down Set lower motor limit in initial programming mode + * 0x6 Up + Down Change motor limit and initial programming mode + * 0x8 Prog Used for (de-)registering remotes, see below + * 0x9 Sun + Flag Enable sun and wind detector (SUN and FLAG symbol on the Telis Soliris RC) + * 0xA Flag Disable sun detector (FLAG symbol on the Telis Soliris RC) + * + * CRC + * + * uint8_t frame[7]; + * for (i=0; i < 7; i++) { + * cksum = cksum ^ frame[i] ^ (frame[i] >> 4); + * } + * cksum = cksum & 0xf; + * + */ + + uint64_t data = instance->data ^ (instance->data >> 8); + instance->btn = (data >> 44) & 0xF; // ctrl + instance->cnt = (data >> 24) & 0xFFFF; // rolling code + instance->serial = data & 0xFFFFFF; // address +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_somfy_telis_get_name_button(uint8_t btn) { + const char* name_btn[16] = { + "Unknown", + "My", + "Up", + "My+Up", + "Down", + "My+Down", + "Up+Down", + "0x07", + "Prog", + "Sun+Flag", + "Flag", + "0x0B", + "0x0C", + "0x0D", + "0x0E", + "0x0F"}; + return btn <= 0xf ? name_btn[btn] : name_btn[0]; +} + +uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_somfy_telis_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_somfy_telis_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + + subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%06lX \r\n" + "Cnt:0x%04lX\r\n" + "Btn:%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, + instance->generic.cnt, + subghz_protocol_somfy_telis_get_name_button(instance->generic.btn)); +} diff --git a/applications/main/subghz/protocols/somfy_telis.h b/applications/main/subghz/protocols/somfy_telis.h new file mode 100644 index 000000000..b5e989866 --- /dev/null +++ b/applications/main/subghz/protocols/somfy_telis.h @@ -0,0 +1,125 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME "Somfy Telis" + +typedef struct SubGhzProtocolDecoderSomfyTelis SubGhzProtocolDecoderSomfyTelis; +typedef struct SubGhzProtocolEncoderSomfyTelis SubGhzProtocolEncoderSomfyTelis; + +extern const SubGhzProtocolDecoder subghz_protocol_somfy_telis_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_somfy_telis_encoder; +extern const SubGhzProtocol subghz_protocol_somfy_telis; + +/** + * Allocate SubGhzProtocolEncoderSomfyTelis. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSomfyTelis* pointer to a SubGhzProtocolEncoderSomfyTelis instance + */ +void* subghz_protocol_encoder_somfy_telis_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance + */ +void subghz_protocol_encoder_somfy_telis_free(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_somfy_telis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance + */ +void subghz_protocol_encoder_somfy_telis_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_somfy_telis_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderSomfyTelis. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSomfyTelis* pointer to a SubGhzProtocolDecoderSomfyTelis instance + */ +void* subghz_protocol_decoder_somfy_telis_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + */ +void subghz_protocol_decoder_somfy_telis_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + */ +void subghz_protocol_decoder_somfy_telis_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_somfy_telis_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param output Resulting text + */ +void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/protocols/star_line.c b/applications/main/subghz/protocols/star_line.c new file mode 100644 index 000000000..3066c6e2b --- /dev/null +++ b/applications/main/subghz/protocols/star_line.c @@ -0,0 +1,780 @@ +#include "star_line.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolStarLine" + +static const SubGhzBlockConst subghz_protocol_star_line_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 120, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderStarLine { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; + const char* manufacture_name; + + FuriString* manufacture_from_file; +}; + +struct SubGhzProtocolEncoderStarLine { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + SubGhzKeystore* keystore; + const char* manufacture_name; + + FuriString* manufacture_from_file; +}; + +typedef enum { + StarLineDecoderStepReset = 0, + StarLineDecoderStepCheckPreambula, + StarLineDecoderStepSaveDuration, + StarLineDecoderStepCheckDuration, +} StarLineDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_star_line_decoder = { + .alloc = subghz_protocol_decoder_star_line_alloc, + .free = subghz_protocol_decoder_star_line_free, + + .feed = subghz_protocol_decoder_star_line_feed, + .reset = subghz_protocol_decoder_star_line_reset, + + .get_hash_data = subghz_protocol_decoder_star_line_get_hash_data, + .serialize = subghz_protocol_decoder_star_line_serialize, + .deserialize = subghz_protocol_decoder_star_line_deserialize, + .get_string = subghz_protocol_decoder_star_line_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_star_line_encoder = { + .alloc = subghz_protocol_encoder_star_line_alloc, + .free = subghz_protocol_encoder_star_line_free, + + .deserialize = subghz_protocol_encoder_star_line_deserialize, + .stop = subghz_protocol_encoder_star_line_stop, + .yield = subghz_protocol_encoder_star_line_yield, +}; + +const SubGhzProtocol subghz_protocol_star_line = { + .name = SUBGHZ_PROTOCOL_STAR_LINE_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_star_line_decoder, + .encoder = &subghz_protocol_star_line_encoder, +}; + +static const char* mfname; + +static int kl_type; + +void star_line_reset_mfname() { + mfname = ""; +} + +void star_line_reset_kl_type() { + kl_type = 0; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ +static void subghz_protocol_star_line_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name); + +void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderStarLine* instance = malloc(sizeof(SubGhzProtocolEncoderStarLine)); + + instance->base.protocol = &subghz_protocol_star_line; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + instance->manufacture_from_file = furi_string_alloc(); + + 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_star_line_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderStarLine* instance = context; + furi_string_free(instance->manufacture_from_file); + free(instance->encoder.upload); + free(instance); +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq* instance + * @param btn Button number, 4 bit + */ +static bool + subghz_protocol_star_line_gen_data(SubGhzProtocolEncoderStarLine* instance, uint8_t btn) { + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + uint32_t fix = btn << 24 | instance->generic.serial; + uint32_t decrypt = btn << 24 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; + uint32_t hop = 0; + uint64_t man = 0; + uint64_t code_found_reverse; + int res = 0; + + if(instance->manufacture_name == 0x0) { + instance->manufacture_name = ""; + } + + if(strcmp(instance->manufacture_name, "Unknown") == 0) { + code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + hop = code_found_reverse & 0x00000000ffffffff; + } else { + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning + hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); + break; + case KEELOQ_LEARNING_NORMAL: + //Normal Learning + man = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_UNKNOWN: + if(kl_type == 1) { + hop = + subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); + } + if(kl_type == 2) { + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + break; + } + break; + } + } + } + if(hop) { + uint64_t yek = (uint64_t)fix << 32 | hop; + instance->generic.data = + subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); + return true; + } else { + instance->manufacture_name = "Unknown"; + return false; + } +} + +bool subghz_protocol_star_line_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderStarLine* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->manufacture_name = manufacture_name; + instance->generic.data_count_bit = 64; + bool res = subghz_protocol_star_line_gen_data(instance, btn); + if(res) { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ +static bool subghz_protocol_encoder_star_line_get_upload( + SubGhzProtocolEncoderStarLine* instance, + uint8_t btn) { + furi_assert(instance); + + //gen new key + if(subghz_protocol_star_line_gen_data(instance, btn)) { + //ToDo if you need to add a callback to automatically update the data on the display + } else { + return false; + } + + size_t index = 0; + size_t size_upload = 6 * 2 + (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 6; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_long * 2); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_long * 2); + } + + //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 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_short); + } + } + + return true; +} + +bool subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderStarLine* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + // Read manufacturer from file + if(flipper_format_read_string( + flipper_format, "Manufacture", instance->manufacture_from_file)) { + instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file); + mfname = furi_string_get_cstr(instance->manufacture_from_file); + } else { + FURI_LOG_D(TAG, "ENCODER: Missing Manufacture"); + } + + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_star_line_get_upload(instance, instance->generic.btn); + + 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; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_star_line_stop(void* context) { + SubGhzProtocolEncoderStarLine* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_star_line_yield(void* context) { + SubGhzProtocolEncoderStarLine* 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_star_line_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderStarLine* instance = malloc(sizeof(SubGhzProtocolDecoderStarLine)); + instance->base.protocol = &subghz_protocol_star_line; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->manufacture_from_file = furi_string_alloc(); + + instance->keystore = subghz_environment_get_keystore(environment); + + return instance; +} + +void subghz_protocol_decoder_star_line_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + furi_string_free(instance->manufacture_from_file); + + free(instance); +} + +void subghz_protocol_decoder_star_line_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + instance->decoder.parser_step = StarLineDecoderStepReset; + mfname = ""; + kl_type = 0; +} + +void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + switch(instance->decoder.parser_step) { + case StarLineDecoderStepReset: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) < + subghz_protocol_star_line_const.te_delta * 2) { + instance->decoder.parser_step = StarLineDecoderStepCheckPreambula; + instance->header_count++; + } else if(instance->header_count > 4) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.te_last = duration; + instance->decoder.parser_step = StarLineDecoderStepCheckDuration; + } + } else { + instance->header_count = 0; + } + break; + case StarLineDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) < + subghz_protocol_star_line_const.te_delta * 2)) { + //Found Preambula + instance->decoder.parser_step = StarLineDecoderStepReset; + } else { + instance->header_count = 0; + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + case StarLineDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_star_line_const.te_long + + subghz_protocol_star_line_const.te_delta)) { + instance->decoder.parser_step = StarLineDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_star_line_const.min_count_bit_for_found) { + if(instance->generic.data != instance->decoder.decode_data) { + 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->header_count = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = StarLineDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + case StarLineDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_short) < + subghz_protocol_star_line_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_short) < + subghz_protocol_star_line_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = StarLineDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_long) < + subghz_protocol_star_line_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long) < + subghz_protocol_star_line_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = StarLineDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + } +} + +/** + * Validation of decrypt data. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param decrypt Decrypd data + * @param btn Button number, 4 bit + * @param end_serial decrement the last 10 bits of the serial number + * @return true On success + */ +static inline bool subghz_protocol_star_line_check_decrypt( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn, + uint32_t end_serial) { + furi_assert(instance); + if((decrypt >> 24 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { + instance->cnt = decrypt & 0x0000FFFF; + return true; + } + return false; +} + +/** + * Checking the accepted code against the database manafacture key + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param fix Fix part of the parcel + * @param hop Hop encrypted part of the parcel + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + * @return true on successful search + */ +static uint8_t subghz_protocol_star_line_check_remote_controller_selector( + SubGhzBlockGeneric* instance, + uint32_t fix, + uint32_t hop, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint16_t end_serial = (uint16_t)(fix & 0xFF); + uint8_t btn = (uint8_t)(fix >> 24); + uint32_t decrypt = 0; + uint64_t man_normal_learning; + int res = 0; + if(mfname == 0x0) { + mfname = ""; + } + + if(strcmp(mfname, "") == 0) { + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + //########################### + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + // Check for mirrored man + man_normal_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + break; + } + } + } else if(strcmp(mfname, "Unknown") == 0) { + return 1; + } else { + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), mfname); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 1; + return 1; + } + //########################### + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + + // Check for mirrored man + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + mfname = *manufacture_name; + kl_type = 2; + return 1; + } + break; + } + } + } + } + + *manufacture_name = "Unknown"; + mfname = "Unknown"; + instance->cnt = 0; + + return 0; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ +static void subghz_protocol_star_line_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t key_fix = key >> 32; + uint32_t key_hop = key & 0x00000000ffffffff; + + subghz_protocol_star_line_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + + instance->serial = key_fix & 0x00FFFFFF; + instance->btn = key_fix >> 24; +} + +uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_star_line_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + if(res && !flipper_format_write_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { + FURI_LOG_E(TAG, "Unable to add manufacture name"); + res = false; + } + if(res && instance->generic.data_count_bit != + subghz_protocol_star_line_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + // Read manufacturer from file + if(flipper_format_read_string( + flipper_format, "Manufacture", instance->manufacture_from_file)) { + instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file); + mfname = furi_string_get_cstr(instance->manufacture_from_file); + } else { + FURI_LOG_D(TAG, "DECODER: Missing Manufacture"); + } + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%02X\r\n" + "MF:%s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name); +} diff --git a/applications/main/subghz/protocols/star_line.h b/applications/main/subghz/protocols/star_line.h new file mode 100644 index 000000000..e8873d41a --- /dev/null +++ b/applications/main/subghz/protocols/star_line.h @@ -0,0 +1,131 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_STAR_LINE_NAME "Star Line" + +typedef struct SubGhzProtocolDecoderStarLine SubGhzProtocolDecoderStarLine; +typedef struct SubGhzProtocolEncoderStarLine SubGhzProtocolEncoderStarLine; + +extern const SubGhzProtocolDecoder subghz_protocol_star_line_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_star_line_encoder; +extern const SubGhzProtocol subghz_protocol_star_line; + +void star_line_reset_mfname(); + +void star_line_reset_kl_type(); + +/** + * Allocate SubGhzProtocolEncoderStarLine. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderStarLine* pointer to a SubGhzProtocolEncoderStarLine instance + */ +void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderStarLine. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + */ +void subghz_protocol_encoder_star_line_free(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_star_line_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + */ +void subghz_protocol_encoder_star_line_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_star_line_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderStarLine. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderStarLine* pointer to a SubGhzProtocolDecoderStarLine instance + */ +void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + */ +void subghz_protocol_decoder_star_line_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + */ +void subghz_protocol_decoder_star_line_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_star_line_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param output Resulting text + */ +void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output); diff --git a/applications/main/subghz/receiver.c b/applications/main/subghz/receiver.c new file mode 100644 index 000000000..698fe098e --- /dev/null +++ b/applications/main/subghz/receiver.c @@ -0,0 +1,124 @@ +#include "receiver.h" + +#include "registry.h" +#include "protocols/protocol_items.h" + +#include + +typedef struct { + SubGhzProtocolEncoderBase* base; +} SubGhzReceiverSlot; + +ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST); +#define M_OPL_SubGhzReceiverSlotArray_t() ARRAY_OPLIST(SubGhzReceiverSlotArray, M_POD_OPLIST) + +struct SubGhzReceiver { + SubGhzReceiverSlotArray_t slots; + SubGhzProtocolFlag filter; + + SubGhzReceiverCallback callback; + void* context; +}; + +SubGhzReceiver* subghz_receiver_alloc_init(SubGhzEnvironment* environment) { + SubGhzReceiver* instance = malloc(sizeof(SubGhzReceiver)); + SubGhzReceiverSlotArray_init(instance->slots); + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(environment); + + for(size_t i = 0; i < subghz_protocol_registry_count(protocol_registry_items); ++i) { + const SubGhzProtocol* protocol = + subghz_protocol_registry_get_by_index(protocol_registry_items, i); + + if(protocol->decoder && protocol->decoder->alloc) { + SubGhzReceiverSlot* slot = SubGhzReceiverSlotArray_push_new(instance->slots); + slot->base = protocol->decoder->alloc(environment); + } + } + + instance->callback = NULL; + instance->context = NULL; + return instance; +} + +void subghz_receiver_free(SubGhzReceiver* instance) { + furi_assert(instance); + + instance->callback = NULL; + instance->context = NULL; + + // Release allocated slots + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + slot->base->protocol->decoder->free(slot->base); + slot->base = NULL; + } + SubGhzReceiverSlotArray_clear(instance->slots); + + free(instance); +} + +void subghz_receiver_decode(SubGhzReceiver* instance, bool level, uint32_t duration) { + furi_assert(instance); + furi_assert(instance->slots); + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + if((slot->base->protocol->flag & instance->filter) != 0) { + slot->base->protocol->decoder->feed(slot->base, level, duration); + } + } +} + +void subghz_receiver_reset(SubGhzReceiver* instance) { + furi_assert(instance); + furi_assert(instance->slots); + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + slot->base->protocol->decoder->reset(slot->base); + } +} + +static void subghz_receiver_rx_callback(SubGhzProtocolDecoderBase* decoder_base, void* context) { + SubGhzReceiver* instance = context; + if(instance->callback) { + instance->callback(instance, decoder_base, instance->context); + } +} + +void subghz_receiver_set_rx_callback( + SubGhzReceiver* instance, + SubGhzReceiverCallback callback, + void* context) { + furi_assert(instance); + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + subghz_protocol_decoder_base_set_decoder_callback( + (SubGhzProtocolDecoderBase*)slot->base, subghz_receiver_rx_callback, instance); + } + + instance->callback = callback; + instance->context = context; +} + +void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag filter) { + furi_assert(instance); + instance->filter = filter; +} + +SubGhzProtocolDecoderBase* subghz_receiver_search_decoder_base_by_name( + SubGhzReceiver* instance, + const char* decoder_name) { + SubGhzProtocolDecoderBase* result = NULL; + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + if(strcmp(slot->base->protocol->name, decoder_name) == 0) { + result = (SubGhzProtocolDecoderBase*)slot->base; + break; + } + } + return result; +} diff --git a/applications/main/subghz/receiver.h b/applications/main/subghz/receiver.h new file mode 100644 index 000000000..2ef722d1f --- /dev/null +++ b/applications/main/subghz/receiver.h @@ -0,0 +1,73 @@ +#pragma once + +#include "types.h" +#include "protocols/base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzReceiver SubGhzReceiver; + +typedef void (*SubGhzReceiverCallback)( + SubGhzReceiver* decoder, + SubGhzProtocolDecoderBase* decoder_base, + void* context); + +/** + * Allocate and init SubGhzReceiver. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzReceiver* pointer to a SubGhzReceiver instance + */ +SubGhzReceiver* subghz_receiver_alloc_init(SubGhzEnvironment* environment); + +/** + * Free SubGhzReceiver. + * @param instance Pointer to a SubGhzReceiver instance + */ +void subghz_receiver_free(SubGhzReceiver* instance); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param instance Pointer to a SubGhzReceiver instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_receiver_decode(SubGhzReceiver* instance, bool level, uint32_t duration); + +/** + * Reset decoder SubGhzReceiver. + * @param instance Pointer to a SubGhzReceiver instance + */ +void subghz_receiver_reset(SubGhzReceiver* instance); + +/** + * Set a callback upon completion of successful decoding of one of the protocols. + * @param instance Pointer to a SubGhzReceiver instance + * @param callback Callback, SubGhzReceiverCallback + * @param context Context + */ +void subghz_receiver_set_rx_callback( + SubGhzReceiver* instance, + SubGhzReceiverCallback callback, + void* context); + +/** + * Set the filter of receivers that will work at the moment. + * @param instance Pointer to a SubGhzReceiver instance + * @param filter Filter, SubGhzProtocolFlag + */ +void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag filter); + +/** + * Search for a cattery by his name. + * @param instance Pointer to a SubGhzReceiver instance + * @param decoder_name Receiver name + * @return SubGhzProtocolDecoderBase* pointer to a SubGhzProtocolDecoderBase instance + */ +SubGhzProtocolDecoderBase* + subghz_receiver_search_decoder_base_by_name(SubGhzReceiver* instance, const char* decoder_name); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/registry.c b/applications/main/subghz/registry.c new file mode 100644 index 000000000..d0c22ea8c --- /dev/null +++ b/applications/main/subghz/registry.c @@ -0,0 +1,30 @@ +#include "registry.h" + +const SubGhzProtocol* subghz_protocol_registry_get_by_name( + const SubGhzProtocolRegistry* protocol_registry, + const char* name) { + furi_assert(protocol_registry); + + for(size_t i = 0; i < subghz_protocol_registry_count(protocol_registry); i++) { + if(strcmp(name, protocol_registry->items[i]->name) == 0) { + return protocol_registry->items[i]; + } + } + return NULL; +} + +const SubGhzProtocol* subghz_protocol_registry_get_by_index( + const SubGhzProtocolRegistry* protocol_registry, + size_t index) { + furi_assert(protocol_registry); + if(index < subghz_protocol_registry_count(protocol_registry)) { + return protocol_registry->items[index]; + } else { + return NULL; + } +} + +size_t subghz_protocol_registry_count(const SubGhzProtocolRegistry* protocol_registry) { + furi_assert(protocol_registry); + return protocol_registry->size; +} diff --git a/applications/main/subghz/registry.h b/applications/main/subghz/registry.h new file mode 100644 index 000000000..91027807e --- /dev/null +++ b/applications/main/subghz/registry.h @@ -0,0 +1,47 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzEnvironment SubGhzEnvironment; + +typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; + +struct SubGhzProtocolRegistry { + const SubGhzProtocol** items; + const size_t size; +}; + +/** + * Registration by name SubGhzProtocol. + * @param protocol_registry SubGhzProtocolRegistry + * @param name Protocol name + * @return SubGhzProtocol* pointer to a SubGhzProtocol instance + */ +const SubGhzProtocol* subghz_protocol_registry_get_by_name( + const SubGhzProtocolRegistry* protocol_registry, + const char* name); + +/** + * Registration protocol by index in array SubGhzProtocol. + * @param protocol_registry SubGhzProtocolRegistry + * @param index Protocol by index in array + * @return SubGhzProtocol* pointer to a SubGhzProtocol instance + */ +const SubGhzProtocol* subghz_protocol_registry_get_by_index( + const SubGhzProtocolRegistry* protocol_registry, + size_t index); + +/** + * Getting the number of registered protocols. + * @param protocol_registry SubGhzProtocolRegistry + * @return Number of protocols + */ +size_t subghz_protocol_registry_count(const SubGhzProtocolRegistry* protocol_registry); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 1ad41a8b5..5acd534dc 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -26,6 +26,7 @@ ADD_SCENE(subghz, set_fix_bft, SetFixBft) ADD_SCENE(subghz, set_cnt_bft, SetCntBft) ADD_SCENE(subghz, set_seed_bft, SetSeedBft) ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer) +ADD_SCENE(subghz, ext_module_settings, ExtModuleSettings) ADD_SCENE(subghz, read_raw, ReadRAW) ADD_SCENE(subghz, more_raw, MoreRAW) ADD_SCENE(subghz, decode_raw, DecodeRAW) diff --git a/applications/main/subghz/scenes/subghz_scene_decode_raw.c b/applications/main/subghz/scenes/subghz_scene_decode_raw.c index 6194d0dba..5746efbed 100644 --- a/applications/main/subghz/scenes/subghz_scene_decode_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_decode_raw.c @@ -170,11 +170,6 @@ void subghz_scene_decode_raw_on_enter(void* context) { subghz_receiver_set_rx_callback( subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); - // make sure we're not in auto-detect mode, which is only meant for the Read app - subghz_protocol_decoder_raw_set_auto_mode( - subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME), - false); subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); if(subghz->decode_raw_state == SubGhzDecodeRawStateStart) { diff --git a/applications/main/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c index 4f00df70b..de17d7a86 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_success.c @@ -32,7 +32,6 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); } else if(scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneSaved)) { - //scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); } else { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); diff --git a/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c b/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c new file mode 100644 index 000000000..7d7a505cb --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_ext_module_settings.c @@ -0,0 +1,92 @@ +#include "../subghz_i.h" +#include "../helpers/subghz_custom_event.h" + +uint8_t value_index; +uint8_t value_index2; + +#define EXT_MODULES_COUNT (sizeof(radio_modules_variables_text) / sizeof(char* const)) +const char* const radio_modules_variables_text[] = { + "Internal", + "External", +}; + +#define DEBUG_P_COUNT 2 +const char* const debug_pin_text[DEBUG_P_COUNT] = { + "OFF", + "A7", +}; + +static void subghz_scene_ext_module_changed(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + value_index = variable_item_get_current_value_index(item); + UNUSED(subghz); + + variable_item_set_current_value_text(item, radio_modules_variables_text[value_index]); +} +static void subghz_ext_module_start_var_list_enter_callback(void* context, uint32_t index) { + SubGhz* subghz = context; + view_dispatcher_send_custom_event(subghz->view_dispatcher, index); +} + +static void subghz_scene_receiver_config_set_debug_pin(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, debug_pin_text[index]); + + subghz->txrx->debug_pin_state = index == 1; +} + +void subghz_scene_ext_module_settings_on_enter(void* context) { + SubGhz* subghz = context; + + VariableItemList* variable_item_list = subghz->variable_item_list; + + value_index = furi_hal_subghz.radio_type; + VariableItem* item = variable_item_list_add( + variable_item_list, "Module", EXT_MODULES_COUNT, subghz_scene_ext_module_changed, subghz); + + variable_item_list_set_enter_callback( + variable_item_list, subghz_ext_module_start_var_list_enter_callback, subghz); + + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, radio_modules_variables_text[value_index]); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + item = variable_item_list_add( + subghz->variable_item_list, + "Debug Pin:", + DEBUG_P_COUNT, + subghz_scene_receiver_config_set_debug_pin, + subghz); + value_index2 = subghz->txrx->debug_pin_state; + variable_item_set_current_value_index(item, value_index2); + variable_item_set_current_value_text(item, debug_pin_text[value_index2]); + } + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); +} + +bool subghz_scene_ext_module_settings_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + UNUSED(subghz); + UNUSED(event); + + // Set selected radio module + furi_hal_subghz_set_radio_type(value_index); + + // Check if module is present, if no -> show error + if(!furi_hal_subghz_check_radio()) { + value_index = 0; + furi_hal_subghz_set_radio_type(value_index); + furi_string_set(subghz->error_str, "Please connect\nexternal radio"); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); + } + + return false; +} + +void subghz_scene_ext_module_settings_on_exit(void* context) { + SubGhz* subghz = context; + variable_item_list_reset(subghz->variable_item_list); +} diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index fea4b6aef..e396527cc 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -119,9 +119,6 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME); furi_assert(subghz->txrx->decoder_result); - // make sure we're not in auto-detect mode, which is only meant for the Read app - subghz_protocol_decoder_raw_set_auto_mode(subghz->txrx->decoder_result, false); - //set filter RAW feed subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_RAW); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReadRAW); @@ -422,10 +419,6 @@ void subghz_scene_read_raw_on_exit(void* context) { subghz->state_notifications = SubGhzNotificationStateIDLE; notification_message(subghz->notifications, &sequence_reset_rgb); -//filter restoration -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - subghz_last_settings_set_detect_raw_values(subghz); -#else - subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); -#endif + //filter restoration + subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); } \ No newline at end of file diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index cab19f730..e1ea08497 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" #include "../views/receiver.h" #include +#include #define TAG "SubGhzSceneReceiver" @@ -53,7 +54,10 @@ static void subghz_scene_receiver_update_statusbar(void* context) { } else { subghz_get_frequency_modulation(subghz, frequency_str, NULL); furi_string_printf( - modulation_str, "Mod: %s", furi_string_get_cstr(subghz->txrx->preset->name)); + modulation_str, + "%s Mod: %s", + furi_hal_subghz_get_radio_type() ? "Ext" : "Int", + furi_string_get_cstr(subghz->txrx->preset->name)); } #else subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); @@ -157,6 +161,11 @@ void subghz_scene_receiver_on_enter(void* context) { } subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); + //to use a universal decoder, we are looking for a link to it + subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( + subghz->txrx->receiver, SUBGHZ_PROTOCOL_BIN_RAW_NAME); + furi_assert(subghz->txrx->decoder_result); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); } @@ -173,7 +182,6 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_sleep(subghz); } subghz->txrx->hopper_state = SubGhzHopperStateOFF; - subghz_history_set_hopper_state(subghz->txrx->history, false); subghz->txrx->idx_menu_chosen = 0; subghz_receiver_set_rx_callback(subghz->txrx->receiver, NULL, subghz); @@ -221,6 +229,13 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_hopper_update(subghz); subghz_scene_receiver_update_statusbar(subghz); } + + //get RSSI + float rssi = furi_hal_subghz_get_rssi(); + subghz_receiver_rssi(subghz->subghz_receiver, rssi); + subghz_protocol_decoder_bin_raw_data_input_rssi( + (SubGhzProtocolDecoderBinRAW*)subghz->txrx->decoder_result, rssi); + switch(subghz->state_notifications) { case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index c23d93496..af4c6aca3 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -1,16 +1,11 @@ #include "../subghz_i.h" #include -#include - -#define TAG "SubGhzSceneReceiverConfig" - enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, - SubGhzSettingIndexDetectRaw, - SubGhzSettingIndexRSSIThreshold, + SubGhzSettingIndexBinRAW, SubGhzSettingIndexSound, SubGhzSettingIndexLock, SubGhzSettingIndexRAWThesholdRSSI, @@ -45,35 +40,6 @@ const float raw_theshold_rssi_value[RAW_THRESHOLD_RSSI_COUNT] = { -40.0f, }; -#define BANDWIDTH_COUNT 16 -const char* const bandwidth_labels[BANDWIDTH_COUNT] = { - "58 kHz", - "68 kHz", - "81 kHz", - "102 kHz", - "116 kHz", - "135 kHz", - "162 kHz", - "203 kHz", - "232 kHz", - "270 kHz", - "325 kHz", - "406 kHz", - "464 kHz", - "541 kHz", - "650 kHz", - "812 kHz", -}; - -// Bandwidths values are ordered from F (58kHz) to 0 (812kHz) -#define BANDWIDTH_INDEX(value) ((uint8_t)15 - ((uint8_t)(value >> 4) & 0x0F)) - -#define MANCHESTER_FLAG_COUNT 2 -const char* const manchester_flag_text[MANCHESTER_FLAG_COUNT] = { - "OFF", - "ON", -}; - #define HOPPING_COUNT 2 const char* const hopping_text[HOPPING_COUNT] = { "OFF", @@ -84,40 +50,6 @@ const uint32_t hopping_value[HOPPING_COUNT] = { SubGhzHopperStateRunnig, }; -#define DETECT_RAW_COUNT 2 -const char* const detect_raw_text[DETECT_RAW_COUNT] = { - "OFF", - "ON", -}; - -#ifndef SUBGHZ_SAVE_DETECT_RAW_SETTING -const SubGhzProtocolFlag detect_raw_value[DETECT_RAW_COUNT] = { - SubGhzProtocolFlag_Decodable, - SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_RAW, -}; -#endif - -#define RSSI_THRESHOLD_COUNT 7 -const char* const rssi_threshold_text[RSSI_THRESHOLD_COUNT] = { - "-72db", - "-67db", - "-62db", - "-57db", - "-52db", - "-47db", - "-42db", -}; - -const int rssi_threshold_value[RSSI_THRESHOLD_COUNT] = { - -72, - -67, - -62, - -57, - -52, - -47, - -42, -}; - #define SPEAKER_COUNT 2 const char* const speaker_text[SPEAKER_COUNT] = { "OFF", @@ -127,18 +59,15 @@ const uint32_t speaker_value[SPEAKER_COUNT] = { SubGhzSpeakerStateShutdown, SubGhzSpeakerStateEnable, }; - -// Allow advanced edit only on specific preset -bool subghz_scene_receiver_config_can_edit_current_preset(SubGhz* subghz) { - SubGhzRadioPreset* preset = subghz->txrx->preset; - - bool preset_name_allow_edit = - !strcmp(furi_string_get_cstr(preset->name), ADVANCED_AM_PRESET_NAME) || - !strcmp(furi_string_get_cstr(preset->name), "CUSTOM"); - - return preset && preset_name_allow_edit && - subghz_preset_custom_is_ook_modulation(preset->data, preset->data_size); -} +#define BIN_RAW_COUNT 2 +const char* const bin_raw_text[BIN_RAW_COUNT] = { + "OFF", + "ON", +}; +const uint32_t bin_raw_value[BIN_RAW_COUNT] = { + SubGhzProtocolFlag_Decodable, + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW, +}; uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); @@ -170,52 +99,6 @@ uint8_t subghz_scene_receiver_config_next_preset(const char* preset_name, void* return index; } -// Advanced settings of preset may change if preset was changed. -// In that case - update values -static void subghz_scene_receiver_config_update_advanced(SubGhz* subghz) { - uint8_t value_index; - - if(subghz->variable_item_bandwidth) { - value_index = BANDWIDTH_INDEX(subghz->txrx->raw_bandwidth); - variable_item_set_current_value_index(subghz->variable_item_bandwidth, value_index); - variable_item_set_current_value_text( - subghz->variable_item_bandwidth, bandwidth_labels[value_index]); - } - - if(subghz->variable_item_datarate) { - variable_item_set_current_value_index(subghz->variable_item_datarate, 0); - - char datarate_str[16] = {0}; - subghz_preset_custom_printf_datarate( - subghz->txrx->raw_datarate, datarate_str, sizeof(datarate_str)); - variable_item_set_current_value_text(subghz->variable_item_datarate, datarate_str); - } - - if(subghz->variable_item_manchester) { - value_index = subghz->txrx->raw_manchester_enabled ? 1 : 0; - - variable_item_set_current_value_index(subghz->variable_item_manchester, value_index); - variable_item_set_current_value_text( - subghz->variable_item_manchester, manchester_flag_text[value_index]); - } -} - -// Apply advanced configuration to advanced am preset -static void subghz_scene_receiver_config_apply_advanced(SubGhz* subghz) { - if(subghz_scene_receiver_config_can_edit_current_preset(subghz)) { - SubGhzRadioPreset* preset = subghz->txrx->preset; - - subghz_preset_custom_set_bandwidth( - preset->data, preset->data_size, subghz->txrx->raw_bandwidth); - - subghz_preset_custom_set_machester_enable( - preset->data, preset->data_size, subghz->txrx->raw_manchester_enabled); - - subghz_preset_custom_set_datarate( - preset->data, preset->data_size, subghz->txrx->raw_datarate); - } -} - uint8_t subghz_scene_receiver_config_hopper_value_index( const uint32_t value, const uint32_t values[], @@ -236,36 +119,6 @@ uint8_t subghz_scene_receiver_config_hopper_value_index( } } -#ifndef SUBGHZ_SAVE_DETECT_RAW_SETTING -uint8_t subghz_scene_receiver_config_detect_raw_value_index( - const SubGhzProtocolFlag value, - const SubGhzProtocolFlag values[], - uint8_t values_count) { - uint8_t index = 0; - for(uint8_t i = 0; i < values_count; i++) { - if(value == values[i]) { - index = i; - break; - } - } - return index; -} -#endif - -uint8_t subghz_scene_receiver_config_rssi_threshold_value_index( - const int value, - const int values[], - uint8_t values_count) { - uint8_t index = 0; - for(uint8_t i = 0; i < values_count; i++) { - if(value == values[i]) { - index = i; - break; - } - } - return index; -} - static void subghz_scene_receiver_config_set_frequency(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -301,49 +154,12 @@ static void subghz_scene_receiver_config_set_preset(VariableItem* item) { subghz->txrx->preset->frequency, subghz_setting_get_preset_data(subghz->setting, index), subghz_setting_get_preset_data_size(subghz->setting, index)); - - subghz_scene_receiver_config_update_advanced(subghz); -} - -static void subghz_scene_receiver_config_set_rssi_threshold(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, rssi_threshold_text[index]); - subghz_protocol_decoder_raw_set_rssi_threshold( - subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME), - rssi_threshold_value[index]); -} - -static void subghz_scene_receiver_config_set_detect_raw(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - //if(subghz->txrx->hopper_state == 0) { - variable_item_set_current_value_text(item, detect_raw_text[index]); -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - subghz->last_settings->detect_raw = index; - - subghz_last_settings_set_detect_raw_values(subghz); -#else - subghz_receiver_set_filter(subghz->txrx->receiver, detect_raw_value[index]); - - subghz_protocol_decoder_raw_set_auto_mode( - subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME), - (index == 1)); -#endif - /*} else { - variable_item_set_current_value_index(item, 0); - }*/ } static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - //if(subghz_receiver_get_filter(subghz->txrx->receiver) == SubGhzProtocolFlag_Decodable) { variable_item_set_current_value_text(item, hopping_text[index]); if(hopping_value[index] == SubGhzHopperStateOFF) { char text_buf[10] = {0}; @@ -374,10 +190,6 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) } subghz->txrx->hopper_state = hopping_value[index]; - subghz_history_set_hopper_state(subghz->txrx->history, (index == 1)); - /*} else { - variable_item_set_current_value_index(item, 0); - }*/ } static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { @@ -388,6 +200,15 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { subghz->txrx->speaker_state = speaker_value[index]; } +static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, bin_raw_text[index]); + subghz->txrx->filter = bin_raw_value[index]; + subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); +} + static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -396,107 +217,6 @@ static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* it subghz->txrx->raw_threshold_rssi = raw_theshold_rssi_value[index]; } -static void subghz_scene_receiver_config_set_raw_ook_bandwidth(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - if(subghz_scene_receiver_config_can_edit_current_preset(subghz)) { - // update bandwidth value from selected index - uint8_t index = variable_item_get_current_value_index(item); - subghz->txrx->raw_bandwidth = subghz_preset_custom_bandwidth_values[index]; - - subghz_scene_receiver_config_update_advanced(subghz); - } else { - furi_string_set( - subghz->error_str, "Read-only\nsetting!\nUse '" ADVANCED_AM_PRESET_NAME "'\npreset."); - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzCustomEventSceneSettingError); - } -} - -static void subghz_scene_receiver_config_set_manchester_flag(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - if(subghz_scene_receiver_config_can_edit_current_preset(subghz)) { - // update enable flag from view - uint8_t index = variable_item_get_current_value_index(item); - subghz->txrx->raw_manchester_enabled = index == 0 ? false : true; - - subghz_scene_receiver_config_update_advanced(subghz); - } else { - furi_string_set( - subghz->error_str, "Read-only\nsetting!\nUse '" ADVANCED_AM_PRESET_NAME "'\npreset."); - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzCustomEventSceneSettingError); - } -} - -static void subghz_scene_receiver_config_datarate_input_callback(void* context) { - furi_assert(context); - SubGhz* subghz = context; - - float value = atoff(subghz->datarate_input_str); - if(value != 0 && value > 0) { - subghz->txrx->raw_datarate = value; - subghz_scene_receiver_config_update_advanced(subghz); - } - - // show list view - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); -} - -static bool subghz_scene_receiver_config_datarate_input_validate( - const char* text, - FuriString* error, - void* context) { - UNUSED(context); - - float value = atoff(text); - if(value == 0) { - furi_string_printf(error, "Cannot parse\r\nvalue"); - } else if(value < 0) { - furi_string_printf(error, "Value\r\nshould be\r\ngreater\r\nthan 0"); - } else { - return true; - } - - return false; -} - -static void subghz_scene_receiver_config_show_datarate_input(SubGhz* subghz) { - TextInput* text_input = subghz->text_input; - - snprintf( - subghz->datarate_input_str, - sizeof(subghz->datarate_input_str), - "%.2f", - (double)subghz->txrx->raw_datarate); - - text_input_set_header_text(text_input, "Datarate bauds (not kBauds)"); - text_input_set_result_callback( - text_input, - subghz_scene_receiver_config_datarate_input_callback, - subghz, - subghz->datarate_input_str, - sizeof(subghz->datarate_input_str), - false); - - text_input_set_validator( - text_input, subghz_scene_receiver_config_datarate_input_validate, NULL); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); -} - -static void subghz_scene_receiver_config_set_datarate(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - if(subghz_scene_receiver_config_can_edit_current_preset(subghz)) { - // reset value index in order to show '>' symbol always - variable_item_set_current_value_index(item, 0); - subghz_scene_receiver_config_show_datarate_input(subghz); - } else { - furi_string_set( - subghz->error_str, "Read-only\nsetting!\nUse '" ADVANCED_AM_PRESET_NAME "'\npreset."); - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzCustomEventSceneSettingError); - } -} - static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { furi_assert(context); SubGhz* subghz = context; @@ -511,13 +231,6 @@ void subghz_scene_receiver_config_on_enter(void* context) { VariableItem* item; uint8_t value_index; -#ifdef FURI_DEBUG - FURI_LOG_D( - TAG, - "Last frequency: %ld, Preset: %ld", - subghz->last_settings->frequency, - subghz->last_settings->preset); -#endif item = variable_item_list_add( subghz->variable_item_list, "Frequency:", @@ -563,52 +276,35 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, hopping_text[value_index]); + } - // Detect Raw + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubGhzCustomEventManagerSet) { item = variable_item_list_add( subghz->variable_item_list, - "Detect Raw:", - DETECT_RAW_COUNT, - subghz_scene_receiver_config_set_detect_raw, + "Bin RAW:", + BIN_RAW_COUNT, + subghz_scene_receiver_config_set_bin_raw, subghz); -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - value_index = subghz->last_settings->detect_raw; -#else - value_index = subghz_scene_receiver_config_detect_raw_value_index( - subghz_receiver_get_filter(subghz->txrx->receiver), - detect_raw_value, - DETECT_RAW_COUNT); -#endif + value_index = value_index_uint32(subghz->txrx->filter, bin_raw_value, BIN_RAW_COUNT); variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, detect_raw_text[value_index]); + variable_item_set_current_value_text(item, bin_raw_text[value_index]); + } - // RSSI - item = variable_item_list_add( - subghz->variable_item_list, - "RSSI for Raw:", - RSSI_THRESHOLD_COUNT, - subghz_scene_receiver_config_set_rssi_threshold, - subghz); - value_index = subghz_scene_receiver_config_rssi_threshold_value_index( - subghz_protocol_encoder_get_rssi_threshold(subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME)), - rssi_threshold_value, - RSSI_THRESHOLD_COUNT); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, rssi_threshold_text[value_index]); + // Enable speaker, will send all incoming noises and signals to speaker so you can listen how your remote sounds like :) + item = variable_item_list_add( + subghz->variable_item_list, + "Sound:", + SPEAKER_COUNT, + subghz_scene_receiver_config_set_speaker, + subghz); + value_index = value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, speaker_text[value_index]); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubGhzCustomEventManagerSet) { // Lock keyboard - item = variable_item_list_add( - subghz->variable_item_list, - "Sound:", - SPEAKER_COUNT, - subghz_scene_receiver_config_set_speaker, - subghz); - value_index = - value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, speaker_text[value_index]); - variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL); variable_item_list_set_enter_callback( subghz->variable_item_list, @@ -627,33 +323,6 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz->txrx->raw_threshold_rssi, raw_theshold_rssi_value, RAW_THRESHOLD_RSSI_COUNT); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, raw_theshold_rssi_text[value_index]); - - // Advanced MODEM settings. RW only for ADVANCED_AM_PRESET_NAME - // Bandwidth - subghz->variable_item_bandwidth = variable_item_list_add( - subghz->variable_item_list, - "Bandwidth:", - BANDWIDTH_COUNT, - subghz_scene_receiver_config_set_raw_ook_bandwidth, - subghz); - - // Data rate (editable via OK click) - subghz->variable_item_datarate = variable_item_list_add( - subghz->variable_item_list, - "Data rate:", - 2, - subghz_scene_receiver_config_set_datarate, - subghz); - - // Manchester codec flag - subghz->variable_item_manchester = variable_item_list_add( - subghz->variable_item_list, - "Manch. Enc.:", - MANCHESTER_FLAG_COUNT, - subghz_scene_receiver_config_set_manchester_flag, - subghz); - - subghz_scene_receiver_config_update_advanced(subghz); } view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } @@ -667,11 +336,6 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even subghz->lock = SubGhzLockOn; scene_manager_previous_scene(subghz->scene_manager); consumed = true; - } else if(event.event == SubGhzCustomEventSceneSettingError) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneShowErrorSub, event.event); - consumed = true; } } return consumed; @@ -679,16 +343,6 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even void subghz_scene_receiver_config_on_exit(void* context) { SubGhz* subghz = context; - - // reset UI variable list items (next scene may be not RAW config) - subghz->variable_item_bandwidth = NULL; - subghz->variable_item_datarate = NULL; - subghz->variable_item_manchester = NULL; - text_input_set_validator(subghz->text_input, NULL, NULL); - - // apply advanced preset variables (if applicable) - subghz_scene_receiver_config_apply_advanced(subghz); - variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); subghz_last_settings_save(subghz->last_settings); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index e9c849e1e..0b265cca2 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -92,8 +92,6 @@ void subghz_scene_receiver_info_draw_widget(SubGhz* subghz) { // Removed static check if(((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && - // disable "Send" for auto-captured RAW signals for now. They can still be saved and sent by loading them. - subghz->txrx->decoder_result->protocol->type != SubGhzProtocolTypeRAW && subghz->txrx->decoder_result->protocol->encoder->deserialize) { widget_add_button_element( subghz->widget, @@ -138,6 +136,21 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_history_get_raw_data( subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); + if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_tx_stop(subghz); + } + if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { + subghz_begin( + subghz, + subghz_setting_get_preset_data_by_name( + subghz->setting, + furi_string_get_cstr(subghz->txrx->preset->name))); + subghz_rx(subghz, subghz->txrx->preset->frequency); + } + if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { + subghz->txrx->hopper_state = SubGhzHopperStateRunnig; + } + subghz->state_notifications = SubGhzNotificationStateRx; } else { subghz->state_notifications = SubGhzNotificationStateTx; } diff --git a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c index 2943c764a..113e7ae74 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c @@ -26,16 +26,8 @@ bool subghz_scene_show_error_sub_on_event(void* context, SceneManagerEvent event SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneShowErrorSub) { - if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowErrorSub) == - SubGhzCustomEventSceneSettingError) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); - scene_manager_search_and_switch_to_previous_scene( - subghz->scene_manager, SubGhzSceneReceiverConfig); - } else { - scene_manager_search_and_switch_to_previous_scene( - subghz->scene_manager, SubGhzSceneStart); - } + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart); return true; } } diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 62e30784f..69e6cbea7 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -10,27 +10,9 @@ enum SubmenuIndex { SubmenuIndexAddManually, SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, + SubmenuIndexExtSettings, }; -void subghz_scene_start_remove_advanced_preset(SubGhz* subghz) { - // delete operation is harmless - subghz_setting_delete_custom_preset(subghz->setting, ADVANCED_AM_PRESET_NAME); -} - -void subghz_scene_start_load_advanced_preset(SubGhz* subghz) { - for(uint8_t i = 0; i < subghz_setting_get_preset_count(subghz->setting); i++) { - if(!strcmp(subghz_setting_get_preset_name(subghz->setting, i), ADVANCED_AM_PRESET_NAME)) { - return; // already exists - } - } - - // Load custom advanced AM preset with configurable CFGMDM settings - FlipperFormat* advanced_am_preset = subghz_preset_custom_advanced_am_preset_alloc(); - subghz_setting_load_custom_preset( - subghz->setting, ADVANCED_AM_PRESET_NAME, advanced_am_preset); - flipper_format_free(advanced_am_preset); -} - void subghz_scene_start_submenu_callback(void* context, uint32_t index) { SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, index); @@ -41,15 +23,6 @@ void subghz_scene_start_on_enter(void* context) { if(subghz->state_notifications == SubGhzNotificationStateStarting) { subghz->state_notifications = SubGhzNotificationStateIDLE; } -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - subghz_last_settings_set_detect_raw_values(subghz); -#else - subghz_protocol_decoder_raw_set_auto_mode( - subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME), - false); - subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); -#endif submenu_add_item( subghz->submenu, "Read", SubmenuIndexRead, subghz_scene_start_submenu_callback, subghz); @@ -73,6 +46,12 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexFrequencyAnalyzer, subghz_scene_start_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Radio Settings", + SubmenuIndexExtSettings, + subghz_scene_start_submenu_callback, + subghz); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); @@ -91,15 +70,23 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { view_dispatcher_stop(subghz->view_dispatcher); return true; } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexReadRAW) { - subghz_scene_start_load_advanced_preset(subghz); + if(event.event == SubmenuIndexExtSettings) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexExtSettings); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneExtModuleSettings); + return true; + + } else if(!furi_hal_subghz_check_radio()) { + furi_string_set(subghz->error_str, "Please connect\nexternal radio"); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); + return true; + } else if(event.event == SubmenuIndexReadRAW) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); return true; } else if(event.event == SubmenuIndexRead) { - subghz_scene_start_remove_advanced_preset(subghz); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRead); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver); @@ -120,7 +107,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer); return true; - } else if(event.event == SubmenuIndexTest) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index d95133da7..39e89e9e9 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -183,7 +183,7 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { //init setting subghz->setting = subghz_setting_alloc(); - subghz_setting_load(subghz->setting, EXT_PATH("subghz/assets/setting_user.txt")); + subghz_setting_load(subghz->setting, EXT_PATH("subghz/assets/setting_user")); // Custom Presets load without using config file @@ -208,37 +208,26 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { flipper_format_free(temp_fm_preset2); - // Pagers + // # HND - FM presets FlipperFormat* temp_fm_preset3 = flipper_format_string_alloc(); flipper_format_write_string_cstr( - temp_fm_preset2, + temp_fm_preset3, (const char*)"Custom_preset_data", - (const char*)"02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00"); + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); flipper_format_rewind(temp_fm_preset3); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"Pagers", temp_fm_preset3); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"HND_1", temp_fm_preset3); flipper_format_free(temp_fm_preset3); - // # HND - FM presets FlipperFormat* temp_fm_preset4 = flipper_format_string_alloc(); flipper_format_write_string_cstr( temp_fm_preset4, (const char*)"Custom_preset_data", - (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); - flipper_format_rewind(temp_fm_preset4); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"Honda_1", temp_fm_preset4); - - flipper_format_free(temp_fm_preset3); - - FlipperFormat* temp_fm_preset5 = flipper_format_string_alloc(); - flipper_format_write_string_cstr( - temp_fm_preset5, - (const char*)"Custom_preset_data", (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); - flipper_format_rewind(temp_fm_preset5); - subghz_setting_load_custom_preset(subghz->setting, (const char*)"Honda_2", temp_fm_preset5); + flipper_format_rewind(temp_fm_preset4); + subghz_setting_load_custom_preset(subghz->setting, (const char*)"HND_2", temp_fm_preset4); - flipper_format_free(temp_fm_preset5); + flipper_format_free(temp_fm_preset4); // custom presets loading - end @@ -247,20 +236,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->last_settings = subghz_last_settings_alloc(); subghz_last_settings_load(subghz->last_settings, 0); #if FURI_DEBUG -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - FURI_LOG_D( - TAG, - "last frequency: %ld, preset: %ld, detect_raw: %d", - subghz->last_settings->frequency, - subghz->last_settings->preset, - subghz->last_settings->detect_raw); -#else FURI_LOG_D( TAG, "last frequency: %ld, preset: %ld", subghz->last_settings->frequency, subghz->last_settings->preset); -#endif #endif subghz_setting_set_default_frequency(subghz->setting, subghz->last_settings->frequency); } @@ -279,6 +259,7 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->txrx->hopper_state = SubGhzHopperStateOFF; subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + subghz->txrx->debug_pin_state = false; if(!alloc_for_tx_only) { subghz->txrx->history = subghz_history_alloc(); } @@ -292,16 +273,15 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->txrx->environment = subghz_environment_alloc(); subghz_environment_set_came_atomo_rainbow_table_file_name( subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + subghz->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); subghz_environment_set_protocol_registry( subghz->txrx->environment, (void*)&subghz_protocol_registry); subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - subghz_last_settings_set_detect_raw_values(subghz); -#else - subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); -#endif + subghz->txrx->filter = SubGhzProtocolFlag_Decodable; + subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); subghz_worker_set_overrun_callback( subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); @@ -325,6 +305,8 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) { subghz->rpc_ctx = NULL; } + subghz_speaker_off(subghz); + #if FURI_DEBUG // Packet Test view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index ed1648083..c047a32b3 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -42,8 +42,9 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); frequency = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_cc1101_g0, true); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, true); furi_hal_power_suppress_charge_enter(); @@ -252,6 +253,8 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); subghz_environment_set_came_atomo_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment, EXT_PATH("subghz/assets/alutech_at_4n")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/nice_flor_s")); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); @@ -264,7 +267,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); frequency = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_power_suppress_charge_enter(); @@ -304,6 +307,81 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { free(instance); } +void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { + UNUSED(context); + uint32_t frequency = 433920000; + + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); + if(ret != 1) { + printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); + return; + } + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", + frequency); + return; + } + } + + // Allocate context and buffers + SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); + instance->stream = + furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); + furi_check(instance->stream); + + // Configure radio + furi_hal_subghz_reset(); + furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok270Async); + frequency = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_power_suppress_charge_enter(); + + // Prepare and start RX + furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); + + // Wait for packets to arrive + printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); + LevelDuration level_duration; + size_t counter = 0; + while(!cli_cmd_interrupt_received(cli)) { + int ret = furi_stream_buffer_receive( + instance->stream, &level_duration, sizeof(LevelDuration), 10); + if(ret == 0) { + continue; + } + if(ret != sizeof(LevelDuration)) { + puts("stream corrupt"); + break; + } + if(level_duration_is_reset(level_duration)) { + puts(". "); + } else { + bool level = level_duration_get_level(level_duration); + uint32_t duration = level_duration_get_duration(level_duration); + printf("%c%lu ", level ? '+' : '-', duration); + } + furi_thread_stdout_flush(); + counter++; + if(counter > 255) { + puts("\r\n"); + counter = 0; + } + } + + // Shutdown radio + furi_hal_subghz_stop_async_rx(); + furi_hal_subghz_sleep(); + + furi_hal_power_suppress_charge_exit(); + + // Cleanup + furi_stream_buffer_free(instance->stream); + free(instance); +} void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* file_name; @@ -372,6 +450,8 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { } subghz_environment_set_came_atomo_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment, EXT_PATH("subghz/assets/alutech_at_4n")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/nice_flor_s")); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); @@ -387,7 +467,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { } printf( - "Listening at %s.\r\n\r\nPress CTRL+C to stop\r\n\r\n", + "Listening at \033[0;33m%s\033[0m.\r\n\r\nPress CTRL+C to stop\r\n\r\n", furi_string_get_cstr(file_name)); LevelDuration level_duration; @@ -426,7 +506,8 @@ static void subghz_cli_command_print_usage() { printf("\tchat \t - Chat with other Flippers\r\n"); printf( "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); - printf("\trx \t - Reception key\r\n"); + printf("\trx \t - Receive\r\n"); + printf("\trx_raw \t - Receive RAW\r\n"); printf("\tdecode_raw \t - Testing\r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { @@ -521,8 +602,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) { - UNUSED(context); +static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t frequency = 433920000; if(furi_string_size(args)) { @@ -578,7 +658,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - furi_string_printf(name, "%s: ", furi_hal_version_get_name_ptr()); + furi_string_printf(name, "\033[0;33m%s\033[0m: ", furi_hal_version_get_name_ptr()); furi_string_set(input, name); printf("%s", furi_string_get_cstr(input)); fflush(stdout); @@ -659,14 +739,18 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) { notification_message(notification, &sequence_single_vibro); break; case SubGhzChatEventUserEntrance: - furi_string_printf(sysmsg, "%s joined chat.\r\n", furi_hal_version_get_name_ptr()); + furi_string_printf( + sysmsg, + "\033[0;34m%s joined chat.\033[0m\r\n", + furi_hal_version_get_name_ptr()); subghz_chat_worker_write( subghz_chat, (uint8_t*)furi_string_get_cstr(sysmsg), strlen(furi_string_get_cstr(sysmsg))); break; case SubGhzChatEventUserExit: - furi_string_printf(sysmsg, "%s left chat.\r\n", furi_hal_version_get_name_ptr()); + furi_string_printf( + sysmsg, "\033[0;31m%s left chat.\033[0m\r\n", furi_hal_version_get_name_ptr()); subghz_chat_worker_write( subghz_chat, (uint8_t*)furi_string_get_cstr(sysmsg), @@ -711,7 +795,7 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "chat") == 0) { - subghz_cli_command_chat(cli, args, NULL); + subghz_cli_command_chat(cli, args); break; } @@ -725,6 +809,11 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "rx_raw") == 0) { + subghz_cli_command_rx_raw(cli, args, context); + break; + } + if(furi_string_cmp_str(cmd, "decode_raw") == 0) { subghz_cli_command_decode_raw(cli, args, context); break; @@ -763,11 +852,9 @@ void subghz_on_system_start() { Cli* cli = furi_record_open(RECORD_CLI); cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL); - // psst RM... i know you dont care much about errors, but if you ever see this... incompatible pointer type :3 - cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat, NULL); furi_record_close(RECORD_CLI); #else UNUSED(subghz_cli_command); #endif -} \ No newline at end of file +} diff --git a/applications/main/subghz/subghz_file_encoder_worker.c b/applications/main/subghz/subghz_file_encoder_worker.c new file mode 100644 index 000000000..a8c6519ef --- /dev/null +++ b/applications/main/subghz/subghz_file_encoder_worker.c @@ -0,0 +1,244 @@ +#include "subghz_file_encoder_worker.h" + +#include +#include +#include + +#define TAG "SubGhzFileEncoderWorker" + +#define SUBGHZ_FILE_ENCODER_LOAD 512 + +struct SubGhzFileEncoderWorker { + FuriThread* thread; + FuriStreamBuffer* stream; + + Storage* storage; + FlipperFormat* flipper_format; + + volatile bool worker_running; + volatile bool worker_stoping; + bool level; + bool is_storage_slow; + FuriString* str_data; + FuriString* file_path; + + SubGhzFileEncoderWorkerCallbackEnd callback_end; + void* context_end; +}; + +void subghz_file_encoder_worker_callback_end( + SubGhzFileEncoderWorker* instance, + SubGhzFileEncoderWorkerCallbackEnd callback_end, + void* context_end) { + furi_assert(instance); + furi_assert(callback_end); + instance->callback_end = callback_end; + instance->context_end = context_end; +} + +void subghz_file_encoder_worker_add_level_duration( + SubGhzFileEncoderWorker* instance, + int32_t duration) { + bool res = true; + if(duration < 0 && !instance->level) { + res = false; + } else if(duration > 0 && instance->level) { + res = false; + } + + if(res) { + instance->level = !instance->level; + furi_stream_buffer_send(instance->stream, &duration, sizeof(int32_t), 100); + } else { + FURI_LOG_E(TAG, "Invalid level in the stream"); + } +} + +bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) { + char* str1; + bool res = false; + // Line sample: "RAW_Data: -1, 2, -2..." + + // Look for a key in the line + str1 = strstr(strStart, "RAW_Data: "); + + if(str1 != NULL) { + // Skip key + str1 = strchr(str1, ' '); + + // Check that there is still an element in the line + while(strchr(str1, ' ') != NULL) { + str1 = strchr(str1, ' '); + + // Skip space + str1 += 1; + subghz_file_encoder_worker_add_level_duration(instance, atoi(str1)); + } + res = true; + } + return res; +} + +void subghz_file_encoder_worker_get_text_progress( + SubGhzFileEncoderWorker* instance, + FuriString* output) { + UNUSED(output); + Stream* stream = flipper_format_get_raw_stream(instance->flipper_format); + size_t total_size = stream_size(stream); + size_t current_offset = stream_tell(stream); + size_t buffer_avail = furi_stream_buffer_bytes_available(instance->stream); + + furi_string_printf(output, "%03u%%", 100 * (current_offset - buffer_avail) / total_size); +} + +LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { + furi_assert(context); + SubGhzFileEncoderWorker* instance = context; + int32_t duration; + int ret = furi_stream_buffer_receive(instance->stream, &duration, sizeof(int32_t), 0); + if(ret == sizeof(int32_t)) { + LevelDuration level_duration = {.level = LEVEL_DURATION_RESET}; + if(duration < 0) { + level_duration = level_duration_make(false, -duration); + } else if(duration > 0) { + level_duration = level_duration_make(true, duration); + } else if(duration == 0) { //-V547 + level_duration = level_duration_reset(); + FURI_LOG_I(TAG, "Stop transmission"); + instance->worker_stoping = true; + } + return level_duration; + } else { + instance->is_storage_slow = true; + return level_duration_wait(); + } +} + +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t subghz_file_encoder_worker_thread(void* context) { + SubGhzFileEncoderWorker* instance = context; + FURI_LOG_I(TAG, "Worker start"); + bool res = false; + instance->is_storage_slow = false; + Stream* stream = flipper_format_get_raw_stream(instance->flipper_format); + do { + if(!flipper_format_file_open_existing( + instance->flipper_format, furi_string_get_cstr(instance->file_path))) { + FURI_LOG_E( + TAG, + "Unable to open file for read: %s", + furi_string_get_cstr(instance->file_path)); + break; + } + if(!flipper_format_read_string(instance->flipper_format, "Protocol", instance->str_data)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + //skip the end of the previous line "\n" + stream_seek(stream, 1, StreamOffsetFromCurrent); + res = true; + instance->worker_stoping = false; + FURI_LOG_I(TAG, "Start transmission"); + } while(0); + + while(res && instance->worker_running) { + size_t stream_free_byte = furi_stream_buffer_spaces_available(instance->stream); + if((stream_free_byte / sizeof(int32_t)) >= SUBGHZ_FILE_ENCODER_LOAD) { + if(stream_read_line(stream, instance->str_data)) { + furi_string_trim(instance->str_data); + if(!subghz_file_encoder_worker_data_parse( + instance, furi_string_get_cstr(instance->str_data))) { + subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); + break; + } + } else { + subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); + break; + } + } else { + furi_delay_ms(1); + } + } + //waiting for the end of the transfer + if(instance->is_storage_slow) { + FURI_LOG_E(TAG, "Storage is slow"); + } + FURI_LOG_I(TAG, "End read file"); + while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) { + furi_delay_ms(5); + } + FURI_LOG_I(TAG, "End transmission"); + while(instance->worker_running) { + if(instance->worker_stoping) { + if(instance->callback_end) instance->callback_end(instance->context_end); + } + furi_delay_ms(50); + } + flipper_format_file_close(instance->flipper_format); + + FURI_LOG_I(TAG, "Worker stop"); + return 0; +} + +SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { + SubGhzFileEncoderWorker* instance = malloc(sizeof(SubGhzFileEncoderWorker)); + + instance->thread = + furi_thread_alloc_ex("SubGhzFEWorker", 2048, subghz_file_encoder_worker_thread, instance); + instance->stream = furi_stream_buffer_alloc(sizeof(int32_t) * 2048, sizeof(int32_t)); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->flipper_format = flipper_format_file_alloc(instance->storage); + + instance->str_data = furi_string_alloc(); + instance->file_path = furi_string_alloc(); + instance->level = false; + instance->worker_stoping = true; + + return instance; +} + +void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { + furi_assert(instance); + + furi_stream_buffer_free(instance->stream); + furi_thread_free(instance->thread); + + furi_string_free(instance->str_data); + furi_string_free(instance->file_path); + + flipper_format_free(instance->flipper_format); + furi_record_close(RECORD_STORAGE); + + free(instance); +} + +bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(!instance->worker_running); + + furi_stream_buffer_reset(instance->stream); + furi_string_set(instance->file_path, file_path); + instance->worker_running = true; + furi_thread_start(instance->thread); + + return true; +} + +void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + furi_thread_join(instance->thread); +} + +bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance) { + furi_assert(instance); + return instance->worker_running; +} diff --git a/applications/main/subghz/subghz_file_encoder_worker.h b/applications/main/subghz/subghz_file_encoder_worker.h new file mode 100644 index 000000000..19a46f1e6 --- /dev/null +++ b/applications/main/subghz/subghz_file_encoder_worker.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +typedef void (*SubGhzFileEncoderWorkerCallbackEnd)(void* context); + +typedef struct SubGhzFileEncoderWorker SubGhzFileEncoderWorker; + +/** + * End callback SubGhzWorker. + * @param instance SubGhzFileEncoderWorker instance + * @param callback SubGhzFileEncoderWorkerCallbackEnd callback + */ +void subghz_file_encoder_worker_callback_end( + SubGhzFileEncoderWorker* instance, + SubGhzFileEncoderWorkerCallbackEnd callback_end, + void* context_end); + +/** + * Allocate SubGhzFileEncoderWorker. + * @return SubGhzFileEncoderWorker* pointer to a SubGhzFileEncoderWorker instance + */ +SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc(); + +/** + * Free SubGhzFileEncoderWorker. + * @param instance Pointer to a SubGhzFileEncoderWorker instance + */ +void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance); + +/** + * Get a description of the progress. + * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @param output + */ +void subghz_file_encoder_worker_get_text_progress( + SubGhzFileEncoderWorker* instance, + FuriString* output); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzFileEncoderWorker instance + * @return LevelDuration + */ +LevelDuration subghz_file_encoder_worker_get_level_duration(void* context); + +/** + * Start SubGhzFileEncoderWorker. + * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @return bool - true if ok + */ +bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path); + +/** + * Stop SubGhzFileEncoderWorker + * @param instance Pointer to a SubGhzFileEncoderWorker instance + */ +void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance); + +/** + * Check if worker is running + * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @return bool - true if running + */ +bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance); diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index e8d3acfd7..184146698 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -1,33 +1,15 @@ #include "subghz_history.h" -#include "subghz_history_private.h" #include -#include -#include -#include "flipper_format_stream_i.h" -#include -#define SUBGHZ_HISTORY_MAX 60 - -/** - * @brief Settings for temporary files - * - */ -#define SUBGHZ_HISTORY_TMP_DIR EXT_PATH("subghz/tmp_history") -#define SUBGHZ_HISTORY_TMP_EXTENSION ".tmp" -#define SUBGHZ_HISTORY_TMP_SIGNAL_MAX 700 -#define SUBGHZ_HISTORY_TMP_SIGNAL_MIN 100 -#define SUBGHZ_HISTORY_TMP_REMOVE_FILES true -#define SUBGHZ_HISTORY_TMP_RAW_KEY "RAW_Data" -#define MAX_LINE 500 -const size_t buffer_size = 32; +#include +#define SUBGHZ_HISTORY_MAX 55 +#define SUBGHZ_HISTORY_FREE_HEAP 20480 #define TAG "SubGhzHistory" typedef struct { FuriString* item_str; FlipperFormat* flipper_string; - FuriString* protocol_name; - bool is_file; uint8_t type; SubGhzRadioPreset* preset; } SubGhzHistoryItem; @@ -45,146 +27,30 @@ struct SubGhzHistory { uint16_t last_index_write; uint8_t code_last_hash_data; FuriString* tmp_string; - bool write_tmp_files; - bool is_hopper_running; - Storage* storage; SubGhzHistoryStruct* history; }; -#ifdef FURI_DEBUG -#define LOG_DELAY 0 -#endif - -FuriString* subghz_history_generate_temp_filename(uint32_t index) { - FuriHalRtcDateTime datetime = {0}; - furi_hal_rtc_get_datetime(&datetime); - return furi_string_alloc_printf("%03ld%s", index, SUBGHZ_HISTORY_TMP_EXTENSION); -} - -bool subghz_history_is_tmp_dir_exists(SubGhzHistory* instance) { - FileInfo file_info; - FS_Error error = storage_common_stat(instance->storage, SUBGHZ_HISTORY_TMP_DIR, &file_info); - - if(error == FSE_OK) { - if(file_info.flags & FSF_DIRECTORY) { - return true; - } - } - - return false; -} - -bool subghz_history_check_sdcard(SubGhzHistory* instance) { -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "check_sdcard"); - uint32_t start_time = furi_get_tick(); -#endif - - bool result = false; - // Stage 0 - check SD Card - FS_Error status = storage_sd_status(instance->storage); - if(status == FSE_OK) { - result = subghz_history_is_tmp_dir_exists(instance); - if(!subghz_history_is_tmp_dir_exists(instance)) { - result = storage_simply_mkdir(instance->storage, SUBGHZ_HISTORY_TMP_DIR); - } - } else { - FURI_LOG_W(TAG, "SD storage not installed! Status: %d", status); - } -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Running time (check_sdcard): %ld ms", furi_get_tick() - start_time); -#endif - - return result; -} - -void subghz_history_clear_tmp_dir(SubGhzHistory* instance) { - furi_assert(instance); -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "clear_tmp_dir"); -#endif - - if(!instance->write_tmp_files) { - // Nothing to do here! - return; - } - //uint32_t start_time = furi_get_tick(); -#ifdef SUBGHZ_HISTORY_TMP_REMOVE_FILES - // Stage 0 - Dir exists? - bool res = subghz_history_is_tmp_dir_exists(instance); - if(res) { - // Stage 1 - delete all content if exists - FileInfo fileinfo; - storage_common_stat(instance->storage, SUBGHZ_HISTORY_TMP_DIR, &fileinfo); - - res = fileinfo.flags & FSF_DIRECTORY ? - storage_simply_remove_recursive(instance->storage, SUBGHZ_HISTORY_TMP_DIR) : - (storage_common_remove(instance->storage, SUBGHZ_HISTORY_TMP_DIR) == FSE_OK); - } - - // Stage 2 - create dir if necessary - res = storage_simply_mkdir(instance->storage, SUBGHZ_HISTORY_TMP_DIR); - if(!res) { - FURI_LOG_E(TAG, "Cannot process temp dir!"); - } -#endif - /* uint32_t stop_time = furi_get_tick() - start_time; - FURI_LOG_I(TAG, "Running time (clear_tmp_dir): %d ms", stop_time);*/ -} - SubGhzHistory* subghz_history_alloc(void) { SubGhzHistory* instance = malloc(sizeof(SubGhzHistory)); instance->tmp_string = furi_string_alloc(); instance->history = malloc(sizeof(SubGhzHistoryStruct)); SubGhzHistoryItemArray_init(instance->history->data); - instance->storage = furi_record_open(RECORD_STORAGE); - instance->write_tmp_files = subghz_history_check_sdcard(instance); - - instance->is_hopper_running = false; - - if(!instance->write_tmp_files) { - FURI_LOG_E(TAG, "Unstable work! Cannot use SD Card!"); - } - return instance; } -void subghz_history_item_free(void* current_item) { - furi_assert(current_item); - SubGhzHistoryItem* item = (SubGhzHistoryItem*)current_item; - furi_string_free(item->item_str); - furi_string_free(item->preset->name); - furi_string_free(item->protocol_name); - - free(item->preset); - item->type = 0; - item->is_file = false; - - if(item->flipper_string != NULL) { - flipper_format_free(item->flipper_string); - } -} - -void subghz_history_clean_item_array(SubGhzHistory* instance) { - for - M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { - subghz_history_item_free(item); - } -} - void subghz_history_free(SubGhzHistory* instance) { furi_assert(instance); furi_string_free(instance->tmp_string); - - subghz_history_clean_item_array(instance); + for + M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } SubGhzHistoryItemArray_clear(instance->history->data); free(instance->history); - - // Delete all temporary file, on exit it's ok - subghz_history_clear_tmp_dir(instance); - - furi_record_close(RECORD_STORAGE); - free(instance); } @@ -209,20 +75,19 @@ const char* subghz_history_get_preset(SubGhzHistory* instance, uint16_t idx) { void subghz_history_reset(SubGhzHistory* instance) { furi_assert(instance); furi_string_reset(instance->tmp_string); - - subghz_history_clean_item_array(instance); - + for + M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } SubGhzHistoryItemArray_reset(instance->history->data); instance->last_index_write = 0; instance->code_last_hash_data = 0; } -void subghz_history_set_hopper_state(SubGhzHistory* instance, bool hopper_state) { - furi_assert(instance); - - instance->is_hopper_running = hopper_state; -} - uint16_t subghz_history_get_item(SubGhzHistory* instance) { furi_assert(instance); return instance->last_index_write; @@ -237,8 +102,12 @@ uint8_t subghz_history_get_type_protocol(SubGhzHistory* instance, uint16_t idx) const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); - - return furi_string_get_cstr(item->protocol_name); + flipper_format_rewind(item->flipper_string); + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + furi_string_reset(instance->tmp_string); + } + return furi_string_get_cstr(instance->tmp_string); } FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx) { @@ -247,72 +116,27 @@ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx if(item->flipper_string) { return item->flipper_string; } else { - bool result_ok = false; - if(instance->write_tmp_files && item->is_file) { - // We have files! - FuriString* filename = subghz_history_generate_temp_filename(idx); - FuriString* dir_path; - - dir_path = furi_string_alloc_printf( - "%s/%s", SUBGHZ_HISTORY_TMP_DIR, furi_string_get_cstr(filename)); - - if(storage_file_exists(instance->storage, furi_string_get_cstr(dir_path))) { -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Exist: %s", furi_string_get_cstr(dir_path)); - furi_delay_ms(LOG_DELAY); -#endif - // Set to current anyway it has NULL value - item->flipper_string = flipper_format_string_alloc(); - Stream* dst_stream = flipper_format_get_raw_stream(item->flipper_string); - stream_clean(dst_stream); - - size_t size = stream_load_from_file( - dst_stream, instance->storage, furi_string_get_cstr(dir_path)); - if(size > 0) { -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Save ok!"); - furi_delay_ms(LOG_DELAY); -#endif - // We changed contents of file, so we no needed to load - // content from disk for the next time - item->is_file = false; - result_ok = true; - } else { - FURI_LOG_E(TAG, "Stream copy failed!"); - flipper_format_free(item->flipper_string); - } - } else { - FURI_LOG_E(TAG, "Can't convert filename to file"); - } - - furi_string_free(filename); - furi_string_free(dir_path); - } else { -#ifdef FURI_DEBUG - FURI_LOG_W(TAG, "Write TMP files failed!"); - furi_delay_ms(LOG_DELAY); -#endif - } - return result_ok ? item->flipper_string : NULL; + return NULL; } } - bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) { furi_assert(instance); - if(instance->last_index_write == SUBGHZ_HISTORY_MAX) { - if(output != NULL) furi_string_printf(output, "Memory is FULL"); + if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) { + if(output != NULL) furi_string_printf(output, " Free heap LOW"); return true; } - if(output != NULL) { - furi_string_printf(output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX); + if(instance->last_index_write == SUBGHZ_HISTORY_MAX) { + if(output != NULL) furi_string_printf(output, " Memory is FULL"); + return true; } + if(output != NULL) + furi_string_printf(output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX); return false; } uint16_t subghz_history_get_last_index(SubGhzHistory* instance) { return instance->last_index_write; } - void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* output, uint16_t idx) { SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); furi_string_set(output, item->item_str); @@ -325,9 +149,8 @@ bool subghz_history_add_to_history( furi_assert(instance); furi_assert(context); - if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) { - return false; - } + if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) return false; + if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; SubGhzProtocolDecoderBase* decoder_base = context; if((instance->code_last_hash_data == @@ -339,6 +162,7 @@ bool subghz_history_add_to_history( instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); instance->last_update_timestamp = furi_get_tick(); + FuriString* text; text = furi_string_alloc(); SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data); @@ -351,11 +175,6 @@ bool subghz_history_add_to_history( item->preset->data_size = preset->data_size; item->item_str = furi_string_alloc(); - item->protocol_name = furi_string_alloc(); - - bool tmp_file_for_raw = false; - - // At this point file mapped to memory otherwise file cannot decode item->flipper_string = flipper_format_string_alloc(); subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); @@ -367,30 +186,8 @@ bool subghz_history_add_to_history( if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { FURI_LOG_E(TAG, "Missing Protocol"); break; - } else { - furi_string_printf( - item->protocol_name, "%s", furi_string_get_cstr(instance->tmp_string)); } - if(!strcmp(furi_string_get_cstr(instance->tmp_string), "RAW")) { - // Check if hopper enabled we need to add little delay - if(instance->is_hopper_running) { - furi_delay_ms(40); - } - // Enable writing temp files to micro sd - tmp_file_for_raw = true; - // Write display name - furi_string_printf( - item->item_str, - "RAW %03ld.%02ld", - preset->frequency / 1000000 % 1000, - preset->frequency / 10000 % 100); - // Rewind - if(!flipper_format_rewind(item->flipper_string)) { - FURI_LOG_E(TAG, "Rewind error"); - } - - break; - } else if(!strcmp(furi_string_get_cstr(instance->tmp_string), "KeeLoq")) { + if(!strcmp(furi_string_get_cstr(instance->tmp_string), "KeeLoq")) { furi_string_set(instance->tmp_string, "KL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); @@ -411,484 +208,34 @@ bool subghz_history_add_to_history( } uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(item->flipper_string, "Key", key_data, sizeof(uint64_t))) { - FURI_LOG_E(TAG, "Missing Key"); - break; + FURI_LOG_D(TAG, "No Key"); } uint64_t data = 0; for(uint8_t i = 0; i < sizeof(uint64_t); i++) { data = (data << 8) | key_data[i]; } - if(!(uint32_t)(data >> 32)) { - furi_string_printf( - item->item_str, - "%s %lX", - furi_string_get_cstr(instance->tmp_string), - (uint32_t)(data & 0xFFFFFFFF)); + if(data != 0) { + if(!(uint32_t)(data >> 32)) { + furi_string_printf( + item->item_str, + "%s %lX", + furi_string_get_cstr(instance->tmp_string), + (uint32_t)(data & 0xFFFFFFFF)); + } else { + furi_string_printf( + item->item_str, + "%s %lX%08lX", + furi_string_get_cstr(instance->tmp_string), + (uint32_t)(data >> 32), + (uint32_t)(data & 0xFFFFFFFF)); + } } else { - furi_string_printf( - item->item_str, - "%s %lX%08lX", - furi_string_get_cstr(instance->tmp_string), - (uint32_t)(data >> 32), - (uint32_t)(data & 0xFFFFFFFF)); + furi_string_printf(item->item_str, "%s", furi_string_get_cstr(instance->tmp_string)); } + } while(false); - // If we can write to files - if(instance->write_tmp_files && tmp_file_for_raw) { - FuriString* filename = subghz_history_generate_temp_filename(instance->last_index_write); - FuriString* dir_path; - dir_path = furi_string_alloc(); - - furi_string_cat_printf( - dir_path, "%s/%s", SUBGHZ_HISTORY_TMP_DIR, furi_string_get_cstr(filename)); -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Save temp file: %s", furi_string_get_cstr(dir_path)); -#endif - if(!subghz_history_tmp_write_file_split(instance, item, furi_string_get_cstr(dir_path))) { - // Plan B! - subghz_history_tmp_write_file_full(instance, item, dir_path); - } - if(item->is_file) { - flipper_format_free(item->flipper_string); - item->flipper_string = NULL; - } - furi_string_free(filename); - furi_string_free(dir_path); - - } else { -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Old fashion way"); -#endif - } - furi_string_free(text); - instance->last_index_write++; return true; } - -static inline bool is_space_playground(char c) { - return c == ' ' || c == '\t' || c == flipper_format_eolr; -} - -bool subghz_history_stream_read_valid_key(Stream* stream, FuriString* key) { - furi_string_reset(key); - uint8_t buffer[buffer_size]; - - bool found = false; - bool error = false; - bool accumulate = true; - bool new_line = true; - - while(true) { - size_t was_read = stream_read(stream, buffer, buffer_size); - if(was_read == 0) break; - - for(size_t i = 0; i < was_read; i++) { - uint8_t data = buffer[i]; - if(data == flipper_format_eoln) { - // EOL found, clean data, start accumulating data and set the new_line flag - furi_string_reset(key); - accumulate = true; - new_line = true; - } else if(data == flipper_format_eolr) { - // ignore - } else if(data == flipper_format_comment && new_line) { - // if there is a comment character and we are at the beginning of a new line - // do not accumulate comment data and reset the new_line flag - accumulate = false; - new_line = false; - } else if(data == flipper_format_delimiter) { - if(new_line) { - // we are on a "new line" and found the delimiter - // this can only be if we have previously found some kind of key, so - // clear the data, set the flag that we no longer want to accumulate data - // and reset the new_line flag - furi_string_reset(key); - accumulate = false; - new_line = false; - } else { - // parse the delimiter only if we are accumulating data - if(accumulate) { - // we found the delimiter, move the rw pointer to the delimiter location - // and signal that we have found something - if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { - error = true; - break; - } - - found = true; - break; - } - } - } else { - // just new symbol, reset the new_line flag - new_line = false; - if(accumulate) { - // and accumulate data if we want - furi_string_push_back(key, data); - } - } - } - - if(found || error) break; - } - - return found; -} - -bool subghz_history_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) { - bool found = false; - FuriString* read_key; - - read_key = furi_string_alloc(); - - while(!stream_eof(stream)) { - if(subghz_history_stream_read_valid_key(stream, read_key)) { - if(furi_string_cmp_str(read_key, key) == 0) { - if(!stream_seek(stream, 2, StreamOffsetFromCurrent)) { - break; - } - found = true; - break; - } else if(strict_mode) { - found = false; - break; - } - } - } - furi_string_free(read_key); - - return found; -} - -bool subghz_history_stream_read_value(Stream* stream, FuriString* value, bool* last) { - enum { LeadingSpace, ReadValue, TrailingSpace } state = LeadingSpace; - const size_t buffer_size = 32; - uint8_t buffer[buffer_size]; - bool result = false; - bool error = false; - - furi_string_reset(value); - - while(true) { - size_t was_read = stream_read(stream, buffer, buffer_size); - - if(was_read == 0) { - if(state != LeadingSpace && stream_eof(stream)) { - result = true; - *last = true; - } else { - error = true; - } - } - - for(uint16_t i = 0; i < was_read; i++) { - const uint8_t data = buffer[i]; - - if(state == LeadingSpace) { - if(is_space_playground(data)) { - continue; - } else if(data == flipper_format_eoln) { - stream_seek(stream, i - was_read, StreamOffsetFromCurrent); - error = true; - break; - } else { - state = ReadValue; - furi_string_push_back(value, data); - } - } else if(state == ReadValue) { - if(is_space_playground(data)) { - state = TrailingSpace; - } else if(data == flipper_format_eoln) { - if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { - error = true; - } else { - result = true; - *last = true; - } - break; - } else { - furi_string_push_back(value, data); - } - } else if(state == TrailingSpace) { - if(is_space_playground(data)) { - continue; - } else if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { - error = true; - } else { - *last = (data == flipper_format_eoln); - result = true; - } - break; - } - } - - if(error || result) break; - } - - return result; -} - -bool subghz_history_read_int32(Stream* stream, int32_t* _data, const uint16_t data_size) { - bool result = false; - result = true; - FuriString* value; - value = furi_string_alloc(); - - for(size_t i = 0; i < data_size; i++) { - bool last = false; - result = subghz_history_stream_read_value(stream, value, &last); - if(result) { - int scan_values = 0; - - int32_t* data = _data; - scan_values = sscanf(furi_string_get_cstr(value), "%" PRIi32, &data[i]); - - if(scan_values != 1) { - result = false; - break; - } - } else { - break; - } - - if(last && ((i + 1) != data_size)) { - result = false; - break; - } - } - - furi_string_free(value); - return result; -} - -uint32_t subghz_history_rand_range(uint32_t min, uint32_t max) { - // size of range, inclusive - const uint32_t length_of_range = max - min + 1; - - // add n so that we don't return a number below our range - return (uint32_t)(rand() % length_of_range + min); -} - -bool subghz_history_write_file_noise( - Stream* file, - bool is_negative_start, - size_t current_position, - bool empty_line) { - size_t was_write = 0; - if(empty_line) { - was_write = stream_write_format(file, "%s: ", SUBGHZ_HISTORY_TMP_RAW_KEY); - - if(was_write <= 0) { - FURI_LOG_E(TAG, "Can't write key!"); - return false; - } - } - - int8_t first; - int8_t second; - if(is_negative_start) { - first = -1; - second = 1; - } else { - first = 1; - second = -1; - } - while(current_position < MAX_LINE) { - was_write = stream_write_format( - file, - "%ld %ld ", - subghz_history_rand_range( - SUBGHZ_HISTORY_TMP_SIGNAL_MIN, SUBGHZ_HISTORY_TMP_SIGNAL_MAX) * - first, - subghz_history_rand_range( - SUBGHZ_HISTORY_TMP_SIGNAL_MIN, SUBGHZ_HISTORY_TMP_SIGNAL_MAX) * - second); - - if(was_write <= 0) { - FURI_LOG_E(TAG, "Can't write random values!"); - return false; - } - - current_position += was_write; - } - - // Step back to write \n instead of space - size_t offset = stream_tell(file); - if(stream_seek(file, offset - 1, StreamOffsetFromCurrent)) { - FURI_LOG_E(TAG, "Step back failed!"); - return false; - } - - return stream_write_char(file, flipper_format_eoln) > 0; -} - -bool subghz_history_write_file_data( - Stream* src, - Stream* file, - bool* is_negative_start, - size_t* current_position) { - size_t offset_file = 0; - bool result = false; - int32_t value = 0; - - do { - if(!subghz_history_read_int32(src, &value, 1)) { - result = true; - break; - } - offset_file = stream_tell(file); - stream_write_format(file, "%ld ", value); - *current_position += stream_tell(file) - offset_file; - - if(*current_position > MAX_LINE) { - if((is_negative_start && value > 0) || (!is_negative_start && value < 0)) { - // Align values - continue; - } - - if(stream_write_format(file, "\n%s: ", SUBGHZ_HISTORY_TMP_RAW_KEY) == 0) { - FURI_LOG_E(TAG, "Can't write new line!"); - result = false; - break; - } - *current_position = 0; - } - } while(true); - - *is_negative_start = value < 0; - - return result; -} - -bool subghz_history_tmp_write_file_split( - SubGhzHistory* instance, - void* current_item, - const char* dir_path) { - furi_assert(instance); - furi_assert(current_item); - furi_assert(dir_path); -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Save temp file splitted: %s", dir_path); -#endif - SubGhzHistoryItem* item = (SubGhzHistoryItem*)current_item; - - uint8_t buffer[buffer_size]; - Stream* src = flipper_format_get_raw_stream(item->flipper_string); - stream_rewind(src); - - FlipperFormat* flipper_format_file = flipper_format_file_alloc(instance->storage); - bool result = false; - FuriString* temp_str = furi_string_alloc(); - - do { - if(storage_file_exists(instance->storage, dir_path) && - storage_common_remove(instance->storage, dir_path) != FSE_OK) { - FURI_LOG_E(TAG, "Can't delete old file!"); - break; - } - path_extract_dirname(dir_path, temp_str); - FS_Error fs_result = - storage_common_mkdir(instance->storage, furi_string_get_cstr(temp_str)); - if(fs_result != FSE_OK && fs_result != FSE_EXIST) { - FURI_LOG_E(TAG, "Can't create dir!"); - break; - } - result = flipper_format_file_open_always(flipper_format_file, dir_path); - if(!result) { - FURI_LOG_E(TAG, "Can't open file for write!"); - break; - } - Stream* file = flipper_format_get_raw_stream(flipper_format_file); - - if(!subghz_history_stream_seek_to_key(src, SUBGHZ_HISTORY_TMP_RAW_KEY, false)) { - FURI_LOG_E(TAG, "Can't find key!"); - break; - } - bool is_negative_start = false; - bool found = false; - - size_t offset_start; - offset_start = stream_tell(src); - - // Check for negative value at the start and end to align file by correct values - size_t was_read = stream_read(src, buffer, 1); - if(was_read <= 0) { - FURI_LOG_E(TAG, "Can't obtain first mark!"); - break; - } - - is_negative_start = buffer[0] == '-'; - - // Ready to write stream to file - size_t current_position; - stream_rewind(src); - current_position = stream_copy(src, file, offset_start); - if(current_position != offset_start) { - FURI_LOG_E(TAG, "Invalid copy header data from one stream to another!"); - break; - } - - found = true; - - current_position = 0; - if(!subghz_history_write_file_noise(file, is_negative_start, current_position, false)) { - FURI_LOG_E(TAG, "Add start noise failed!"); - break; - } - - if(stream_write_format(file, "%s: ", SUBGHZ_HISTORY_TMP_RAW_KEY) == 0) { - FURI_LOG_E(TAG, "Can't write new line!"); - result = false; - break; - } - - if(!subghz_history_write_file_data(src, file, &is_negative_start, ¤t_position)) { - FURI_LOG_E(TAG, "Split by lines failed!"); - break; - } - - if(!subghz_history_write_file_noise(file, is_negative_start, current_position, false)) { - FURI_LOG_E(TAG, "Add end noise failed!"); - break; - } - - if(!subghz_history_write_file_noise(file, is_negative_start, 0, true)) { - FURI_LOG_E(TAG, "Add end noise failed!"); - break; - } - - result = found; - } while(false); - flipper_format_file_close(flipper_format_file); - flipper_format_free(flipper_format_file); - furi_string_free(temp_str); - - item->is_file = result; - - return result; -} - -void subghz_history_tmp_write_file_full( - SubGhzHistory* instance, - void* current_item, - FuriString* dir_path) { - SubGhzHistoryItem* item = (SubGhzHistoryItem*)current_item; -#ifdef FURI_DEBUG - FURI_LOG_W(TAG, "Save temp file full: %s", furi_string_get_cstr(dir_path)); -#endif - Stream* dst = flipper_format_get_raw_stream(item->flipper_string); - stream_rewind(dst); - if(stream_save_to_file( - dst, instance->storage, furi_string_get_cstr(dir_path), FSOM_CREATE_ALWAYS) > 0) { -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Save done!"); -#endif - // This item contains fake data to load from SD - item->is_file = true; - } else { - FURI_LOG_E(TAG, "Stream copy failed!"); - } -} \ No newline at end of file diff --git a/applications/main/subghz/subghz_history.h b/applications/main/subghz/subghz_history.h index ee1ee1a4d..607dbeae2 100644 --- a/applications/main/subghz/subghz_history.h +++ b/applications/main/subghz/subghz_history.h @@ -110,10 +110,3 @@ bool subghz_history_add_to_history( * @return SubGhzProtocolCommonLoad* */ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx); - -/** Set hopper state for internal usage in history - * - * @param instance - SubGhzHistory instance - * @param hopper_state - bool is hopper running? - */ -void subghz_history_set_hopper_state(SubGhzHistory* instance, bool hopper_state); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index bac25759a..1fbe662ed 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -30,12 +30,6 @@ void subghz_preset_init( subghz->txrx->preset->frequency = frequency; subghz->txrx->preset->data = preset_data; subghz->txrx->preset->data_size = preset_data_size; - - subghz->txrx->raw_bandwidth = - subghz_preset_custom_get_bandwidth(preset_data, preset_data_size); - subghz->txrx->raw_manchester_enabled = - subghz_preset_custom_get_machester_enable(preset_data, preset_data_size); - subghz->txrx->raw_datarate = subghz_preset_custom_get_datarate(preset_data, preset_data_size); } bool subghz_set_preset(SubGhz* subghz, const char* preset) { @@ -75,7 +69,7 @@ void subghz_begin(SubGhz* subghz, uint8_t* preset_data) { furi_hal_subghz_reset(); furi_hal_subghz_idle(); furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } @@ -90,7 +84,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { furi_hal_subghz_idle(); uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_flush_rx(); subghz_speaker_on(subghz); furi_hal_subghz_rx(); @@ -109,8 +103,9 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); furi_hal_subghz_idle(); furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_write(&gpio_cc1101_g0, false); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); subghz_speaker_on(subghz); bool ret = furi_hal_subghz_tx(); subghz->txrx->txrx_state = SubGhzTxRxStateTx; @@ -602,9 +597,15 @@ void subghz_hopper_update(SubGhz* subghz) { } void subghz_speaker_on(SubGhz* subghz) { + if(subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_ext_pa7); + } + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_acquire(30)) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + if(!subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } } else { subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; } @@ -612,9 +613,14 @@ void subghz_speaker_on(SubGhz* subghz) { } void subghz_speaker_off(SubGhz* subghz) { + if(subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } if(subghz->txrx->speaker_state != SubGhzSpeakerStateDisable) { if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(NULL); + if(!subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } furi_hal_speaker_release(); if(subghz->txrx->speaker_state == SubGhzSpeakerStateShutdown) subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; @@ -623,17 +629,27 @@ void subghz_speaker_off(SubGhz* subghz) { } void subghz_speaker_mute(SubGhz* subghz) { + if(subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(NULL); + if(!subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } } } } void subghz_speaker_unmute(SubGhz* subghz) { + if(subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_ext_pa7); + } if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + if(!subghz->txrx->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } } } } diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index a6c96cb69..393dd667d 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -3,7 +3,6 @@ #include "helpers/subghz_types.h" #include "helpers/subghz_error_type.h" #include -#include #include "subghz.h" #include "views/receiver.h" #include "views/transmitter.h" @@ -63,6 +62,7 @@ struct SubGhzTxRx { SubGhzEnvironment* environment; SubGhzReceiver* receiver; SubGhzTransmitter* transmitter; + SubGhzProtocolFlag filter; SubGhzProtocolDecoderBase* decoder_result; FlipperFormat* fff_data; SecureData* secure_data; @@ -77,15 +77,10 @@ struct SubGhzTxRx { uint8_t hopper_idx_frequency; SubGhzRxKeyState rx_key_state; + bool debug_pin_state; + float raw_threshold_rssi; uint8_t raw_threshold_rssi_low_count; - - // one of the 16 possible bandwidth values - uint8_t raw_bandwidth; - // datarate in bauds - float raw_datarate; - // flag if manchester encoding/decoding enabled - bool raw_manchester_enabled; }; typedef struct SubGhzTxRx SubGhzTxRx; @@ -114,13 +109,6 @@ struct SubGhz { SubGhzViewTransmitter* subghz_transmitter; VariableItemList* variable_item_list; - // Advanced config items - VariableItem* variable_item_bandwidth; // specific config list view item: bandwidth - VariableItem* variable_item_datarate; // specific config list view item: data rate - VariableItem* variable_item_manchester; // specific config list view item: manchester enc flag - // Advanced config strings - char datarate_input_str[16]; - SubGhzFrequencyAnalyzer* subghz_frequency_analyzer; SubGhzReadRAW* subghz_read_raw; bool raw_send_only; diff --git a/applications/main/subghz/subghz_keystore.c b/applications/main/subghz/subghz_keystore.c new file mode 100644 index 000000000..e0b1cf6ca --- /dev/null +++ b/applications/main/subghz/subghz_keystore.c @@ -0,0 +1,613 @@ +#include "subghz_keystore.h" + +#include +#include + +#include +#include +#include +#include +#include + +#define TAG "SubGhzKeystore" + +#define FILE_BUFFER_SIZE 64 + +#define SUBGHZ_KEYSTORE_FILE_TYPE "Flipper SubGhz Keystore File" +#define SUBGHZ_KEYSTORE_FILE_RAW_TYPE "Flipper SubGhz Keystore RAW File" +#define SUBGHZ_KEYSTORE_FILE_VERSION 0 + +#define SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT 1 +#define SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE 512 +#define SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE (SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE * 2) + +typedef enum { + SubGhzKeystoreEncryptionNone, + SubGhzKeystoreEncryptionAES256, +} SubGhzKeystoreEncryption; + +struct SubGhzKeystore { + SubGhzKeyArray_t data; +}; + +SubGhzKeystore* subghz_keystore_alloc() { + SubGhzKeystore* instance = malloc(sizeof(SubGhzKeystore)); + + SubGhzKeyArray_init(instance->data); + + return instance; +} + +void subghz_keystore_free(SubGhzKeystore* instance) { + furi_assert(instance); + + for + M_EACH(manufacture_code, instance->data, SubGhzKeyArray_t) { + furi_string_free(manufacture_code->name); + manufacture_code->key = 0; + } + SubGhzKeyArray_clear(instance->data); + + free(instance); +} + +static void subghz_keystore_add_key( + SubGhzKeystore* instance, + const char* name, + uint64_t key, + uint16_t type) { + SubGhzKey* manufacture_code = SubGhzKeyArray_push_raw(instance->data); + manufacture_code->name = furi_string_alloc_set(name); + manufacture_code->key = key; + manufacture_code->type = type; +} + +static bool subghz_keystore_process_line(SubGhzKeystore* instance, char* line) { + uint64_t key = 0; + uint16_t type = 0; + char skey[17] = {0}; + char name[65] = {0}; + int ret = sscanf(line, "%16s:%hu:%64s", skey, &type, name); + key = strtoull(skey, NULL, 16); + if(ret == 3) { + subghz_keystore_add_key(instance, name, key, type); + return true; + } else { + FURI_LOG_E(TAG, "Failed to load line: %s\r\n", line); + return false; + } +} + +static void subghz_keystore_mess_with_iv(uint8_t* iv) { + // Alignment check for `ldrd` instruction + furi_assert(((uint32_t)iv) % 4 == 0); + // Please do not share decrypted manufacture keys + // Sharing them will bring some discomfort to legal owners + // And potential legal action against you + // While you reading this code think about your own personal responsibility + asm volatile("nani%=: \n" + "ldrd r0, r2, [%0, #0x0] \n" + "lsl r1, r0, #8 \n" + "lsl r3, r2, #8 \n" + "orr r3, r3, r0, lsr #24\n" + "uadd8 r1, r1, r0 \n" + "uadd8 r3, r3, r2 \n" + "strd r1, r3, [%0, #0x0] \n" + "ldrd r1, r3, [%0, #0x8] \n" + "lsl r0, r1, #8 \n" + "orr r0, r0, r2, lsr #24\n" + "lsl r2, r3, #8 \n" + "orr r2, r2, r1, lsr #24\n" + "uadd8 r1, r1, r0 \n" + "uadd8 r3, r3, r2 \n" + "strd r1, r3, [%0, #0x8] \n" + : + : "r"(iv) + : "r0", "r1", "r2", "r3", "memory"); +} + +static bool subghz_keystore_read_file(SubGhzKeystore* instance, Stream* stream, uint8_t* iv) { + bool result = true; + uint8_t buffer[FILE_BUFFER_SIZE]; + + char* decrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + char* encrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + size_t encrypted_line_cursor = 0; + + do { + if(iv) { + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load decryption key"); + break; + } + } + + size_t ret = 0; + do { + ret = stream_read(stream, buffer, FILE_BUFFER_SIZE); + for(uint16_t i = 0; i < ret; i++) { + if(buffer[i] == '\n' && encrypted_line_cursor > 0) { + // Process line + if(iv) { + // Data alignment check, 32 instead of 16 because of hex encoding + size_t len = strlen(encrypted_line); + if(len % 32 == 0) { + // Inplace hex to bin conversion + for(size_t i = 0; i < len; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(encrypted_line[i], &hi_nibble); + hex_char_to_hex_nibble(encrypted_line[i + 1], &lo_nibble); + encrypted_line[i / 2] = (hi_nibble << 4) | lo_nibble; + } + len /= 2; + + if(furi_hal_crypto_decrypt( + (uint8_t*)encrypted_line, (uint8_t*)decrypted_line, len)) { + subghz_keystore_process_line(instance, decrypted_line); + } else { + FURI_LOG_E(TAG, "Decryption failed"); + result = false; + break; + } + } else { + FURI_LOG_E(TAG, "Invalid encrypted data: %s", encrypted_line); + } + } else { + subghz_keystore_process_line(instance, encrypted_line); + } + // reset line buffer + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + encrypted_line_cursor = 0; + } else if(buffer[i] == '\r' || buffer[i] == '\n') { + // do not add line endings to the buffer + } else { + if(encrypted_line_cursor < SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE) { + encrypted_line[encrypted_line_cursor] = buffer[i]; + encrypted_line_cursor++; + } else { + FURI_LOG_E(TAG, "Malformed file"); + result = false; + break; + } + } + } + } while(ret > 0 && result); + + if(iv) furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + } while(false); + + free(encrypted_line); + free(decrypted_line); + + return result; +} + +bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { + furi_assert(instance); + bool result = false; + uint8_t iv[16]; + uint32_t version; + uint32_t encryption; + + FuriString* filetype; + filetype = furi_string_alloc(); + + FURI_LOG_I(TAG, "Loading keystore %s", file_name); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + do { + if(!flipper_format_file_open_existing(flipper_format, file_name)) { + FURI_LOG_E(TAG, "Unable to open file for read: %s", file_name); + break; + } + if(!flipper_format_read_header(flipper_format, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Encryption", (uint32_t*)&encryption, 1)) { + FURI_LOG_E(TAG, "Missing encryption type"); + break; + } + + if(strcmp(furi_string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_TYPE) != 0 || + version != SUBGHZ_KEYSTORE_FILE_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + Stream* stream = flipper_format_get_raw_stream(flipper_format); + if(encryption == SubGhzKeystoreEncryptionNone) { + result = subghz_keystore_read_file(instance, stream, NULL); + } else if(encryption == SubGhzKeystoreEncryptionAES256) { + if(!flipper_format_read_hex(flipper_format, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + subghz_keystore_mess_with_iv(iv); + result = subghz_keystore_read_file(instance, stream, iv); + } else { + FURI_LOG_E(TAG, "Unknown encryption"); + break; + } + } while(0); + flipper_format_free(flipper_format); + + furi_record_close(RECORD_STORAGE); + + furi_string_free(filetype); + + return result; +} + +bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8_t* iv) { + furi_assert(instance); + bool result = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + char* decrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + char* encrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + do { + if(!flipper_format_file_open_always(flipper_format, file_name)) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", file_name); + break; + } + if(!flipper_format_write_header_cstr( + flipper_format, SUBGHZ_KEYSTORE_FILE_TYPE, SUBGHZ_KEYSTORE_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + uint32_t encryption = SubGhzKeystoreEncryptionAES256; + if(!flipper_format_write_uint32(flipper_format, "Encryption", &encryption, 1)) { + FURI_LOG_E(TAG, "Unable to add Encryption"); + break; + } + if(!flipper_format_write_hex(flipper_format, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Unable to add IV"); + break; + } + + subghz_keystore_mess_with_iv(iv); + + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + + Stream* stream = flipper_format_get_raw_stream(flipper_format); + size_t encrypted_line_count = 0; + for + M_EACH(key, instance->data, SubGhzKeyArray_t) { + // Wipe buffer before packing + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + // Form unecreypted line + int len = snprintf( + decrypted_line, + SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE, + "%08lX%08lX:%hu:%s", + (uint32_t)(key->key >> 32), + (uint32_t)key->key, + key->type, + furi_string_get_cstr(key->name)); + // Verify length and align + furi_assert(len > 0); + if(len % 16 != 0) { + len += (16 - len % 16); + } + furi_assert(len % 16 == 0); + furi_assert(len <= SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + // Form encrypted line + if(!furi_hal_crypto_encrypt( + (uint8_t*)decrypted_line, (uint8_t*)encrypted_line, len)) { + FURI_LOG_E(TAG, "Encryption failed"); + break; + } + // HEX Encode encrypted line + const char xx[] = "0123456789ABCDEF"; + for(int i = 0; i < len; i++) { + size_t cursor = len - i - 1; + size_t hex_cursor = len * 2 - i * 2 - 1; + encrypted_line[hex_cursor] = xx[encrypted_line[cursor] & 0xF]; + encrypted_line[hex_cursor - 1] = xx[(encrypted_line[cursor] >> 4) & 0xF]; + } + stream_write_cstring(stream, encrypted_line); + stream_write_char(stream, '\n'); + encrypted_line_count++; + } + furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + size_t total_keys = SubGhzKeyArray_size(instance->data); + result = encrypted_line_count == total_keys; + if(result) { + FURI_LOG_I(TAG, "Success. Encrypted: %zu of %zu", encrypted_line_count, total_keys); + } else { + FURI_LOG_E(TAG, "Failure. Encrypted: %zu of %zu", encrypted_line_count, total_keys); + } + } while(0); + flipper_format_free(flipper_format); + + free(encrypted_line); + free(decrypted_line); + furi_record_close(RECORD_STORAGE); + + return result; +} + +SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance) { + furi_assert(instance); + return &instance->data; +} + +bool subghz_keystore_raw_encrypted_save( + const char* input_file_name, + const char* output_file_name, + uint8_t* iv) { + bool encrypted = false; + uint32_t version; + uint32_t encryption; + FuriString* filetype; + filetype = furi_string_alloc(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + char* encrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + + FlipperFormat* input_flipper_format = flipper_format_file_alloc(storage); + do { + if(!flipper_format_file_open_existing(input_flipper_format, input_file_name)) { + FURI_LOG_E(TAG, "Unable to open file for read: %s", input_file_name); + break; + } + if(!flipper_format_read_header(input_flipper_format, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(!flipper_format_read_uint32( + input_flipper_format, "Encryption", (uint32_t*)&encryption, 1)) { + FURI_LOG_E(TAG, "Missing encryption type"); + break; + } + + if(strcmp(furi_string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || + version != SUBGHZ_KEYSTORE_FILE_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + if(encryption != SubGhzKeystoreEncryptionNone) { + FURI_LOG_E(TAG, "Already encryption"); + break; + } + Stream* input_stream = flipper_format_get_raw_stream(input_flipper_format); + + FlipperFormat* output_flipper_format = flipper_format_file_alloc(storage); + + if(!flipper_format_file_open_always(output_flipper_format, output_file_name)) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", output_file_name); + break; + } + if(!flipper_format_write_header_cstr( + output_flipper_format, + furi_string_get_cstr(filetype), + SUBGHZ_KEYSTORE_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + uint32_t encryption = SubGhzKeystoreEncryptionAES256; + if(!flipper_format_write_uint32(output_flipper_format, "Encryption", &encryption, 1)) { + FURI_LOG_E(TAG, "Unable to add Encryption"); + break; + } + if(!flipper_format_write_hex(output_flipper_format, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Unable to add IV"); + break; + } + + if(!flipper_format_write_string_cstr(output_flipper_format, "Encrypt_data", "RAW")) { + FURI_LOG_E(TAG, "Unable to add Encrypt_data"); + break; + } + + subghz_keystore_mess_with_iv(iv); + + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + + Stream* output_stream = flipper_format_get_raw_stream(output_flipper_format); + uint8_t buffer[FILE_BUFFER_SIZE]; + bool result = true; + + size_t ret = 0; + furi_assert(FILE_BUFFER_SIZE % 16 == 0); + + //skip the end of the previous line "\n" + stream_read(input_stream, buffer, 1); + + do { + memset(buffer, 0, FILE_BUFFER_SIZE); + ret = stream_read(input_stream, buffer, FILE_BUFFER_SIZE); + if(ret == 0) { + break; + } + + for(uint16_t i = 0; i < FILE_BUFFER_SIZE - 1; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(buffer[i], &hi_nibble); + hex_char_to_hex_nibble(buffer[i + 1], &lo_nibble); + buffer[i / 2] = (hi_nibble << 4) | lo_nibble; + } + + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + // Form encrypted line + if(!furi_hal_crypto_encrypt( + (uint8_t*)buffer, (uint8_t*)encrypted_line, FILE_BUFFER_SIZE / 2)) { + FURI_LOG_E(TAG, "Encryption failed"); + result = false; + break; + } + + // HEX Encode encrypted line + const char xx[] = "0123456789ABCDEF"; + for(size_t i = 0; i < FILE_BUFFER_SIZE / 2; i++) { + size_t cursor = FILE_BUFFER_SIZE / 2 - i - 1; + size_t hex_cursor = FILE_BUFFER_SIZE - i * 2 - 1; + encrypted_line[hex_cursor] = xx[encrypted_line[cursor] & 0xF]; + encrypted_line[hex_cursor - 1] = xx[(encrypted_line[cursor] >> 4) & 0xF]; + } + stream_write_cstring(output_stream, encrypted_line); + + } while(true); + + flipper_format_free(output_flipper_format); + + furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + + if(!result) break; + + encrypted = true; + } while(0); + + flipper_format_free(input_flipper_format); + + free(encrypted_line); + + furi_record_close(RECORD_STORAGE); + + return encrypted; +} + +bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* data, size_t len) { + bool result = false; + uint8_t iv[16]; + uint32_t version; + uint32_t encryption; + + FuriString* str_temp; + str_temp = furi_string_alloc(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + char* decrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + do { + if(!flipper_format_file_open_existing(flipper_format, file_name)) { + FURI_LOG_E(TAG, "Unable to open file for read: %s", file_name); + break; + } + if(!flipper_format_read_header(flipper_format, str_temp, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Encryption", (uint32_t*)&encryption, 1)) { + FURI_LOG_E(TAG, "Missing encryption type"); + break; + } + + if(strcmp(furi_string_get_cstr(str_temp), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || + version != SUBGHZ_KEYSTORE_FILE_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + Stream* stream = flipper_format_get_raw_stream(flipper_format); + if(encryption != SubGhzKeystoreEncryptionAES256) { + FURI_LOG_E(TAG, "Unknown encryption"); + break; + } + + if(offset < 16) { + if(!flipper_format_read_hex(flipper_format, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + subghz_keystore_mess_with_iv(iv); + } + + if(!flipper_format_read_string(flipper_format, "Encrypt_data", str_temp)) { + FURI_LOG_E(TAG, "Missing Encrypt_data"); + break; + } + + size_t bufer_size; + if(len <= (16 - offset % 16)) { + bufer_size = 32; + } else { + bufer_size = (((len) / 16) + 2) * 32; + } + furi_assert(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE >= bufer_size / 2); + + uint8_t buffer[bufer_size]; + size_t ret = 0; + bool decrypted = true; + //skip the end of the previous line "\n" + stream_read(stream, buffer, 1); + + size_t size = stream_size(stream); + size -= stream_tell(stream); + if(size < (offset * 2 + len * 2)) { + FURI_LOG_E(TAG, "Seek position exceeds file size"); + break; + } + + if(offset >= 16) { + stream_seek(stream, ((offset / 16) - 1) * 32, StreamOffsetFromCurrent); + ret = stream_read(stream, buffer, 32); + furi_assert(ret == 32); + for(uint16_t i = 0; i < ret - 1; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(buffer[i], &hi_nibble); + hex_char_to_hex_nibble(buffer[i + 1], &lo_nibble); + iv[i / 2] = (hi_nibble << 4) | lo_nibble; + } + } + + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + + do { + memset(buffer, 0, bufer_size); + ret = stream_read(stream, buffer, bufer_size); + furi_assert(ret == bufer_size); + for(uint16_t i = 0; i < ret - 1; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(buffer[i], &hi_nibble); + hex_char_to_hex_nibble(buffer[i + 1], &lo_nibble); + buffer[i / 2] = (hi_nibble << 4) | lo_nibble; + } + + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + + if(!furi_hal_crypto_decrypt( + (uint8_t*)buffer, (uint8_t*)decrypted_line, bufer_size / 2)) { + decrypted = false; + FURI_LOG_E(TAG, "Decryption failed"); + break; + } + memcpy(data, (uint8_t*)decrypted_line + (offset - (offset / 16) * 16), len); + + } while(0); + furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + if(decrypted) result = true; + } while(0); + flipper_format_free(flipper_format); + + furi_record_close(RECORD_STORAGE); + + free(decrypted_line); + + furi_string_free(str_temp); + + return result; +} diff --git a/applications/main/subghz/subghz_keystore.h b/applications/main/subghz/subghz_keystore.h new file mode 100644 index 000000000..06ae8adae --- /dev/null +++ b/applications/main/subghz/subghz_keystore.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + FuriString* name; + uint64_t key; + uint16_t type; +} SubGhzKey; + +ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST) + +#define M_OPL_SubGhzKeyArray_t() ARRAY_OPLIST(SubGhzKeyArray, M_POD_OPLIST) + +typedef struct SubGhzKeystore SubGhzKeystore; + +/** + * Allocate SubGhzKeystore. + * @return SubGhzKeystore* pointer to a SubGhzKeystore instance + */ +SubGhzKeystore* subghz_keystore_alloc(); + +/** + * Free SubGhzKeystore. + * @param instance Pointer to a SubGhzKeystore instance + */ +void subghz_keystore_free(SubGhzKeystore* instance); + +/** + * Loading manufacture key from file + * @param instance Pointer to a SubGhzKeystore instance + * @param filename Full path to the file + */ +bool subghz_keystore_load(SubGhzKeystore* instance, const char* filename); + +/** + * Save manufacture key to file + * @param instance Pointer to a SubGhzKeystore instance + * @param filename Full path to the file + * @return true On success + */ +bool subghz_keystore_save(SubGhzKeystore* instance, const char* filename, uint8_t* iv); + +/** + * Get array of keys and names manufacture + * @param instance Pointer to a SubGhzKeystore instance + * @return SubGhzKeyArray_t* + */ +SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance); + +/** + * Save RAW encrypted to file + * @param input_file_name Full path to the input file + * @param output_file_name Full path to the output file + * @param iv IV, 16 bytes in hex + */ +bool subghz_keystore_raw_encrypted_save( + const char* input_file_name, + const char* output_file_name, + uint8_t* iv); + +/** + * Get decrypt RAW data to file + * @param file_name Full path to the input file + * @param offset Offset from the start of the RAW data + * @param data Returned array + * @param len Required data length + * @return true On success + */ +bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* data, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 478e32347..8e7016df7 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -1,8 +1,5 @@ #include "subghz_last_settings.h" #include "subghz_i.h" -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING -#include -#endif #define TAG "SubGhzLastSettings" @@ -16,11 +13,6 @@ #define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL 2 #define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER -93.0f -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING -#define SUBGHZ_LAST_SETTING_DEFAULT_READ_RAW 0 -#define SUBGHZ_LAST_SETTING_FIELD_DETECT_RAW "DetectRaw" -#endif - #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY "Frequency" //#define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel" @@ -52,9 +44,6 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count //int32_t temp_preset = 0; bool frequency_analyzer_feedback_level_was_read = false; bool frequency_analyzer_trigger_was_read = false; -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - uint32_t temp_read_raw = 0; -#endif if(FSE_OK == storage_sd_status(storage) && SUBGHZ_LAST_SETTINGS_PATH && flipper_format_file_open_existing(fff_data_file, SUBGHZ_LAST_SETTINGS_PATH)) { @@ -73,10 +62,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, (float*)&temp_frequency_analyzer_trigger, 1); -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_DETECT_RAW, (uint32_t*)&temp_read_raw, 1); -#endif + } else { FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH); } @@ -88,9 +74,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->frequency_analyzer_feedback_level = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - instance->detect_raw = SUBGHZ_LAST_SETTING_DEFAULT_READ_RAW; -#endif + } else { instance->frequency = temp_frequency; instance->frequency_analyzer_feedback_level = @@ -101,9 +85,6 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->frequency_analyzer_trigger = frequency_analyzer_trigger_was_read ? temp_frequency_analyzer_trigger : SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - instance->detect_raw = temp_read_raw; -#endif /*if(temp_preset > (int32_t)preset_count - 1 || temp_preset < 0) { FURI_LOG_W(TAG, "Last used preset no found");*/ @@ -164,12 +145,6 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { 1)) { break; } -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_DETECT_RAW, &instance->detect_raw, 1)) { - break; - } -#endif saved = true; } while(0); @@ -183,17 +158,3 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { return saved; } - -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING -void subghz_last_settings_set_detect_raw_values(void* context) { - furi_assert(context); - SubGhz* instance = (SubGhz*)context; - bool is_detect_raw = instance->last_settings->detect_raw > 0; - subghz_receiver_set_filter( - instance->txrx->receiver, is_detect_raw ? DETECT_RAW_TRUE : DETECT_RAW_FALSE); - subghz_protocol_decoder_raw_set_auto_mode( - subghz_receiver_search_decoder_base_by_name( - instance->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME), - is_detect_raw); -} -#endif \ No newline at end of file diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index ef3674d69..f08d99c81 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -1,23 +1,12 @@ #pragma once -// Enable saving detect raw setting state -// #define SUBGHZ_SAVE_DETECT_RAW_SETTING 1 - #include #include #include #include -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING -#include -#define DETECT_RAW_FALSE SubGhzProtocolFlag_Decodable -#define DETECT_RAW_TRUE SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_RAW -#endif typedef struct { uint32_t frequency; -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING - uint32_t detect_raw; -#endif int32_t preset; uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; @@ -30,6 +19,3 @@ void subghz_last_settings_free(SubGhzLastSettings* instance); void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count); bool subghz_last_settings_save(SubGhzLastSettings* instance); -#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING -void subghz_last_settings_set_detect_raw_values(void* context); -#endif \ No newline at end of file diff --git a/applications/main/subghz/subghz_setting.c b/applications/main/subghz/subghz_setting.c new file mode 100644 index 000000000..35ba54a8a --- /dev/null +++ b/applications/main/subghz/subghz_setting.c @@ -0,0 +1,480 @@ +#include "subghz_setting.h" +#include "types.h" +//#include "subghz_i.h" + +#include +#include +#include + +#define TAG "SubGhzSetting" + +#define SUBGHZ_SETTING_FILE_TYPE "Flipper SubGhz Setting File" +#define SUBGHZ_SETTING_FILE_VERSION 1 + +#define FREQUENCY_FLAG_DEFAULT (1 << 31) +#define FREQUENCY_MASK (0xFFFFFFFF ^ FREQUENCY_FLAG_DEFAULT) + +/* Default */ +static const uint32_t subghz_frequency_list[] = { + /* 300 - 348 */ + 300000000, + 302757000, + 303875000, + 304250000, + 307000000, + 307500000, + 307800000, + 309000000, + 310000000, + 312000000, + 312100000, + 312200000, + 313000000, + 313850000, + 314000000, + 314350000, + 314980000, + 315000000, + 318000000, + 330000000, + 345000000, + 348000000, + 350000000, + + /* 387 - 464 */ + 387000000, + 390000000, + 418000000, + 433075000, /* LPD433 first */ + 433220000, + 433420000, + 433657070, + 433889000, + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ + 434075000, + 434176948, + 434190000, + 434390000, + 434420000, + 434620000, + 434775000, /* LPD433 last channels */ + 438900000, + 440175000, + 464000000, + + /* 779 - 928 */ + 779000000, + 868350000, + 868400000, + 868800000, + 868950000, + 906400000, + 915000000, + 925000000, + 928000000, + 0, +}; + +static const uint32_t subghz_hopper_frequency_list[] = { + 315000000, + 330000000, + 390000000, + 433420000, + 433920000, + 868350000, + 0, +}; + +typedef struct { + FuriString* custom_preset_name; + uint8_t* custom_preset_data; + size_t custom_preset_data_size; +} SubGhzSettingCustomPresetItem; + +ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST) + +#define M_OPL_SubGhzSettingCustomPresetItemArray_t() \ + ARRAY_OPLIST(SubGhzSettingCustomPresetItemArray, M_POD_OPLIST) + +LIST_DEF(FrequencyList, uint32_t) + +#define M_OPL_FrequencyList_t() LIST_OPLIST(FrequencyList) + +typedef struct { + SubGhzSettingCustomPresetItemArray_t data; +} SubGhzSettingCustomPresetStruct; + +struct SubGhzSetting { + FrequencyList_t frequencies; + FrequencyList_t hopper_frequencies; + SubGhzSettingCustomPresetStruct* preset; +}; + +SubGhzSetting* subghz_setting_alloc(void) { + SubGhzSetting* instance = malloc(sizeof(SubGhzSetting)); + FrequencyList_init(instance->frequencies); + FrequencyList_init(instance->hopper_frequencies); + instance->preset = malloc(sizeof(SubGhzSettingCustomPresetStruct)); + SubGhzSettingCustomPresetItemArray_init(instance->preset->data); + return instance; +} + +static void subghz_setting_preset_reset(SubGhzSetting* instance) { + for + M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { + furi_string_free(item->custom_preset_name); + free(item->custom_preset_data); + } + SubGhzSettingCustomPresetItemArray_reset(instance->preset->data); +} + +void subghz_setting_free(SubGhzSetting* instance) { + furi_assert(instance); + FrequencyList_clear(instance->frequencies); + FrequencyList_clear(instance->hopper_frequencies); + for + M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { + furi_string_free(item->custom_preset_name); + free(item->custom_preset_data); + } + SubGhzSettingCustomPresetItemArray_clear(instance->preset->data); + free(instance->preset); + free(instance); +} + +static void subghz_setting_load_default_preset( + SubGhzSetting* instance, + const char* preset_name, + const uint8_t* preset_data, + const uint8_t preset_pa_table[8]) { + furi_assert(instance); + furi_assert(preset_data); + uint32_t preset_data_count = 0; + SubGhzSettingCustomPresetItem* item = + SubGhzSettingCustomPresetItemArray_push_raw(instance->preset->data); + + item->custom_preset_name = furi_string_alloc(); + furi_string_set(item->custom_preset_name, preset_name); + + while(preset_data[preset_data_count]) { + preset_data_count += 2; + } + preset_data_count += 2; + item->custom_preset_data_size = sizeof(uint8_t) * preset_data_count + sizeof(uint8_t) * 8; + item->custom_preset_data = malloc(item->custom_preset_data_size); + //load preset register + memcpy(&item->custom_preset_data[0], &preset_data[0], preset_data_count); + //load pa table + memcpy(&item->custom_preset_data[preset_data_count], &preset_pa_table[0], 8); +} + +static void subghz_setting_load_default_region( + SubGhzSetting* instance, + const uint32_t frequencies[], + const uint32_t hopper_frequencies[]) { + furi_assert(instance); + + FrequencyList_reset(instance->frequencies); + FrequencyList_reset(instance->hopper_frequencies); + subghz_setting_preset_reset(instance); + + while(*frequencies) { + FrequencyList_push_back(instance->frequencies, *frequencies); + frequencies++; + } + + while(*hopper_frequencies) { + FrequencyList_push_back(instance->hopper_frequencies, *hopper_frequencies); + hopper_frequencies++; + } + + subghz_setting_load_default_preset( + instance, + "AM270", + (uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs, + furi_hal_subghz_preset_ook_async_patable); + subghz_setting_load_default_preset( + instance, + "AM650", + (uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs, + furi_hal_subghz_preset_ook_async_patable); + subghz_setting_load_default_preset( + instance, + "FM238", + (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs, + furi_hal_subghz_preset_2fsk_async_patable); + subghz_setting_load_default_preset( + instance, + "FM476", + (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs, + furi_hal_subghz_preset_2fsk_async_patable); +} + +// Region check removed +void subghz_setting_load_default(SubGhzSetting* instance) { + subghz_setting_load_default_region( + instance, subghz_frequency_list, subghz_hopper_frequency_list); +} + +void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { + furi_assert(instance); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + FuriString* temp_str; + temp_str = furi_string_alloc(); + uint32_t temp_data32; + bool temp_bool; + + subghz_setting_load_default(instance); + + if(file_path) { + do { + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_I(TAG, "File is not used %s", file_path); + break; + } + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_SETTING_FILE_TYPE)) && + temp_data32 == SUBGHZ_SETTING_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + // Standard frequencies (optional) + temp_bool = true; + flipper_format_read_bool(fff_data_file, "Add_standard_frequencies", &temp_bool, 1); + if(!temp_bool) { + FURI_LOG_I(TAG, "Removing standard frequencies"); + FrequencyList_reset(instance->frequencies); + FrequencyList_reset(instance->hopper_frequencies); + } else { + FURI_LOG_I(TAG, "Keeping standard frequencies"); + } + + // Load frequencies + if(!flipper_format_rewind(fff_data_file)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + while(flipper_format_read_uint32( + fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { + if(furi_hal_subghz_is_frequency_valid(temp_data32)) { + FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); + FrequencyList_push_back(instance->frequencies, temp_data32); + } else { + FURI_LOG_E(TAG, "Frequency not supported %lu", temp_data32); + } + } + + // Load hopper frequencies + if(!flipper_format_rewind(fff_data_file)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + while(flipper_format_read_uint32( + fff_data_file, "Hopper_frequency", (uint32_t*)&temp_data32, 1)) { + if(furi_hal_subghz_is_frequency_valid(temp_data32)) { + FURI_LOG_I(TAG, "Hopper frequency loaded %lu", temp_data32); + FrequencyList_push_back(instance->hopper_frequencies, temp_data32); + } else { + FURI_LOG_E(TAG, "Hopper frequency not supported %lu", temp_data32); + } + } + + // Default frequency (optional) + if(!flipper_format_rewind(fff_data_file)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(flipper_format_read_uint32(fff_data_file, "Default_frequency", &temp_data32, 1)) { + subghz_setting_set_default_frequency(instance, temp_data32); + } + + // custom preset (optional) + if(!flipper_format_rewind(fff_data_file)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + while(flipper_format_read_string(fff_data_file, "Custom_preset_name", temp_str)) { + FURI_LOG_I(TAG, "Custom preset loaded %s", furi_string_get_cstr(temp_str)); + subghz_setting_load_custom_preset( + instance, furi_string_get_cstr(temp_str), fff_data_file); + } + + } while(false); + } + + furi_string_free(temp_str); + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); + + if(!FrequencyList_size(instance->frequencies) || + !FrequencyList_size(instance->hopper_frequencies)) { + FURI_LOG_E(TAG, "Error loading user settings, loading default settings"); + subghz_setting_load_default(instance); + } +} + +void subghz_setting_set_default_frequency(SubGhzSetting* instance, uint32_t frequency_to_setup) { + for + M_EACH(frequency, instance->frequencies, FrequencyList_t) { + *frequency &= FREQUENCY_MASK; + if(*frequency == frequency_to_setup) { + *frequency |= FREQUENCY_FLAG_DEFAULT; + } + } +} + +size_t subghz_setting_get_frequency_count(SubGhzSetting* instance) { + furi_assert(instance); + return FrequencyList_size(instance->frequencies); +} + +size_t subghz_setting_get_hopper_frequency_count(SubGhzSetting* instance) { + furi_assert(instance); + return FrequencyList_size(instance->hopper_frequencies); +} + +size_t subghz_setting_get_preset_count(SubGhzSetting* instance) { + furi_assert(instance); + return SubGhzSettingCustomPresetItemArray_size(instance->preset->data); +} + +const char* subghz_setting_get_preset_name(SubGhzSetting* instance, size_t idx) { + furi_assert(instance); + if(idx >= SubGhzSettingCustomPresetItemArray_size(instance->preset->data)) { + idx = 0; + } + SubGhzSettingCustomPresetItem* item = + SubGhzSettingCustomPresetItemArray_get(instance->preset->data, idx); + return furi_string_get_cstr(item->custom_preset_name); +} + +int subghz_setting_get_inx_preset_by_name(SubGhzSetting* instance, const char* preset_name) { + furi_assert(instance); + size_t idx = 0; + for + M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { + if(strcmp(furi_string_get_cstr(item->custom_preset_name), preset_name) == 0) { + return idx; + } + idx++; + } + furi_crash("SubGhz: No name preset."); + return -1; +} + +bool subghz_setting_load_custom_preset( + SubGhzSetting* instance, + const char* preset_name, + FlipperFormat* fff_data_file) { + furi_assert(instance); + furi_assert(preset_name); + uint32_t temp_data32; + SubGhzSettingCustomPresetItem* item = + SubGhzSettingCustomPresetItemArray_push_raw(instance->preset->data); + item->custom_preset_name = furi_string_alloc(); + furi_string_set(item->custom_preset_name, preset_name); + do { + if(!flipper_format_get_value_count(fff_data_file, "Custom_preset_data", &temp_data32)) + break; + if(!temp_data32 || (temp_data32 % 2)) { + FURI_LOG_E(TAG, "Integrity error Custom_preset_data"); + break; + } + item->custom_preset_data_size = sizeof(uint8_t) * temp_data32; + item->custom_preset_data = malloc(item->custom_preset_data_size); + if(!flipper_format_read_hex( + fff_data_file, + "Custom_preset_data", + item->custom_preset_data, + item->custom_preset_data_size)) { + FURI_LOG_E(TAG, "Missing Custom_preset_data"); + break; + } + return true; + } while(true); + return false; +} + +bool subghz_setting_delete_custom_preset(SubGhzSetting* instance, const char* preset_name) { + furi_assert(instance); + furi_assert(preset_name); + SubGhzSettingCustomPresetItemArray_it_t it; + SubGhzSettingCustomPresetItemArray_it_last(it, instance->preset->data); + while(!SubGhzSettingCustomPresetItemArray_end_p(it)) { + SubGhzSettingCustomPresetItem* item = SubGhzSettingCustomPresetItemArray_ref(it); + if(strcmp(furi_string_get_cstr(item->custom_preset_name), preset_name) == 0) { + furi_string_free(item->custom_preset_name); + free(item->custom_preset_data); + SubGhzSettingCustomPresetItemArray_remove(instance->preset->data, it); + return true; + } + SubGhzSettingCustomPresetItemArray_previous(it); + } + return false; +} + +uint8_t* subghz_setting_get_preset_data(SubGhzSetting* instance, size_t idx) { + furi_assert(instance); + SubGhzSettingCustomPresetItem* item = + SubGhzSettingCustomPresetItemArray_get(instance->preset->data, idx); + return item->custom_preset_data; +} + +size_t subghz_setting_get_preset_data_size(SubGhzSetting* instance, size_t idx) { + furi_assert(instance); + SubGhzSettingCustomPresetItem* item = + SubGhzSettingCustomPresetItemArray_get(instance->preset->data, idx); + return item->custom_preset_data_size; +} + +uint8_t* subghz_setting_get_preset_data_by_name(SubGhzSetting* instance, const char* preset_name) { + furi_assert(instance); + SubGhzSettingCustomPresetItem* item = SubGhzSettingCustomPresetItemArray_get( + instance->preset->data, subghz_setting_get_inx_preset_by_name(instance, preset_name)); + return item->custom_preset_data; +} + +uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { + furi_assert(instance); + if(idx < FrequencyList_size(instance->frequencies)) { + return (*FrequencyList_get(instance->frequencies, idx)) & FREQUENCY_MASK; + } else { + return 0; + } +} + +uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx) { + furi_assert(instance); + if(idx < FrequencyList_size(instance->frequencies)) { + return *FrequencyList_get(instance->hopper_frequencies, idx); + } else { + return 0; + } +} + +uint32_t subghz_setting_get_frequency_default_index(SubGhzSetting* instance) { + furi_assert(instance); + for(size_t i = 0; i < FrequencyList_size(instance->frequencies); i++) { + uint32_t frequency = *FrequencyList_get(instance->frequencies, i); + if(frequency & FREQUENCY_FLAG_DEFAULT) { + return i; + } + } + return 0; +} + +uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance) { + furi_assert(instance); + return subghz_setting_get_frequency( + instance, subghz_setting_get_frequency_default_index(instance)); +} diff --git a/applications/main/subghz/subghz_setting.h b/applications/main/subghz/subghz_setting.h new file mode 100644 index 000000000..3cb07ff6d --- /dev/null +++ b/applications/main/subghz/subghz_setting.h @@ -0,0 +1,58 @@ + +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SUBGHZ_SETTING_DEFAULT_PRESET_COUNT 4 + +typedef struct SubGhzSetting SubGhzSetting; + +SubGhzSetting* subghz_setting_alloc(void); + +void subghz_setting_free(SubGhzSetting* instance); + +void subghz_setting_load(SubGhzSetting* instance, const char* file_path); + +size_t subghz_setting_get_frequency_count(SubGhzSetting* instance); + +size_t subghz_setting_get_hopper_frequency_count(SubGhzSetting* instance); + +size_t subghz_setting_get_preset_count(SubGhzSetting* instance); + +const char* subghz_setting_get_preset_name(SubGhzSetting* instance, size_t idx); + +int subghz_setting_get_inx_preset_by_name(SubGhzSetting* instance, const char* preset_name); + +uint8_t* subghz_setting_get_preset_data(SubGhzSetting* instance, size_t idx); + +size_t subghz_setting_get_preset_data_size(SubGhzSetting* instance, size_t idx); + +uint8_t* subghz_setting_get_preset_data_by_name(SubGhzSetting* instance, const char* preset_name); + +bool subghz_setting_load_custom_preset( + SubGhzSetting* instance, + const char* preset_name, + FlipperFormat* fff_data_file); + +bool subghz_setting_delete_custom_preset(SubGhzSetting* instance, const char* preset_name); + +uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx); + +uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx); + +uint32_t subghz_setting_get_frequency_default_index(SubGhzSetting* instance); + +uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance); + +void subghz_setting_set_default_frequency(SubGhzSetting* instance, uint32_t frequency_to_setup); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/subghz_tx_rx_worker.c b/applications/main/subghz/subghz_tx_rx_worker.c new file mode 100644 index 000000000..e380fc967 --- /dev/null +++ b/applications/main/subghz/subghz_tx_rx_worker.c @@ -0,0 +1,260 @@ +#include "subghz_tx_rx_worker.h" + +#include + +#define TAG "SubGhzTxRxWorker" + +#define SUBGHZ_TXRX_WORKER_BUF_SIZE 2048 +//you can not set more than 62 because it will not fit into the FIFO CC1101 +#define SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE 60 + +#define SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF 40 + +struct SubGhzTxRxWorker { + FuriThread* thread; + FuriStreamBuffer* stream_tx; + FuriStreamBuffer* stream_rx; + + volatile bool worker_running; + volatile bool worker_stoping; + + SubGhzTxRxWorkerStatus status; + + uint32_t frequency; + + SubGhzTxRxWorkerCallbackHaveRead callback_have_read; + void* context_have_read; +}; + +bool subghz_tx_rx_worker_write(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { + furi_assert(instance); + bool ret = false; + size_t stream_tx_free_byte = furi_stream_buffer_spaces_available(instance->stream_tx); + if(size && (stream_tx_free_byte >= size)) { + if(furi_stream_buffer_send( + instance->stream_tx, data, size, SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF) == + size) { + ret = true; + } + } + return ret; +} + +size_t subghz_tx_rx_worker_available(SubGhzTxRxWorker* instance) { + furi_assert(instance); + return furi_stream_buffer_bytes_available(instance->stream_rx); +} + +size_t subghz_tx_rx_worker_read(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { + furi_assert(instance); + return furi_stream_buffer_receive(instance->stream_rx, data, size, 0); +} + +void subghz_tx_rx_worker_set_callback_have_read( + SubGhzTxRxWorker* instance, + SubGhzTxRxWorkerCallbackHaveRead callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(context); + instance->callback_have_read = callback; + instance->context_have_read = context; +} + +bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* size) { + uint8_t timeout = 100; + bool ret = false; + if(instance->status != SubGhzTxRxWorkerStatusRx) { + furi_hal_subghz_rx(); + instance->status = SubGhzTxRxWorkerStatusRx; + furi_delay_tick(1); + } + //waiting for reception to complete + while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin)) { + furi_delay_tick(1); + if(!--timeout) { + FURI_LOG_W(TAG, "RX cc1101_g0 timeout"); + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + break; + } + } + + if(furi_hal_subghz_rx_pipe_not_empty()) { + FURI_LOG_I( + TAG, + "RSSI: %03.1fdbm LQI: %d", + (double)furi_hal_subghz_get_rssi(), + furi_hal_subghz_get_lqi()); + if(furi_hal_subghz_is_rx_data_crc_valid()) { + furi_hal_subghz_read_packet(data, size); + ret = true; + } + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + } + return ret; +} + +void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { + uint8_t timeout = 200; + if(instance->status != SubGhzTxRxWorkerStatusIDLE) { + furi_hal_subghz_idle(); + } + furi_hal_subghz_write_packet(data, size); + furi_hal_subghz_tx(); //start send + instance->status = SubGhzTxRxWorkerStatusTx; + while(!furi_hal_gpio_read( + furi_hal_subghz.cc1101_g0_pin)) { // Wait for GDO0 to be set -> sync transmitted + furi_delay_tick(1); + if(!--timeout) { + FURI_LOG_W(TAG, "TX !cc1101_g0 timeout"); + break; + } + } + while(furi_hal_gpio_read( + furi_hal_subghz.cc1101_g0_pin)) { // Wait for GDO0 to be cleared -> end of packet + furi_delay_tick(1); + if(!--timeout) { + FURI_LOG_W(TAG, "TX cc1101_g0 timeout"); + break; + } + } + furi_hal_subghz_idle(); + instance->status = SubGhzTxRxWorkerStatusIDLE; +} +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t subghz_tx_rx_worker_thread(void* context) { + SubGhzTxRxWorker* instance = context; + FURI_LOG_I(TAG, "Worker start"); + + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_preset(FuriHalSubGhzPresetGFSK9_99KbAsync); + //furi_hal_subghz_load_preset(FuriHalSubGhzPresetMSK99_97KbAsync); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_subghz_set_frequency_and_path(instance->frequency); + furi_hal_subghz_flush_rx(); + + uint8_t data[SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE + 1] = {0}; + size_t size_tx = 0; + uint8_t size_rx[1] = {0}; + uint8_t timeout_tx = 0; + bool callback_rx = false; + + while(instance->worker_running) { + //transmit + size_tx = furi_stream_buffer_bytes_available(instance->stream_tx); + if(size_tx > 0 && !timeout_tx) { + timeout_tx = 10; //20ms + if(size_tx > SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE) { + furi_stream_buffer_receive( + instance->stream_tx, + &data, + SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE, + SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); + subghz_tx_rx_worker_tx(instance, data, SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE); + } else { + //todo checking that he managed to write all the data to the TX buffer + furi_stream_buffer_receive( + instance->stream_tx, &data, size_tx, SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); + subghz_tx_rx_worker_tx(instance, data, size_tx); + } + } else { + //recive + if(subghz_tx_rx_worker_rx(instance, data, size_rx)) { + if(furi_stream_buffer_spaces_available(instance->stream_rx) >= size_rx[0]) { + if(instance->callback_have_read && + furi_stream_buffer_bytes_available(instance->stream_rx) == 0) { + callback_rx = true; + } + //todo checking that he managed to write all the data to the RX buffer + furi_stream_buffer_send( + instance->stream_rx, + &data, + size_rx[0], + SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); + if(callback_rx) { + instance->callback_have_read(instance->context_have_read); + callback_rx = false; + } + } else { + //todo RX buffer overflow + } + } + } + + if(timeout_tx) timeout_tx--; + furi_delay_tick(1); + } + + furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); + furi_hal_subghz_sleep(); + + FURI_LOG_I(TAG, "Worker stop"); + return 0; +} + +SubGhzTxRxWorker* subghz_tx_rx_worker_alloc() { + SubGhzTxRxWorker* instance = malloc(sizeof(SubGhzTxRxWorker)); + + instance->thread = + furi_thread_alloc_ex("SubGhzTxRxWorker", 2048, subghz_tx_rx_worker_thread, instance); + instance->stream_tx = + furi_stream_buffer_alloc(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); + instance->stream_rx = + furi_stream_buffer_alloc(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); + + instance->status = SubGhzTxRxWorkerStatusIDLE; + instance->worker_stoping = true; + + return instance; +} + +void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance) { + furi_assert(instance); + furi_assert(!instance->worker_running); + furi_stream_buffer_free(instance->stream_tx); + furi_stream_buffer_free(instance->stream_rx); + furi_thread_free(instance->thread); + + free(instance); +} + +bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { + furi_assert(instance); + furi_assert(!instance->worker_running); + bool res = false; + furi_stream_buffer_reset(instance->stream_tx); + furi_stream_buffer_reset(instance->stream_rx); + + instance->worker_running = true; + + if(furi_hal_subghz_is_tx_allowed(frequency)) { + instance->frequency = frequency; + res = true; + } + + furi_thread_start(instance->thread); + + return res; +} + +void subghz_tx_rx_worker_stop(SubGhzTxRxWorker* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + + furi_thread_join(instance->thread); +} + +bool subghz_tx_rx_worker_is_running(SubGhzTxRxWorker* instance) { + furi_assert(instance); + return instance->worker_running; +} diff --git a/applications/main/subghz/subghz_tx_rx_worker.h b/applications/main/subghz/subghz_tx_rx_worker.h new file mode 100644 index 000000000..ddc02e749 --- /dev/null +++ b/applications/main/subghz/subghz_tx_rx_worker.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*SubGhzTxRxWorkerCallbackHaveRead)(void* context); + +typedef struct SubGhzTxRxWorker SubGhzTxRxWorker; + +typedef enum { + SubGhzTxRxWorkerStatusIDLE, + SubGhzTxRxWorkerStatusTx, + SubGhzTxRxWorkerStatusRx, +} SubGhzTxRxWorkerStatus; + +/** + * SubGhzTxRxWorker, add data to transfer + * @param instance Pointer to a SubGhzTxRxWorker instance + * @param data *data + * @param size data size + * @return bool true if ok + */ +bool subghz_tx_rx_worker_write(SubGhzTxRxWorker* instance, uint8_t* data, size_t size); + +/** + * SubGhzTxRxWorker, get available data + * @param instance Pointer to a SubGhzTxRxWorker instance + * @return size_t data size + */ +size_t subghz_tx_rx_worker_available(SubGhzTxRxWorker* instance); + +/** + * SubGhzTxRxWorker, read data + * @param instance Pointer to a SubGhzTxRxWorker instance + * @param data *data + * @param size max data size, which can be read + * @return size_t data size, how much is actually read + */ +size_t subghz_tx_rx_worker_read(SubGhzTxRxWorker* instance, uint8_t* data, size_t size); + +/** + * Сallback SubGhzTxRxWorker when there is data to read in an empty buffer + * @param instance Pointer to a SubGhzTxRxWorker instance + * @param callback SubGhzTxRxWorkerCallbackHaveRead callback + * @param context + */ +void subghz_tx_rx_worker_set_callback_have_read( + SubGhzTxRxWorker* instance, + SubGhzTxRxWorkerCallbackHaveRead callback, + void* context); + +/** + * Allocate SubGhzTxRxWorker + * @return SubGhzTxRxWorker* Pointer to a SubGhzTxRxWorker instance + */ +SubGhzTxRxWorker* subghz_tx_rx_worker_alloc(); + +/** + * Free SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance + */ +void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance); + +/** + * Start SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance + * @return bool - true if ok + */ +bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency); + +/** + * Stop SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance + */ +void subghz_tx_rx_worker_stop(SubGhzTxRxWorker* instance); + +/** + * Check if worker is running + * @param instance Pointer to a SubGhzTxRxWorker instance + * @return bool - true if running + */ +bool subghz_tx_rx_worker_is_running(SubGhzTxRxWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/subghz_worker.c b/applications/main/subghz/subghz_worker.c new file mode 100644 index 000000000..50b5aba51 --- /dev/null +++ b/applications/main/subghz/subghz_worker.c @@ -0,0 +1,149 @@ +#include "subghz_worker.h" + +#include + +#define TAG "SubGhzWorker" + +struct SubGhzWorker { + FuriThread* thread; + FuriStreamBuffer* stream; + + volatile bool running; + volatile bool overrun; + + LevelDuration filter_level_duration; + uint16_t filter_duration; + + SubGhzWorkerOverrunCallback overrun_callback; + SubGhzWorkerPairCallback pair_callback; + void* context; +}; + +/** Rx callback timer + * + * @param level received signal level + * @param duration received signal duration + * @param context + */ +void subghz_worker_rx_callback(bool level, uint32_t duration, void* context) { + SubGhzWorker* instance = context; + + LevelDuration level_duration = level_duration_make(level, duration); + if(instance->overrun) { + instance->overrun = false; + level_duration = level_duration_reset(); + } + size_t ret = + furi_stream_buffer_send(instance->stream, &level_duration, sizeof(LevelDuration), 0); + if(sizeof(LevelDuration) != ret) instance->overrun = true; +} + +/** Worker callback thread + * + * @param context + * @return exit code + */ +static int32_t subghz_worker_thread_callback(void* context) { + SubGhzWorker* instance = context; + + LevelDuration level_duration; + while(instance->running) { + int ret = furi_stream_buffer_receive( + instance->stream, &level_duration, sizeof(LevelDuration), 10); + if(ret == sizeof(LevelDuration)) { + if(level_duration_is_reset(level_duration)) { + FURI_LOG_E(TAG, "Overrun buffer"); + if(instance->overrun_callback) instance->overrun_callback(instance->context); + } else { + bool level = level_duration_get_level(level_duration); + uint32_t duration = level_duration_get_duration(level_duration); + + if((duration < instance->filter_duration) || + (instance->filter_level_duration.level == level)) { + instance->filter_level_duration.duration += duration; + + } else if(instance->filter_level_duration.level != level) { + if(instance->pair_callback) + instance->pair_callback( + instance->context, + instance->filter_level_duration.level, + instance->filter_level_duration.duration); + + instance->filter_level_duration.duration = duration; + instance->filter_level_duration.level = level; + } + } + } + } + + return 0; +} + +SubGhzWorker* subghz_worker_alloc() { + SubGhzWorker* instance = malloc(sizeof(SubGhzWorker)); + + instance->thread = + furi_thread_alloc_ex("SubGhzWorker", 2048, subghz_worker_thread_callback, instance); + + instance->stream = + furi_stream_buffer_alloc(sizeof(LevelDuration) * 4096, sizeof(LevelDuration)); + + //setting default filter in us + instance->filter_duration = 30; + + return instance; +} + +void subghz_worker_free(SubGhzWorker* instance) { + furi_assert(instance); + + furi_stream_buffer_free(instance->stream); + furi_thread_free(instance->thread); + + free(instance); +} + +void subghz_worker_set_overrun_callback( + SubGhzWorker* instance, + SubGhzWorkerOverrunCallback callback) { + furi_assert(instance); + instance->overrun_callback = callback; +} + +void subghz_worker_set_pair_callback(SubGhzWorker* instance, SubGhzWorkerPairCallback callback) { + furi_assert(instance); + instance->pair_callback = callback; +} + +void subghz_worker_set_context(SubGhzWorker* instance, void* context) { + furi_assert(instance); + instance->context = context; +} + +void subghz_worker_start(SubGhzWorker* instance) { + furi_assert(instance); + furi_assert(!instance->running); + + instance->running = true; + + furi_thread_start(instance->thread); +} + +void subghz_worker_stop(SubGhzWorker* instance) { + furi_assert(instance); + furi_assert(instance->running); + + instance->running = false; + + furi_thread_join(instance->thread); +} + +bool subghz_worker_is_running(SubGhzWorker* instance) { + furi_assert(instance); + return instance->running; +} + +void subghz_worker_set_filter(SubGhzWorker* instance, uint16_t timeout) { + furi_assert(instance); + instance->filter_duration = timeout; +} \ No newline at end of file diff --git a/applications/main/subghz/subghz_worker.h b/applications/main/subghz/subghz_worker.h new file mode 100644 index 000000000..657278f50 --- /dev/null +++ b/applications/main/subghz/subghz_worker.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzWorker SubGhzWorker; + +typedef void (*SubGhzWorkerOverrunCallback)(void* context); + +typedef void (*SubGhzWorkerPairCallback)(void* context, bool level, uint32_t duration); + +void subghz_worker_rx_callback(bool level, uint32_t duration, void* context); + +/** + * Allocate SubGhzWorker. + * @return SubGhzWorker* Pointer to a SubGhzWorker instance + */ +SubGhzWorker* subghz_worker_alloc(); + +/** + * Free SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance + */ +void subghz_worker_free(SubGhzWorker* instance); + +/** + * Overrun callback SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance + * @param callback SubGhzWorkerOverrunCallback callback + */ +void subghz_worker_set_overrun_callback( + SubGhzWorker* instance, + SubGhzWorkerOverrunCallback callback); + +/** + * Pair callback SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance + * @param callback SubGhzWorkerOverrunCallback callback + */ +void subghz_worker_set_pair_callback(SubGhzWorker* instance, SubGhzWorkerPairCallback callback); + +/** + * Context callback SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance + * @param context + */ +void subghz_worker_set_context(SubGhzWorker* instance, void* context); + +/** + * Start SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance + */ +void subghz_worker_start(SubGhzWorker* instance); + +/** Stop SubGhzWorker + * @param instance Pointer to a SubGhzWorker instance + */ +void subghz_worker_stop(SubGhzWorker* instance); + +/** + * Check if worker is running. + * @param instance Pointer to a SubGhzWorker instance + * @return bool - true if running + */ +bool subghz_worker_is_running(SubGhzWorker* instance); + +/** + * Short duration filter setting. + * glues short durations into 1. The default setting is 30 us, if set to 0 the filter will be disabled + * @param instance Pointer to a SubGhzWorker instance + * @param timeout time in us + */ +void subghz_worker_set_filter(SubGhzWorker* instance, uint16_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/transmitter.c b/applications/main/subghz/transmitter.c new file mode 100644 index 000000000..8507ee054 --- /dev/null +++ b/applications/main/subghz/transmitter.c @@ -0,0 +1,64 @@ +#include "transmitter.h" + +#include "protocols/base.h" +#include "registry.h" +#include "protocols/protocol_items.h" + +struct SubGhzTransmitter { + const SubGhzProtocol* protocol; + SubGhzProtocolEncoderBase* protocol_instance; +}; + +SubGhzTransmitter* + subghz_transmitter_alloc_init(SubGhzEnvironment* environment, const char* protocol_name) { + SubGhzTransmitter* instance = NULL; + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(environment); + + const SubGhzProtocol* protocol = + subghz_protocol_registry_get_by_name(protocol_registry_items, protocol_name); + + if(protocol && protocol->encoder && protocol->encoder->alloc) { + instance = malloc(sizeof(SubGhzTransmitter)); + instance->protocol = protocol; + instance->protocol_instance = instance->protocol->encoder->alloc(environment); + } + return instance; +} + +void subghz_transmitter_free(SubGhzTransmitter* instance) { + furi_assert(instance); + instance->protocol->encoder->free(instance->protocol_instance); + free(instance); +} + +SubGhzProtocolEncoderBase* subghz_transmitter_get_protocol_instance(SubGhzTransmitter* instance) { + furi_assert(instance); + return instance->protocol_instance; +} + +bool subghz_transmitter_stop(SubGhzTransmitter* instance) { + furi_assert(instance); + bool ret = false; + if(instance->protocol && instance->protocol->encoder && instance->protocol->encoder->stop) { + instance->protocol->encoder->stop(instance->protocol_instance); + ret = true; + } + return ret; +} + +bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool ret = false; + if(instance->protocol && instance->protocol->encoder && + instance->protocol->encoder->deserialize) { + ret = + instance->protocol->encoder->deserialize(instance->protocol_instance, flipper_format); + } + return ret; +} + +LevelDuration subghz_transmitter_yield(void* context) { + SubGhzTransmitter* instance = context; + return instance->protocol->encoder->yield(instance->protocol_instance); +} diff --git a/applications/main/subghz/transmitter.h b/applications/main/subghz/transmitter.h new file mode 100644 index 000000000..cce98a463 --- /dev/null +++ b/applications/main/subghz/transmitter.h @@ -0,0 +1,55 @@ +#pragma once + +#include "types.h" +#include "environment.h" +#include "protocols/base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzTransmitter SubGhzTransmitter; + +/** + * Allocate and init SubGhzTransmitter. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzTransmitter* pointer to a SubGhzTransmitter instance + */ +SubGhzTransmitter* + subghz_transmitter_alloc_init(SubGhzEnvironment* environment, const char* protocol_name); + +/** + * Free SubGhzTransmitter. + * @param instance Pointer to a SubGhzTransmitter instance + */ +void subghz_transmitter_free(SubGhzTransmitter* instance); + +/** Get protocol instance. + * @param instance Pointer to a SubGhzTransmitter instance + */ +SubGhzProtocolEncoderBase* subghz_transmitter_get_protocol_instance(SubGhzTransmitter* instance); + +/** + * Forced transmission stop. + * @param instance Pointer to a SubGhzTransmitter instance + */ +bool subghz_transmitter_stop(SubGhzTransmitter* instance); + +/** + * Deserialize and generating an upload to send. + * @param instance Pointer to a SubGhzTransmitter instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzTransmitter instance + * @return LevelDuration + */ +LevelDuration subghz_transmitter_yield(void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/subghz/types.h b/applications/main/subghz/types.h new file mode 100644 index 000000000..9d121dc3c --- /dev/null +++ b/applications/main/subghz/types.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "environment.h" +#include +#include + +#define SUBGHZ_APP_FOLDER ANY_PATH("subghz") +#define SUBGHZ_RAW_FOLDER EXT_PATH("subghz") +#define SUBGHZ_APP_EXTENSION ".sub" + +#define SUBGHZ_KEY_FILE_VERSION 1 +#define SUBGHZ_KEY_FILE_TYPE "Flipper SubGhz Key File" + +#define SUBGHZ_RAW_FILE_VERSION 1 +#define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" + +// Radio Preset +typedef struct { + FuriString* name; + uint32_t frequency; + uint8_t* data; + size_t data_size; +} SubGhzRadioPreset; + +// Allocator and Deallocator +typedef void* (*SubGhzAlloc)(SubGhzEnvironment* environment); +typedef void (*SubGhzFree)(void* context); + +// Serialize and Deserialize +typedef bool ( + *SubGhzSerialize)(void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); +typedef bool (*SubGhzDeserialize)(void* context, FlipperFormat* flipper_format); + +// Decoder specific +typedef void (*SubGhzDecoderFeed)(void* decoder, bool level, uint32_t duration); +typedef void (*SubGhzDecoderReset)(void* decoder); +typedef uint8_t (*SubGhzGetHashData)(void* decoder); +typedef void (*SubGhzGetString)(void* decoder, FuriString* output); + +// Encoder specific +typedef void (*SubGhzEncoderStop)(void* encoder); +typedef LevelDuration (*SubGhzEncoderYield)(void* context); + +typedef struct { + SubGhzAlloc alloc; + SubGhzFree free; + + SubGhzDecoderFeed feed; + SubGhzDecoderReset reset; + + SubGhzGetHashData get_hash_data; + SubGhzGetString get_string; + SubGhzSerialize serialize; + SubGhzDeserialize deserialize; +} SubGhzProtocolDecoder; + +typedef struct { + SubGhzAlloc alloc; + SubGhzFree free; + + SubGhzDeserialize deserialize; + SubGhzEncoderStop stop; + SubGhzEncoderYield yield; +} SubGhzProtocolEncoder; + +typedef enum { + SubGhzProtocolTypeUnknown = 0, + SubGhzProtocolTypeStatic, + SubGhzProtocolTypeDynamic, + SubGhzProtocolTypeRAW, + SubGhzProtocolWeatherStation, + SubGhzProtocolCustom, + SubGhzProtocolTypeBinRAW, +} SubGhzProtocolType; + +typedef enum { + SubGhzProtocolFlag_RAW = (1 << 0), + SubGhzProtocolFlag_Decodable = (1 << 1), + SubGhzProtocolFlag_315 = (1 << 2), + SubGhzProtocolFlag_433 = (1 << 3), + SubGhzProtocolFlag_868 = (1 << 4), + SubGhzProtocolFlag_AM = (1 << 5), + SubGhzProtocolFlag_FM = (1 << 6), + SubGhzProtocolFlag_Save = (1 << 7), + SubGhzProtocolFlag_Load = (1 << 8), + SubGhzProtocolFlag_Send = (1 << 9), + SubGhzProtocolFlag_BinRAW = (1 << 10), +} SubGhzProtocolFlag; + +typedef struct { + const char* name; + SubGhzProtocolType type; + SubGhzProtocolFlag flag; + + const SubGhzProtocolEncoder* encoder; + const SubGhzProtocolDecoder* decoder; +} SubGhzProtocol; diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 0a219a48c..74bdbf5b0 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -14,6 +14,8 @@ #define MENU_ITEMS 4u #define UNLOCK_CNT 3 +#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f + typedef struct { FuriString* item_str; uint8_t type; @@ -34,7 +36,7 @@ static const Icon* ReceiverItemIcons[] = { [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, [SubGhzProtocolTypeStatic] = &I_Static_9x7, [SubGhzProtocolTypeDynamic] = &I_Dynamic_9x7, - [SubGhzProtocolTypeRAW] = &I_Raw_9x7, + [SubGhzProtocolTypeBinRAW] = &I_Raw_9x7, }; typedef enum { @@ -64,6 +66,7 @@ typedef struct { uint16_t history_item; SubGhzViewReceiverBarShow bar_show; SubGhzViewReceiverMode mode; + uint8_t u_rssi; } SubGhzViewReceiverModel; void subghz_view_receiver_set_mode( @@ -73,6 +76,21 @@ void subghz_view_receiver_set_mode( subghz_receiver->view, SubGhzViewReceiverModel * model, { model->mode = mode; }, true); } +void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) { + furi_assert(instance); + with_view_model( + instance->view, + SubGhzViewReceiverModel * model, + { + if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { + model->u_rssi = 0; + } else { + model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); + } + }, + true); +} + void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { furi_assert(subghz_receiver); subghz_receiver->lock_count = 0; @@ -191,6 +209,16 @@ static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool s canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); } +static void subghz_view_rssi_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { + for(uint8_t i = 1; i < model->u_rssi; i++) { + if(i % 5) { + canvas_draw_dot(canvas, 46 + i, 50); + canvas_draw_dot(canvas, 47 + i, 51); + canvas_draw_dot(canvas, 46 + i, 52); + } + } +} + void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); @@ -198,8 +226,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { if(model->mode == SubGhzViewReceiverModeLive) { elements_button_left(canvas, "Config"); - canvas_draw_line(canvas, 46, 51, 125, 51); + //canvas_draw_line(canvas, 46, 51, 125, 51); } else { + canvas_draw_line(canvas, 2, 52, 125, 52); canvas_draw_str(canvas, 3, 62, furi_string_get_cstr(model->progress_str)); } @@ -235,7 +264,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Scanning_123x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 46, "Scanning..."); - canvas_draw_line(canvas, 46, 51, 125, 51); + //canvas_draw_line(canvas, 46, 51, 125, 51); canvas_set_font(canvas, FontSecondary); } else { canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Scanning_123x52); @@ -245,6 +274,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { } } + if(model->mode == SubGhzViewReceiverModeLive) { + subghz_view_rssi_draw(canvas, model); + } switch(model->bar_show) { case SubGhzViewReceiverBarShowLock: canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); @@ -252,7 +284,28 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { break; case SubGhzViewReceiverBarShowToUnlockPress: canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); +#ifdef SUBGHZ_EXT_PRESET_NAME + if(model->history_item == 0 && model->mode == SubGhzViewReceiverModeLive) { + canvas_draw_str( + canvas, + 44 + canvas_string_width(canvas, furi_string_get_cstr(model->frequency_str)) + 1, + 62, + "MHz"); + const char* str = furi_string_get_cstr(model->preset_str); + const uint8_t vertical_offset = 7; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + canvas_draw_str( + canvas, + canvas_width(canvas) - (string_width + horizontal_offset), + vertical_offset, + str); + } else { + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + } +#else canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); +#endif canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); canvas_set_font(canvas, FontSecondary); elements_bold_rounded_frame(canvas, 14, 8, 99, 48); diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index 829277174..37eb473de 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -12,6 +12,8 @@ void subghz_view_receiver_set_mode( SubGhzViewReceiver* subghz_receiver, SubGhzViewReceiverMode mode); +void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi); + void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); void subghz_view_receiver_set_callback( diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index ff971fd3b..9fa304e90 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -23,10 +23,10 @@ static const uint32_t subghz_frequency_list[] = { 300000000, 302757000, 303875000, 304250000, 307000000, 307500000, 307800000, 309000000, 310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000, 314980000, - 315000000, 318000000, 330000000, 345000000, 348000000, 387000000, 390000000, 418000000, - 433075000, 433220000, 433420000, 433657070, 433889000, 433920000, 434075000, 434176948, - 434390000, 434420000, 434775000, 438900000, 440175000, 464000000, 779000000, 868350000, - 868400000, 868800000, 868950000, 906400000, 915000000, 925000000, 928000000}; + 315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000, 390000000, + 418000000, 433075000, 433220000, 433420000, 433657070, 433889000, 433920000, 434075000, + 434176948, 434390000, 434420000, 434775000, 438900000, 440175000, 464000000, 779000000, + 868350000, 868400000, 868800000, 868950000, 906400000, 915000000, 925000000, 928000000}; typedef enum { SubGhzFrequencyAnalyzerStatusIDLE, @@ -165,6 +165,7 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel // Title canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 7, furi_hal_subghz_get_radio_type() ? "Ext" : "Int"); canvas_draw_str(canvas, 20, 7, "Frequency Analyzer"); // RSSI diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index dcfc281d2..7ba2f4434 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -30,6 +30,7 @@ typedef struct { SubGhzReadRAWStatus status; bool raw_send_only; float raw_threshold_rssi; + bool not_showing_samples; } SubGhzReadRAWModel; void subghz_read_raw_set_callback( @@ -92,7 +93,10 @@ void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample) with_view_model( instance->view, SubGhzReadRAWModel * model, - { furi_string_printf(model->sample_write, "%zu spl.", sample); }, + { + model->not_showing_samples = false; + furi_string_printf(model->sample_write, "%zu spl.", sample); + }, false); } @@ -280,8 +284,15 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { uint8_t graphics_mode = 1; canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 5, 7, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 40, 7, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 0, 7, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 35, 7, furi_string_get_cstr(model->preset_str)); + + if(model->not_showing_samples) { + canvas_draw_str(canvas, 77, 7, furi_hal_subghz_get_radio_type() ? "R: Ext" : "R: Int"); + } else { + canvas_draw_str(canvas, 70, 7, furi_hal_subghz_get_radio_type() ? "E" : "I"); + } + canvas_draw_str_aligned( canvas, 126, 0, AlignRight, AlignTop, furi_string_get_cstr(model->sample_write)); @@ -448,6 +459,7 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { model->status = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; + model->not_showing_samples = true; furi_string_set(model->sample_write, "0 spl."); furi_string_reset(model->file_name); instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); @@ -509,6 +521,7 @@ void subghz_read_raw_set_status( model->status = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; + model->not_showing_samples = true; furi_string_reset(model->file_name); furi_string_set(model->sample_write, "0 spl."); model->raw_threshold_rssi = raw_threshold_rssi; @@ -530,6 +543,7 @@ void subghz_read_raw_set_status( model->status = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; + model->not_showing_samples = true; furi_string_set(model->file_name, file_name); furi_string_set(model->sample_write, "RAW"); }, @@ -542,6 +556,7 @@ void subghz_read_raw_set_status( { model->status = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { + model->not_showing_samples = true; furi_string_set(model->file_name, file_name); furi_string_set(model->sample_write, "RAW"); } else { diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index e533a6aac..0cc9a2966 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -115,14 +115,16 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { furi_hal_subghz_set_path(model->path); if(model->status == SubGhzTestCarrierModelStatusRx) { - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_rx(); } else { furi_hal_gpio_init( - &gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_cc1101_g0, true); + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, true); if(!furi_hal_subghz_tx()) { - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); subghz_test_carrier->callback( SubGhzTestCarrierEventOnlyRx, subghz_test_carrier->context); } @@ -140,7 +142,7 @@ void subghz_test_carrier_enter(void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); with_view_model( subghz_test_carrier->view, diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c index 6abefda76..b9e5a8c9c 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/main/subghz/views/subghz_test_static.c @@ -143,8 +143,9 @@ void subghz_test_static_enter(void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_cc1101_g0, false); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); instance->status_tx = SubGhzTestStaticStatusIDLE; with_view_model( diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index dc324a6a5..102639924 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -84,10 +84,15 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - elements_multiline_text(canvas, 0, 7, furi_string_get_cstr(model->key_str)); + elements_multiline_text_aligned( + canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->key_str)); canvas_draw_str(canvas, 78, 7, furi_string_get_cstr(model->frequency_str)); canvas_draw_str(canvas, 113, 7, furi_string_get_cstr(model->preset_str)); - if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); + + if(model->show_button) { + canvas_draw_str(canvas, 58, 62, furi_hal_subghz_get_radio_type() ? "R: Ext" : "R: Int"); + subghz_view_transmitter_button_right(canvas, "Send"); + } } bool subghz_view_transmitter_input(InputEvent* event, void* context) { diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 24c2d32db..cbe0ec2ed 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,15.0,, +Version,+,15.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -178,6 +178,7 @@ Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/environment.h,, +Header,+,lib/subghz/protocols/protocol_items.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, @@ -534,6 +535,8 @@ Function,-,atoff,float,const char* Function,+,atoi,int,const char* Function,-,atol,long,const char* Function,-,atoll,long long,const char* +Function,-,atomo_decrypt,void,uint8_t* +Function,-,atomo_encrypt,void,uint8_t* Function,-,basename,char*,const char* Function,-,bcmp,int,"const void*, const void*, size_t" Function,-,bcopy,void,"const void*, void*, size_t" @@ -1276,6 +1279,7 @@ Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t Function,+,furi_hal_region_is_provisioned,_Bool, Function,+,furi_hal_region_set,void,FuriHalRegion* Function,-,furi_hal_resources_deinit_early,void, +Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_rfid_change_read_config,void,"float, float" @@ -1354,13 +1358,18 @@ Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, Function,+,furi_hal_spi_dma_init,void, Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,+,furi_hal_subghz_check_radio,_Bool, +Function,+,furi_hal_subghz_disable_ext_power,void, Function,-,furi_hal_subghz_dump_state,void, +Function,+,furi_hal_subghz_enable_ext_power,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, Function,+,furi_hal_subghz_get_lqi,uint8_t, +Function,+,furi_hal_subghz_get_radio_type,SubGhzRadioType, Function,+,furi_hal_subghz_get_rssi,float, Function,+,furi_hal_subghz_idle,void, Function,-,furi_hal_subghz_init,void, +Function,+,furi_hal_subghz_init_check,_Bool, Function,+,furi_hal_subghz_is_async_tx_complete,_Bool, Function,+,furi_hal_subghz_is_frequency_valid,_Bool,uint32_t Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, @@ -1377,6 +1386,7 @@ Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,+,furi_hal_subghz_set_radio_type,_Bool,SubGhzRadioType Function,-,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" @@ -1746,6 +1756,8 @@ Function,-,j1f,float,float Function,-,jn,double,"int, double" Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] +Function,-,keeloq_reset_kl_type,void, +Function,-,keeloq_reset_mfname,void, Function,-,l64a,char*,long Function,-,labs,long,long Function,-,lcong48,void,unsigned short[7] @@ -2505,6 +2517,8 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,-,star_line_reset_kl_type,void, +Function,-,star_line_reset_mfname,void, Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" @@ -2686,28 +2700,541 @@ Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" -Function,+,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" +Function,-,subghz_protocol_decoder_alutech_at_4n_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_alutech_at_4n_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_alutech_at_4n_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_alutech_at_4n_free,void,void* +Function,-,subghz_protocol_decoder_alutech_at_4n_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_alutech_at_4n_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_alutech_at_4n_reset,void,void* +Function,-,subghz_protocol_decoder_alutech_at_4n_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_ansonic_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_ansonic_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_ansonic_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_ansonic_free,void,void* +Function,-,subghz_protocol_decoder_ansonic_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_ansonic_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_ansonic_reset,void,void* +Function,-,subghz_protocol_decoder_ansonic_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" +Function,-,subghz_protocol_decoder_bett_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_bett_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_bett_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_bett_free,void,void* +Function,-,subghz_protocol_decoder_bett_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_bett_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_bett_reset,void,void* +Function,-,subghz_protocol_decoder_bett_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_bin_raw_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_bin_raw_data_input_rssi,void,"SubGhzProtocolDecoderBinRAW*, float" +Function,-,subghz_protocol_decoder_bin_raw_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_bin_raw_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_bin_raw_free,void,void* +Function,-,subghz_protocol_decoder_bin_raw_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_bin_raw_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_bin_raw_reset,void,void* +Function,-,subghz_protocol_decoder_bin_raw_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_came_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_came_atomo_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_came_atomo_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_came_atomo_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_came_atomo_free,void,void* +Function,-,subghz_protocol_decoder_came_atomo_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_came_atomo_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_came_atomo_reset,void,void* +Function,-,subghz_protocol_decoder_came_atomo_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_came_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_came_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_came_free,void,void* +Function,-,subghz_protocol_decoder_came_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_came_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_came_reset,void,void* +Function,-,subghz_protocol_decoder_came_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_came_twee_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_came_twee_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_came_twee_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_came_twee_free,void,void* +Function,-,subghz_protocol_decoder_came_twee_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_came_twee_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_came_twee_reset,void,void* +Function,-,subghz_protocol_decoder_came_twee_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_chamb_code_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_chamb_code_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_chamb_code_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_chamb_code_free,void,void* +Function,-,subghz_protocol_decoder_chamb_code_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_chamb_code_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_chamb_code_reset,void,void* +Function,-,subghz_protocol_decoder_chamb_code_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_clemsa_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_clemsa_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_clemsa_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_clemsa_free,void,void* +Function,-,subghz_protocol_decoder_clemsa_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_clemsa_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_clemsa_reset,void,void* +Function,-,subghz_protocol_decoder_clemsa_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_doitrand_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_doitrand_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_doitrand_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_doitrand_free,void,void* +Function,-,subghz_protocol_decoder_doitrand_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_doitrand_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_doitrand_reset,void,void* +Function,-,subghz_protocol_decoder_doitrand_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_dooya_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_dooya_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_dooya_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_dooya_free,void,void* +Function,-,subghz_protocol_decoder_dooya_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_dooya_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_dooya_reset,void,void* +Function,-,subghz_protocol_decoder_dooya_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_faac_slh_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_faac_slh_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_faac_slh_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_faac_slh_free,void,void* +Function,-,subghz_protocol_decoder_faac_slh_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_faac_slh_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_faac_slh_reset,void,void* +Function,-,subghz_protocol_decoder_faac_slh_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_gate_tx_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_gate_tx_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_gate_tx_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_gate_tx_free,void,void* +Function,-,subghz_protocol_decoder_gate_tx_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_gate_tx_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_gate_tx_reset,void,void* +Function,-,subghz_protocol_decoder_gate_tx_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_holtek_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_holtek_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_holtek_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_holtek_free,void,void* +Function,-,subghz_protocol_decoder_holtek_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_holtek_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_holtek_reset,void,void* +Function,-,subghz_protocol_decoder_holtek_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_holtek_th12x_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_holtek_th12x_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_holtek_th12x_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_holtek_th12x_free,void,void* +Function,-,subghz_protocol_decoder_holtek_th12x_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_holtek_th12x_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_holtek_th12x_reset,void,void* +Function,-,subghz_protocol_decoder_holtek_th12x_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_honeywell_wdb_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_honeywell_wdb_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_honeywell_wdb_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_honeywell_wdb_free,void,void* +Function,-,subghz_protocol_decoder_honeywell_wdb_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_honeywell_wdb_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_honeywell_wdb_reset,void,void* +Function,-,subghz_protocol_decoder_honeywell_wdb_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_hormann_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_hormann_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_hormann_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_hormann_free,void,void* +Function,-,subghz_protocol_decoder_hormann_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_hormann_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_hormann_reset,void,void* +Function,-,subghz_protocol_decoder_hormann_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_ido_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_ido_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_ido_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_ido_free,void,void* +Function,-,subghz_protocol_decoder_ido_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_ido_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_ido_reset,void,void* +Function,-,subghz_protocol_decoder_ido_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_intertechno_v3_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_intertechno_v3_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_intertechno_v3_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_intertechno_v3_free,void,void* +Function,-,subghz_protocol_decoder_intertechno_v3_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_intertechno_v3_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_intertechno_v3_reset,void,void* +Function,-,subghz_protocol_decoder_intertechno_v3_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_keeloq_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_keeloq_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_keeloq_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_keeloq_free,void,void* +Function,-,subghz_protocol_decoder_keeloq_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_keeloq_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_keeloq_reset,void,void* +Function,-,subghz_protocol_decoder_keeloq_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_kia_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_kia_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_kia_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_kia_free,void,void* +Function,-,subghz_protocol_decoder_kia_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_kia_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_kia_reset,void,void* +Function,-,subghz_protocol_decoder_kia_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_free,void,void* +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_reset,void,void* +Function,-,subghz_protocol_decoder_kinggates_stylo_4k_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_linear_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_linear_delta3_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_linear_delta3_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_linear_delta3_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_linear_delta3_free,void,void* +Function,-,subghz_protocol_decoder_linear_delta3_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_linear_delta3_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_linear_delta3_reset,void,void* +Function,-,subghz_protocol_decoder_linear_delta3_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_linear_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_linear_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_linear_free,void,void* +Function,-,subghz_protocol_decoder_linear_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_linear_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_linear_reset,void,void* +Function,-,subghz_protocol_decoder_linear_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_magellan_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_magellan_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_magellan_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_magellan_free,void,void* +Function,-,subghz_protocol_decoder_magellan_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_magellan_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_magellan_reset,void,void* +Function,-,subghz_protocol_decoder_magellan_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_marantec_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_marantec_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_marantec_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_marantec_free,void,void* +Function,-,subghz_protocol_decoder_marantec_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_marantec_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_marantec_reset,void,void* +Function,-,subghz_protocol_decoder_marantec_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_megacode_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_megacode_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_megacode_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_megacode_free,void,void* +Function,-,subghz_protocol_decoder_megacode_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_megacode_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_megacode_reset,void,void* +Function,-,subghz_protocol_decoder_megacode_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_nero_radio_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_nero_radio_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_nero_radio_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_nero_radio_free,void,void* +Function,-,subghz_protocol_decoder_nero_radio_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_nero_radio_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_nero_radio_reset,void,void* +Function,-,subghz_protocol_decoder_nero_radio_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_nero_sketch_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_nero_sketch_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_nero_sketch_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_nero_sketch_free,void,void* +Function,-,subghz_protocol_decoder_nero_sketch_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_nero_sketch_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_nero_sketch_reset,void,void* +Function,-,subghz_protocol_decoder_nero_sketch_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_nice_flo_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_nice_flo_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_nice_flo_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_nice_flo_free,void,void* +Function,-,subghz_protocol_decoder_nice_flo_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_nice_flo_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_nice_flo_reset,void,void* +Function,-,subghz_protocol_decoder_nice_flo_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_nice_flor_s_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_nice_flor_s_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_nice_flor_s_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_nice_flor_s_free,void,void* +Function,-,subghz_protocol_decoder_nice_flor_s_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_nice_flor_s_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_nice_flor_s_reset,void,void* +Function,-,subghz_protocol_decoder_nice_flor_s_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_phoenix_v2_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_phoenix_v2_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_phoenix_v2_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_phoenix_v2_free,void,void* +Function,-,subghz_protocol_decoder_phoenix_v2_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_phoenix_v2_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_phoenix_v2_reset,void,void* +Function,-,subghz_protocol_decoder_phoenix_v2_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_power_smart_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_power_smart_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_power_smart_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_power_smart_free,void,void* +Function,-,subghz_protocol_decoder_power_smart_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_power_smart_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_power_smart_reset,void,void* +Function,-,subghz_protocol_decoder_power_smart_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_protocol_decoder_princeton_alloc,void*,SubGhzEnvironment* +Function,+,subghz_protocol_decoder_princeton_deserialize,_Bool,"void*, FlipperFormat*" +Function,+,subghz_protocol_decoder_princeton_feed,void,"void*, _Bool, uint32_t" +Function,+,subghz_protocol_decoder_princeton_free,void,void* +Function,+,subghz_protocol_decoder_princeton_get_hash_data,uint8_t,void* +Function,+,subghz_protocol_decoder_princeton_get_string,void,"void*, FuriString*" +Function,+,subghz_protocol_decoder_princeton_reset,void,void* +Function,+,subghz_protocol_decoder_princeton_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_decoder_raw_deserialize,_Bool,"void*, FlipperFormat*" Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" Function,+,subghz_protocol_decoder_raw_free,void,void* -Function,+,subghz_protocol_decoder_raw_get_hash_data,uint8_t,void* Function,+,subghz_protocol_decoder_raw_get_string,void,"void*, FuriString*" Function,+,subghz_protocol_decoder_raw_reset,void,void* -Function,+,subghz_protocol_decoder_raw_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" -Function,+,subghz_protocol_decoder_raw_set_auto_mode,void,"void*, _Bool" -Function,+,subghz_protocol_decoder_raw_set_rssi_threshold,void,"void*, int" -Function,+,subghz_protocol_decoder_raw_write_data,_Bool,"void*, _Bool, uint32_t" -Function,-,subghz_protocol_encoder_get_rssi_threshold,int,void* +Function,-,subghz_protocol_decoder_scher_khan_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_scher_khan_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_scher_khan_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_scher_khan_free,void,void* +Function,-,subghz_protocol_decoder_scher_khan_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_scher_khan_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_scher_khan_reset,void,void* +Function,-,subghz_protocol_decoder_scher_khan_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_secplus_v1_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_secplus_v1_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_secplus_v1_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_secplus_v1_free,void,void* +Function,-,subghz_protocol_decoder_secplus_v1_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_secplus_v1_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_secplus_v1_reset,void,void* +Function,-,subghz_protocol_decoder_secplus_v1_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_secplus_v2_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_secplus_v2_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_secplus_v2_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_secplus_v2_free,void,void* +Function,-,subghz_protocol_decoder_secplus_v2_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_secplus_v2_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_secplus_v2_reset,void,void* +Function,-,subghz_protocol_decoder_secplus_v2_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_smc5326_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_smc5326_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_smc5326_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_smc5326_free,void,void* +Function,-,subghz_protocol_decoder_smc5326_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_smc5326_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_smc5326_reset,void,void* +Function,-,subghz_protocol_decoder_smc5326_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_somfy_keytis_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_somfy_keytis_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_somfy_keytis_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_somfy_keytis_free,void,void* +Function,-,subghz_protocol_decoder_somfy_keytis_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_somfy_keytis_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_somfy_keytis_reset,void,void* +Function,-,subghz_protocol_decoder_somfy_keytis_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_somfy_telis_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_somfy_telis_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_somfy_telis_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_somfy_telis_free,void,void* +Function,-,subghz_protocol_decoder_somfy_telis_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_somfy_telis_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_somfy_telis_reset,void,void* +Function,-,subghz_protocol_decoder_somfy_telis_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_decoder_star_line_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_decoder_star_line_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_decoder_star_line_feed,void,"void*, _Bool, uint32_t" +Function,-,subghz_protocol_decoder_star_line_free,void,void* +Function,-,subghz_protocol_decoder_star_line_get_hash_data,uint8_t,void* +Function,-,subghz_protocol_decoder_star_line_get_string,void,"void*, FuriString*" +Function,-,subghz_protocol_decoder_star_line_reset,void,void* +Function,-,subghz_protocol_decoder_star_line_serialize,_Bool,"void*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_protocol_encoder_ansonic_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_ansonic_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_ansonic_free,void,void* +Function,-,subghz_protocol_encoder_ansonic_stop,void,void* +Function,-,subghz_protocol_encoder_ansonic_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_bett_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_bett_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_bett_free,void,void* +Function,-,subghz_protocol_encoder_bett_stop,void,void* +Function,-,subghz_protocol_encoder_bett_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_bin_raw_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_bin_raw_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_bin_raw_free,void,void* +Function,-,subghz_protocol_encoder_bin_raw_stop,void,void* +Function,-,subghz_protocol_encoder_bin_raw_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_came_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_came_atomo_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_came_atomo_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_came_atomo_free,void,void* +Function,-,subghz_protocol_encoder_came_atomo_stop,void,void* +Function,-,subghz_protocol_encoder_came_atomo_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_came_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_came_free,void,void* +Function,-,subghz_protocol_encoder_came_stop,void,void* +Function,-,subghz_protocol_encoder_came_twee_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_came_twee_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_came_twee_free,void,void* +Function,-,subghz_protocol_encoder_came_twee_stop,void,void* +Function,-,subghz_protocol_encoder_came_twee_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_came_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_chamb_code_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_chamb_code_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_chamb_code_free,void,void* +Function,-,subghz_protocol_encoder_chamb_code_stop,void,void* +Function,-,subghz_protocol_encoder_chamb_code_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_clemsa_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_clemsa_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_clemsa_free,void,void* +Function,-,subghz_protocol_encoder_clemsa_stop,void,void* +Function,-,subghz_protocol_encoder_clemsa_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_doitrand_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_doitrand_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_doitrand_free,void,void* +Function,-,subghz_protocol_encoder_doitrand_stop,void,void* +Function,-,subghz_protocol_encoder_doitrand_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_dooya_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_dooya_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_dooya_free,void,void* +Function,-,subghz_protocol_encoder_dooya_stop,void,void* +Function,-,subghz_protocol_encoder_dooya_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_faac_slh_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_faac_slh_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_faac_slh_free,void,void* +Function,-,subghz_protocol_encoder_faac_slh_stop,void,void* +Function,-,subghz_protocol_encoder_faac_slh_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_gate_tx_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_gate_tx_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_gate_tx_free,void,void* +Function,-,subghz_protocol_encoder_gate_tx_stop,void,void* +Function,-,subghz_protocol_encoder_gate_tx_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_holtek_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_holtek_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_holtek_free,void,void* +Function,-,subghz_protocol_encoder_holtek_stop,void,void* +Function,-,subghz_protocol_encoder_holtek_th12x_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_holtek_th12x_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_holtek_th12x_free,void,void* +Function,-,subghz_protocol_encoder_holtek_th12x_stop,void,void* +Function,-,subghz_protocol_encoder_holtek_th12x_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_holtek_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_honeywell_wdb_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_honeywell_wdb_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_honeywell_wdb_free,void,void* +Function,-,subghz_protocol_encoder_honeywell_wdb_stop,void,void* +Function,-,subghz_protocol_encoder_honeywell_wdb_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_hormann_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_hormann_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_hormann_free,void,void* +Function,-,subghz_protocol_encoder_hormann_stop,void,void* +Function,-,subghz_protocol_encoder_hormann_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_intertechno_v3_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_intertechno_v3_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_intertechno_v3_free,void,void* +Function,-,subghz_protocol_encoder_intertechno_v3_stop,void,void* +Function,-,subghz_protocol_encoder_intertechno_v3_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_keeloq_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_keeloq_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_keeloq_free,void,void* +Function,-,subghz_protocol_encoder_keeloq_stop,void,void* +Function,-,subghz_protocol_encoder_keeloq_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_kinggates_stylo_4k_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_kinggates_stylo_4k_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_kinggates_stylo_4k_free,void,void* +Function,-,subghz_protocol_encoder_kinggates_stylo_4k_stop,void,void* +Function,-,subghz_protocol_encoder_kinggates_stylo_4k_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_linear_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_linear_delta3_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_linear_delta3_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_linear_delta3_free,void,void* +Function,-,subghz_protocol_encoder_linear_delta3_stop,void,void* +Function,-,subghz_protocol_encoder_linear_delta3_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_linear_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_linear_free,void,void* +Function,-,subghz_protocol_encoder_linear_stop,void,void* +Function,-,subghz_protocol_encoder_linear_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_magellan_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_magellan_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_magellan_free,void,void* +Function,-,subghz_protocol_encoder_magellan_stop,void,void* +Function,-,subghz_protocol_encoder_magellan_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_marantec_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_marantec_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_marantec_free,void,void* +Function,-,subghz_protocol_encoder_marantec_stop,void,void* +Function,-,subghz_protocol_encoder_marantec_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_megacode_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_megacode_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_megacode_free,void,void* +Function,-,subghz_protocol_encoder_megacode_stop,void,void* +Function,-,subghz_protocol_encoder_megacode_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_nero_radio_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_nero_radio_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_nero_radio_free,void,void* +Function,-,subghz_protocol_encoder_nero_radio_stop,void,void* +Function,-,subghz_protocol_encoder_nero_radio_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_nero_sketch_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_nero_sketch_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_nero_sketch_free,void,void* +Function,-,subghz_protocol_encoder_nero_sketch_stop,void,void* +Function,-,subghz_protocol_encoder_nero_sketch_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_nice_flo_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_nice_flo_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_nice_flo_free,void,void* +Function,-,subghz_protocol_encoder_nice_flo_stop,void,void* +Function,-,subghz_protocol_encoder_nice_flo_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_nice_flor_s_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_nice_flor_s_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_nice_flor_s_free,void,void* +Function,-,subghz_protocol_encoder_nice_flor_s_stop,void,void* +Function,-,subghz_protocol_encoder_nice_flor_s_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_phoenix_v2_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_phoenix_v2_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_phoenix_v2_free,void,void* +Function,-,subghz_protocol_encoder_phoenix_v2_stop,void,void* +Function,-,subghz_protocol_encoder_phoenix_v2_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_power_smart_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_power_smart_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_power_smart_free,void,void* +Function,-,subghz_protocol_encoder_power_smart_stop,void,void* +Function,-,subghz_protocol_encoder_power_smart_yield,LevelDuration,void* +Function,+,subghz_protocol_encoder_princeton_alloc,void*,SubGhzEnvironment* +Function,+,subghz_protocol_encoder_princeton_deserialize,_Bool,"void*, FlipperFormat*" +Function,+,subghz_protocol_encoder_princeton_free,void,void* +Function,+,subghz_protocol_encoder_princeton_stop,void,void* +Function,+,subghz_protocol_encoder_princeton_yield,LevelDuration,void* Function,+,subghz_protocol_encoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_encoder_raw_deserialize,_Bool,"void*, FlipperFormat*" Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_secplus_v1_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_secplus_v1_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_secplus_v1_free,void,void* +Function,-,subghz_protocol_encoder_secplus_v1_stop,void,void* +Function,-,subghz_protocol_encoder_secplus_v1_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_secplus_v2_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_secplus_v2_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_secplus_v2_free,void,void* +Function,-,subghz_protocol_encoder_secplus_v2_stop,void,void* +Function,-,subghz_protocol_encoder_secplus_v2_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_smc5326_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_smc5326_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_smc5326_free,void,void* +Function,-,subghz_protocol_encoder_smc5326_stop,void,void* +Function,-,subghz_protocol_encoder_smc5326_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_somfy_keytis_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_somfy_keytis_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_somfy_keytis_free,void,void* +Function,-,subghz_protocol_encoder_somfy_keytis_stop,void,void* +Function,-,subghz_protocol_encoder_somfy_keytis_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_somfy_telis_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_somfy_telis_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_somfy_telis_free,void,void* +Function,-,subghz_protocol_encoder_somfy_telis_stop,void,void* +Function,-,subghz_protocol_encoder_somfy_telis_yield,LevelDuration,void* +Function,-,subghz_protocol_encoder_star_line_alloc,void*,SubGhzEnvironment* +Function,-,subghz_protocol_encoder_star_line_deserialize,_Bool,"void*, FlipperFormat*" +Function,-,subghz_protocol_encoder_star_line_free,void,void* +Function,-,subghz_protocol_encoder_star_line_stop,void,void* +Function,-,subghz_protocol_encoder_star_line_yield,LevelDuration,void* +Function,-,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, uint32_t, const char*, SubGhzRadioPreset*" +Function,-,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" +Function,-,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" +Function,-,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" +Function,-,subghz_protocol_nice_flor_s_encrypt,uint64_t,"uint64_t, const char*" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* @@ -2717,10 +3244,14 @@ Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_registry_count,size_t,const SubGhzProtocolRegistry* Function,+,subghz_protocol_registry_get_by_index,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, size_t" Function,+,subghz_protocol_registry_get_by_name,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, const char*" +Function,-,subghz_protocol_secplus_v1_check_fixed,_Bool,uint32_t +Function,-,subghz_protocol_secplus_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, SubGhzRadioPreset*" +Function,-,subghz_protocol_somfy_keytis_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" +Function,-,subghz_protocol_somfy_telis_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" +Function,-,subghz_protocol_star_line_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* -Function,+,subghz_receiver_get_filter,SubGhzProtocolFlag,SubGhzReceiver* Function,+,subghz_receiver_reset,void,SubGhzReceiver* Function,+,subghz_receiver_search_decoder_base_by_name,SubGhzProtocolDecoderBase*,"SubGhzReceiver*, const char*" Function,+,subghz_receiver_set_filter,void,"SubGhzReceiver*, SubGhzProtocolFlag" @@ -4178,12 +4709,15 @@ Variable,+,furi_hal_spi_bus_handle_nfc,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_subghz,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_subghz_ext,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_subghz_int,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_4m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_8m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_2edge_low_8m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_subghz,volatile FuriHalSubGhz, Variable,+,gpio_button_back,const GpioPin, Variable,+,gpio_button_down,const GpioPin, Variable,+,gpio_button_left,const GpioPin, @@ -4191,6 +4725,7 @@ Variable,+,gpio_button_ok,const GpioPin, Variable,+,gpio_button_right,const GpioPin, Variable,+,gpio_button_up,const GpioPin, Variable,+,gpio_cc1101_g0,const GpioPin, +Variable,+,gpio_cc1101_g0_ext,const GpioPin, Variable,+,gpio_display_cs,const GpioPin, Variable,+,gpio_display_di,const GpioPin, Variable,+,gpio_display_rst_n,const GpioPin, @@ -4221,9 +4756,13 @@ Variable,+,gpio_spi_d_miso,const GpioPin, Variable,+,gpio_spi_d_mosi,const GpioPin, Variable,+,gpio_spi_d_sck,const GpioPin, Variable,+,gpio_spi_r_miso,const GpioPin, +Variable,+,gpio_spi_r_miso_ext,const GpioPin, Variable,+,gpio_spi_r_mosi,const GpioPin, +Variable,+,gpio_spi_r_mosi_ext,const GpioPin, Variable,+,gpio_spi_r_sck,const GpioPin, +Variable,+,gpio_spi_r_sck_ext,const GpioPin, Variable,+,gpio_subghz_cs,const GpioPin, +Variable,+,gpio_subghz_cs_ext,const GpioPin, Variable,+,gpio_usart_rx,const GpioPin, Variable,+,gpio_usart_tx,const GpioPin, Variable,+,gpio_usb_dm,const GpioPin, @@ -4426,9 +4965,133 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,-,subghz_protocol_alutech_at_4n,const SubGhzProtocol, +Variable,-,subghz_protocol_alutech_at_4n_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_alutech_at_4n_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_ansonic,const SubGhzProtocol, +Variable,-,subghz_protocol_ansonic_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_ansonic_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_bett,const SubGhzProtocol, +Variable,-,subghz_protocol_bett_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_bett_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_bin_raw,const SubGhzProtocol, +Variable,-,subghz_protocol_bin_raw_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_bin_raw_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_came,const SubGhzProtocol, +Variable,-,subghz_protocol_came_atomo,const SubGhzProtocol, +Variable,-,subghz_protocol_came_atomo_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_came_atomo_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_came_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_came_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_came_twee,const SubGhzProtocol, +Variable,-,subghz_protocol_came_twee_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_came_twee_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_chamb_code,const SubGhzProtocol, +Variable,-,subghz_protocol_chamb_code_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_chamb_code_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_clemsa,const SubGhzProtocol, +Variable,-,subghz_protocol_clemsa_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_clemsa_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_doitrand,const SubGhzProtocol, +Variable,-,subghz_protocol_doitrand_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_doitrand_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_dooya,const SubGhzProtocol, +Variable,-,subghz_protocol_dooya_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_dooya_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_faac_slh,const SubGhzProtocol, +Variable,-,subghz_protocol_faac_slh_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_faac_slh_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_gate_tx,const SubGhzProtocol, +Variable,-,subghz_protocol_gate_tx_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_gate_tx_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_holtek,const SubGhzProtocol, +Variable,-,subghz_protocol_holtek_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_holtek_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_holtek_th12x,const SubGhzProtocol, +Variable,-,subghz_protocol_holtek_th12x_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_holtek_th12x_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_honeywell_wdb,const SubGhzProtocol, +Variable,-,subghz_protocol_honeywell_wdb_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_honeywell_wdb_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_hormann,const SubGhzProtocol, +Variable,-,subghz_protocol_hormann_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_hormann_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_ido,const SubGhzProtocol, +Variable,-,subghz_protocol_ido_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_ido_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_intertechno_v3,const SubGhzProtocol, +Variable,-,subghz_protocol_intertechno_v3_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_intertechno_v3_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_keeloq,const SubGhzProtocol, +Variable,-,subghz_protocol_keeloq_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_keeloq_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_kia,const SubGhzProtocol, +Variable,-,subghz_protocol_kia_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_kia_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_kinggates_stylo_4k,const SubGhzProtocol, +Variable,-,subghz_protocol_kinggates_stylo_4k_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_kinggates_stylo_4k_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_linear,const SubGhzProtocol, +Variable,-,subghz_protocol_linear_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_linear_delta3,const SubGhzProtocol, +Variable,-,subghz_protocol_linear_delta3_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_linear_delta3_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_linear_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_magellan,const SubGhzProtocol, +Variable,-,subghz_protocol_magellan_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_magellan_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_marantec,const SubGhzProtocol, +Variable,-,subghz_protocol_marantec_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_marantec_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_megacode,const SubGhzProtocol, +Variable,-,subghz_protocol_megacode_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_megacode_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_nero_radio,const SubGhzProtocol, +Variable,-,subghz_protocol_nero_radio_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_nero_radio_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_nero_sketch,const SubGhzProtocol, +Variable,-,subghz_protocol_nero_sketch_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_nero_sketch_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_nice_flo,const SubGhzProtocol, +Variable,-,subghz_protocol_nice_flo_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_nice_flo_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_nice_flor_s,const SubGhzProtocol, +Variable,-,subghz_protocol_nice_flor_s_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_nice_flor_s_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_phoenix_v2,const SubGhzProtocol, +Variable,-,subghz_protocol_phoenix_v2_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_phoenix_v2_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_power_smart,const SubGhzProtocol, +Variable,-,subghz_protocol_power_smart_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_power_smart_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_princeton,const SubGhzProtocol, +Variable,-,subghz_protocol_princeton_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_princeton_encoder,const SubGhzProtocolEncoder, Variable,+,subghz_protocol_raw,const SubGhzProtocol, Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_registry,const SubGhzProtocolRegistry, +Variable,-,subghz_protocol_scher_khan,const SubGhzProtocol, +Variable,-,subghz_protocol_scher_khan_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_scher_khan_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_secplus_v1,const SubGhzProtocol, +Variable,-,subghz_protocol_secplus_v1_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_secplus_v1_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_secplus_v2,const SubGhzProtocol, +Variable,-,subghz_protocol_secplus_v2_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_secplus_v2_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_smc5326,const SubGhzProtocol, +Variable,-,subghz_protocol_smc5326_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_smc5326_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_somfy_keytis,const SubGhzProtocol, +Variable,-,subghz_protocol_somfy_keytis_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_somfy_keytis_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_somfy_telis,const SubGhzProtocol, +Variable,-,subghz_protocol_somfy_telis_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_somfy_telis_encoder,const SubGhzProtocolEncoder, +Variable,-,subghz_protocol_star_line,const SubGhzProtocol, +Variable,-,subghz_protocol_star_line_decoder,const SubGhzProtocolDecoder, +Variable,-,subghz_protocol_star_line_encoder,const SubGhzProtocolEncoder, Variable,-,suboptarg,char*, Variable,-,u8g2_cb_mirror,const u8g2_cb_t, Variable,-,u8g2_cb_r0,const u8g2_cb_t, diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 03cc6e714..4e63263cf 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -8,9 +8,11 @@ const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; const GpioPin gpio_cc1101_g0 = {.port = CC1101_G0_GPIO_Port, .pin = CC1101_G0_Pin}; +const GpioPin gpio_cc1101_g0_ext = {.port = GPIOB, .pin = LL_GPIO_PIN_2}; const GpioPin gpio_rf_sw_0 = {.port = RF_SW_0_GPIO_Port, .pin = RF_SW_0_Pin}; const GpioPin gpio_subghz_cs = {.port = CC1101_CS_GPIO_Port, .pin = CC1101_CS_Pin}; +const GpioPin gpio_subghz_cs_ext = {.port = GPIOA, .pin = LL_GPIO_PIN_4}; const GpioPin gpio_display_cs = {.port = DISPLAY_CS_GPIO_Port, .pin = DISPLAY_CS_Pin}; const GpioPin gpio_display_rst_n = {.port = DISPLAY_RST_GPIO_Port, .pin = DISPLAY_RST_Pin}; const GpioPin gpio_display_di = {.port = DISPLAY_DI_GPIO_Port, .pin = DISPLAY_DI_Pin}; @@ -31,6 +33,9 @@ const GpioPin gpio_spi_d_sck = {.port = SPI_D_SCK_GPIO_Port, .pin = SPI_D_SCK_Pi const GpioPin gpio_spi_r_miso = {.port = SPI_R_MISO_GPIO_Port, .pin = SPI_R_MISO_Pin}; const GpioPin gpio_spi_r_mosi = {.port = SPI_R_MOSI_GPIO_Port, .pin = SPI_R_MOSI_Pin}; const GpioPin gpio_spi_r_sck = {.port = SPI_R_SCK_GPIO_Port, .pin = SPI_R_SCK_Pin}; +const GpioPin gpio_spi_r_miso_ext = {.port = GPIOA, .pin = LL_GPIO_PIN_6}; +const GpioPin gpio_spi_r_mosi_ext = {.port = GPIOA, .pin = LL_GPIO_PIN_7}; +const GpioPin gpio_spi_r_sck_ext = {.port = GPIOB, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_ext_pc0 = {.port = GPIOC, .pin = LL_GPIO_PIN_0}; const GpioPin gpio_ext_pc1 = {.port = GPIOC, .pin = LL_GPIO_PIN_1}; @@ -191,3 +196,26 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); } + +int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { + if(gpio == &gpio_ext_pa7) + return 2; + else if(gpio == &gpio_ext_pa6) + return 3; + else if(gpio == &gpio_ext_pa4) + return 4; + else if(gpio == &gpio_ext_pb3) + return 5; + else if(gpio == &gpio_ext_pb2) + return 6; + else if(gpio == &gpio_ext_pc3) + return 7; + else if(gpio == &gpio_ext_pc1) + return 15; + else if(gpio == &gpio_ext_pc0) + return 16; + else if(gpio == &ibutton_gpio) + return 17; + else + return -1; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 51ea7bcc1..33e2a3289 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -54,9 +54,11 @@ extern const GpioPin vibro_gpio; extern const GpioPin ibutton_gpio; extern const GpioPin gpio_cc1101_g0; +extern const GpioPin gpio_cc1101_g0_ext; extern const GpioPin gpio_rf_sw_0; extern const GpioPin gpio_subghz_cs; +extern const GpioPin gpio_subghz_cs_ext; extern const GpioPin gpio_display_cs; extern const GpioPin gpio_display_rst_n; extern const GpioPin gpio_display_di; @@ -77,6 +79,9 @@ extern const GpioPin gpio_spi_d_sck; extern const GpioPin gpio_spi_r_miso; extern const GpioPin gpio_spi_r_mosi; extern const GpioPin gpio_spi_r_sck; +extern const GpioPin gpio_spi_r_miso_ext; +extern const GpioPin gpio_spi_r_mosi_ext; +extern const GpioPin gpio_spi_r_sck_ext; extern const GpioPin gpio_ext_pc0; extern const GpioPin gpio_ext_pc1; @@ -216,6 +221,13 @@ void furi_hal_resources_deinit_early(); void furi_hal_resources_init(); +/** + * Get a corresponding external connector pin number for a gpio + * @param gpio GpioPin + * @return pin number or -1 if gpio is not on the external connector + */ +int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 9cf332dac..695418af1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -2,6 +2,7 @@ #include #include #include +#include #define TAG "FuriHalSpiConfig" @@ -89,7 +90,7 @@ void furi_hal_spi_config_deinit_early() { void furi_hal_spi_config_init() { furi_hal_spi_bus_init(&furi_hal_spi_bus_r); - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_bus_handle_init(furi_hal_subghz.spi_bus_handle); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow); @@ -285,6 +286,15 @@ static void furi_hal_spi_bus_handle_subghz_event_callback( furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_8m); } +FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_int = { + .bus = &furi_hal_spi_bus_r, + .callback = furi_hal_spi_bus_handle_subghz_event_callback, + .miso = &gpio_spi_r_miso, + .mosi = &gpio_spi_r_mosi, + .sck = &gpio_spi_r_sck, + .cs = &gpio_subghz_cs, +}; + FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_subghz_event_callback, @@ -294,6 +304,15 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { .cs = &gpio_subghz_cs, }; +FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_ext = { + .bus = &furi_hal_spi_bus_r, + .callback = furi_hal_spi_bus_handle_subghz_event_callback, + .miso = &gpio_ext_pa6, + .mosi = &gpio_ext_pa7, + .sck = &gpio_ext_pb3, + .cs = &gpio_ext_pa4, +}; + static void furi_hal_spi_bus_handle_nfc_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.h b/firmware/targets/f7/furi_hal/furi_hal_spi_config.h index eab633a19..8ea138bdc 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.h +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.h @@ -27,8 +27,12 @@ extern FuriHalSpiBus furi_hal_spi_bus_r; /** Furi Hal Spi Bus D (Display, SdCard) */ extern FuriHalSpiBus furi_hal_spi_bus_d; -/** CC1101 on `furi_hal_spi_bus_r` */ +/** CC1101 on current SPI bus */ extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; +/** CC1101 on `furi_hal_spi_bus_r` */ +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_int; +/** CC1101 on external `furi_hal_spi_bus_r` */ +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_ext; /** ST25R3916 on `furi_hal_spi_bus_r` */ extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index ccea7728d..c2c238a13 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -1,111 +1,204 @@ #include #include -#include #include #include #include #include #include - -#include +#include #include +#include + #include #include #include #define TAG "FuriHalSubGhz" +//Initialisation timeout (ms) +#define INIT_TIMEOUT 10 static uint32_t furi_hal_subghz_debug_gpio_buff[2]; +static bool last_OTG_state = false; -typedef struct { - volatile SubGhzState state; - volatile SubGhzRegulation regulation; - volatile FuriHalSubGhzPreset preset; - const GpioPin* async_mirror_pin; -} FuriHalSubGhz; +/* DMA Channels definition */ +#define SUBGHZ_DMA DMA2 +#define SUBGHZ_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 +#define SUBGHZ_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 +#define SUBGHZ_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define SUBGHZ_DMA_CH1_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH1_CHANNEL +#define SUBGHZ_DMA_CH2_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH2_CHANNEL volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, .preset = FuriHalSubGhzPresetIDLE, .async_mirror_pin = NULL, + .radio_type = SubGhzRadioInternal, + .spi_bus_handle = &furi_hal_spi_bus_handle_subghz, + .cc1101_g0_pin = &gpio_cc1101_g0, }; +bool furi_hal_subghz_set_radio_type(SubGhzRadioType state) { + furi_hal_subghz.radio_type = state; + furi_hal_spi_bus_handle_deinit(furi_hal_subghz.spi_bus_handle); + if(state) { + furi_hal_subghz.spi_bus_handle = &furi_hal_spi_bus_handle_subghz_ext; + furi_hal_subghz.cc1101_g0_pin = &gpio_cc1101_g0_ext; + } else { + furi_hal_subghz.spi_bus_handle = &furi_hal_spi_bus_handle_subghz; + furi_hal_subghz.cc1101_g0_pin = &gpio_cc1101_g0; + } + furi_hal_spi_bus_handle_init(furi_hal_subghz.spi_bus_handle); + furi_hal_subghz_init_check(); + return true; +} + +SubGhzRadioType furi_hal_subghz_get_radio_type(void) { + return furi_hal_subghz.radio_type; +} + void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } -void furi_hal_subghz_init() { +void furi_hal_subghz_init(void) { + furi_hal_subghz_init_check(); +} + +void furi_hal_subghz_enable_ext_power(void) { + if(furi_hal_subghz.radio_type != SubGhzRadioInternal && !furi_hal_power_is_otg_enabled()) { + furi_hal_power_enable_otg(); + } +} + +void furi_hal_subghz_disable_ext_power(void) { + if(furi_hal_subghz.radio_type != SubGhzRadioInternal && !last_OTG_state) { + furi_hal_power_disable_otg(); + } +} + +bool furi_hal_subghz_check_radio(void) { + bool result = true; + + furi_hal_subghz_enable_ext_power(); + + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + uint8_t ver = cc1101_get_version(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + + if((ver != 0) && (ver != 255)) { + FURI_LOG_D(TAG, "Radio check ok"); + } else { + FURI_LOG_D(TAG, "Radio check failed"); + furi_hal_subghz_disable_ext_power(); + result = false; + } + return result; +} + +bool furi_hal_subghz_init_check(void) { + bool result = true; + furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + last_OTG_state = furi_hal_power_is_otg_enabled(); + furi_hal_subghz_enable_ext_power(); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); #ifdef FURI_HAL_SUBGHZ_TX_GPIO furi_hal_gpio_init(&FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); #endif // Reset - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_reset(furi_hal_subghz.spi_bus_handle); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); // Prepare GD0 for power on self test - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { + // GD0 low + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + uint32_t test_start_time = furi_get_tick(); + while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin) != false && result) { + if(furi_get_tick() - test_start_time > INIT_TIMEOUT) { + result = false; + } + } - // GD0 low - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); - while(furi_hal_gpio_read(&gpio_cc1101_g0) != false) - ; - - // GD0 high - cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); - while(furi_hal_gpio_read(&gpio_cc1101_g0) != true) - ; + // GD0 high + cc1101_write_reg( + furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + test_start_time = furi_get_tick(); + while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin) != true && result) { + if(furi_get_tick() - test_start_time > INIT_TIMEOUT) { + result = false; + } + } + } else { + // GD0 low + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != false) + ; + // GD0 high + cc1101_write_reg( + furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != true) + ; + } // Reset GD0 to floating state - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); // RF switches furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); // Go to sleep - cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + cc1101_shutdown(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - FURI_LOG_I(TAG, "Init OK"); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + + if(result) { + FURI_LOG_I(TAG, "Init OK"); + } else { + FURI_LOG_E(TAG, "Failed to initialization"); + furi_hal_subghz_disable_ext_power(); + } + return result; } void furi_hal_subghz_sleep() { furi_assert(furi_hal_subghz.state == SubGhzStateIdle); - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + cc1101_shutdown(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + + furi_hal_subghz_disable_ext_power(); furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; } void furi_hal_subghz_dump_state() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); printf( "[furi_hal_subghz] cc1101 chip %d, version %d\r\n", - cc1101_get_partnumber(&furi_hal_spi_bus_handle_subghz), - cc1101_get_version(&furi_hal_spi_bus_handle_subghz)); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_get_partnumber(furi_hal_subghz.spi_bus_handle), + cc1101_get_version(furi_hal_subghz.spi_bus_handle)); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { @@ -137,15 +230,15 @@ void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { //load config - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_reset(furi_hal_subghz.spi_bus_handle); uint32_t i = 0; uint8_t pa[8] = {0}; while(preset_data[i]) { - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, preset_data[i], preset_data[i + 1]); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, preset_data[i], preset_data[i + 1]); i += 2; } - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); //load pa table memcpy(&pa[0], &preset_data[i + 2], 8); @@ -167,48 +260,48 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { } void furi_hal_subghz_load_registers(uint8_t* data) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_reset(furi_hal_subghz.spi_bus_handle); uint32_t i = 0; while(data[i]) { - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i], data[i + 1]); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, data[i], data[i + 1]); i += 2; } - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_load_patable(const uint8_t data[8]) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_set_pa_table(&furi_hal_spi_bus_handle_subghz, data); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_set_pa_table(furi_hal_subghz.spi_bus_handle, data); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_write_packet(const uint8_t* data, uint8_t size) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_FIFO, size); - cc1101_write_fifo(&furi_hal_spi_bus_handle_subghz, data, size); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_flush_tx(furi_hal_subghz.spi_bus_handle); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_FIFO, size); + cc1101_write_fifo(furi_hal_subghz.spi_bus_handle, data, size); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_flush_rx() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_flush_rx(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_flush_tx() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_flush_tx(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } bool furi_hal_subghz_rx_pipe_not_empty() { CC1101RxBytes status[1]; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); cc1101_read_reg( - &furi_hal_spi_bus_handle_subghz, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_subghz.spi_bus_handle, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); // TODO: you can add a buffer overflow flag if needed if(status->NUM_RXBYTES > 0) { return true; @@ -218,10 +311,10 @@ bool furi_hal_subghz_rx_pipe_not_empty() { } bool furi_hal_subghz_is_rx_data_crc_valid() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); uint8_t data[1]; - cc1101_read_reg(&furi_hal_spi_bus_handle_subghz, CC1101_STATUS_LQI | CC1101_BURST, data); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_read_reg(furi_hal_subghz.spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); if(((data[0] >> 7) & 0x01)) { return true; } else { @@ -230,51 +323,52 @@ bool furi_hal_subghz_is_rx_data_crc_valid() { } void furi_hal_subghz_read_packet(uint8_t* data, uint8_t* size) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_read_fifo(&furi_hal_spi_bus_handle_subghz, data, size); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_read_fifo(furi_hal_subghz.spi_bus_handle, data, size); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_shutdown() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); // Reset and shutdown - cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_shutdown(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_subghz_disable_ext_power(); } void furi_hal_subghz_reset() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); + cc1101_reset(furi_hal_subghz.spi_bus_handle); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_idle() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } void furi_hal_subghz_rx() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_rx(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } bool furi_hal_subghz_tx() { - // if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_tx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false; + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_tx(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); return true; } float furi_hal_subghz_get_rssi() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - int32_t rssi_dec = cc1101_get_rssi(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + int32_t rssi_dec = cc1101_get_rssi(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); float rssi = rssi_dec; if(rssi_dec >= 128) { @@ -287,18 +381,18 @@ float furi_hal_subghz_get_rssi() { } uint8_t furi_hal_subghz_get_lqi() { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); uint8_t data[1]; - cc1101_read_reg(&furi_hal_spi_bus_handle_subghz, CC1101_STATUS_LQI | CC1101_BURST, data); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_read_reg(furi_hal_subghz.spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); return data[0] & 0x7F; } /* Modified by @tkerby & MX to the full YARD Stick One extended range of 281-361 MHz, 378-481 MHz, and 749-962 MHz. - These changes are at your own risk. The PLL may not lock and FZ devs have warned of possible damage - Set flag use_ext_range_at_own_risk in extend_range.txt to use + These changes are at your own risk. The PLL may not lock and FZ devs have warned of possible damage! */ + bool furi_hal_subghz_is_frequency_valid(uint32_t value) { if(!(value >= 281000000 && value <= 361000000) && !(value >= 378000000 && value <= 481000000) && @@ -325,113 +419,72 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { } bool furi_hal_subghz_is_tx_allowed(uint32_t value) { - //checking regional settings bool is_extended = false; - bool is_allowed = false; + // TODO: !!! Move file check to another place Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/extend_range.txt")) { - flipper_format_read_bool(fff_data_file, "use_ext_range_at_own_risk", &is_extended, 1); - flipper_format_read_bool(fff_data_file, "ignore_default_tx_region", &is_allowed, 1); + + if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { + flipper_format_read_bool( + fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended, 1); } + flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); - switch(furi_hal_version_get_hw_region()) { - case FuriHalVersionRegionEuRu: - //433,05..434,79; 868,15..868,55 - if(!(value >= 433050000 && value <= 434790000) && - !(value >= 868150000 && value <= 868550000)) { - } else { - is_allowed = true; - } - break; - case FuriHalVersionRegionUsCaAu: - //304,10..321,95; 433,05..434,79; 915,00..928,00 - if(!(value >= 304100000 && value <= 321950000) && - !(value >= 433050000 && value <= 434790000) && - !(value >= 915000000 && value <= 928000000)) { - } else { - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - if(value <= 321950000 && - ((furi_hal_subghz.preset == FuriHalSubGhzPresetOok270Async) || - (furi_hal_subghz.preset == FuriHalSubGhzPresetOok650Async))) { - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable_au); - } - } - is_allowed = true; - } - break; - case FuriHalVersionRegionJp: - //312,00..315,25; 920,50..923,50 - if(!(value >= 312000000 && value <= 315250000) && - !(value >= 920500000 && value <= 923500000)) { - } else { - is_allowed = true; - } - break; - - default: - is_allowed = true; - break; - } - // No flag - test original range, flag set, test extended range - if(!(value >= 299999755 && value <= 348000335) && + if(!(value >= 299999755 && value <= 350000335) && !(value >= 386999938 && value <= 464000000) && !(value >= 778999847 && value <= 928000000) && !(is_extended)) { - FURI_LOG_I(TAG, "Frequency blocked - outside standard range"); - is_allowed = false; + FURI_LOG_I(TAG, "Frequency blocked - outside default range"); + return false; } else if( !(value >= 281000000 && value <= 361000000) && !(value >= 378000000 && value <= 481000000) && !(value >= 749000000 && value <= 962000000) && is_extended) { - FURI_LOG_I(TAG, "Frequency blocked - outside extended range"); - is_allowed = false; + FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); + return false; } - return is_allowed; + + return true; } uint32_t furi_hal_subghz_set_frequency(uint32_t value) { - if(furi_hal_region_is_frequency_allowed(value)) { - furi_hal_subghz.regulation = SubGhzRegulationTxRx; - } else { - furi_hal_subghz.regulation = SubGhzRegulationTxRx; - } + furi_hal_subghz.regulation = SubGhzRegulationTxRx; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - uint32_t real_frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, value); - cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + uint32_t real_frequency = cc1101_set_frequency(furi_hal_subghz.spi_bus_handle, value); + cc1101_calibrate(furi_hal_subghz.spi_bus_handle); while(true) { - CC1101Status status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + CC1101Status status = cc1101_get_status(furi_hal_subghz.spi_bus_handle); if(status.STATE == CC1101StateIDLE) break; } - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); return real_frequency; } void furi_hal_subghz_set_path(FuriHalSubGhzPath path) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); if(path == FuriHalSubGhzPath433) { furi_hal_gpio_write(&gpio_rf_sw_0, 0); cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); } else if(path == FuriHalSubGhzPath315) { furi_hal_gpio_write(&gpio_rf_sw_0, 1); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); } else if(path == FuriHalSubGhzPath868) { furi_hal_gpio_write(&gpio_rf_sw_0, 1); cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); } else if(path == FuriHalSubGhzPathIsolate) { furi_hal_gpio_write(&gpio_rf_sw_0, 0); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); } else { furi_crash("SubGhz: Incorrect path during set."); } - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); } static bool furi_hal_subghz_start_debug() { @@ -462,31 +515,53 @@ volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL; volatile void* furi_hal_subghz_capture_callback_context = NULL; static void furi_hal_subghz_capture_ISR() { - // Channel 1 - if(LL_TIM_IsActiveFlag_CC1(TIM2)) { - LL_TIM_ClearFlag_CC1(TIM2); - furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2); - if(furi_hal_subghz_capture_callback) { - if(furi_hal_subghz.async_mirror_pin != NULL) - furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); + if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { + if(!furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin)) { + if(furi_hal_subghz_capture_callback) { + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); - furi_hal_subghz_capture_callback( - true, - furi_hal_subghz_capture_delta_duration, - (void*)furi_hal_subghz_capture_callback_context); + furi_hal_subghz_capture_callback( + true, TIM2->CNT, (void*)furi_hal_subghz_capture_callback_context); + } + } else { + if(furi_hal_subghz_capture_callback) { + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true); + + furi_hal_subghz_capture_callback( + false, TIM2->CNT, (void*)furi_hal_subghz_capture_callback_context); + } } - } - // Channel 2 - if(LL_TIM_IsActiveFlag_CC2(TIM2)) { - LL_TIM_ClearFlag_CC2(TIM2); - if(furi_hal_subghz_capture_callback) { - if(furi_hal_subghz.async_mirror_pin != NULL) - furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true); + //Forced correction for improved accuracy + TIM2->CNT = 9; + } else { + // Channel 1 + if(LL_TIM_IsActiveFlag_CC1(TIM2)) { + LL_TIM_ClearFlag_CC1(TIM2); + furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2); + if(furi_hal_subghz_capture_callback) { + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); - furi_hal_subghz_capture_callback( - false, - LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration, - (void*)furi_hal_subghz_capture_callback_context); + furi_hal_subghz_capture_callback( + true, + furi_hal_subghz_capture_delta_duration, + (void*)furi_hal_subghz_capture_callback_context); + } + } + // Channel 2 + if(LL_TIM_IsActiveFlag_CC2(TIM2)) { + LL_TIM_ClearFlag_CC2(TIM2); + if(furi_hal_subghz_capture_callback) { + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true); + + furi_hal_subghz_capture_callback( + false, + LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration, + (void*)furi_hal_subghz_capture_callback_context); + } } } } @@ -498,47 +573,64 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* furi_hal_subghz_capture_callback = callback; furi_hal_subghz_capture_callback_context = context; - furi_hal_gpio_init_ex( - &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, + GpioModeInterruptRiseFall, + GpioPullUp, + GpioSpeedVeryHigh); + furi_hal_gpio_add_int_callback( + furi_hal_subghz.cc1101_g0_pin, + furi_hal_subghz_capture_ISR, + furi_hal_subghz_capture_callback); + furi_hal_gpio_enable_int_callback(furi_hal_subghz.cc1101_g0_pin); + } else { + furi_hal_gpio_init_ex( + &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + } // Timer: base LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 0x7FFFFFFE; - TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; // Clock division for capture filter LL_TIM_Init(TIM2, &TIM_InitStruct); // Timer: advanced LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); LL_TIM_DisableARRPreload(TIM2); - LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI2FP2); - LL_TIM_SetSlaveMode(TIM2, LL_TIM_SLAVEMODE_RESET); - LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET); - LL_TIM_EnableMasterSlaveMode(TIM2); + if(furi_hal_subghz.radio_type == SubGhzRadioInternal) { + LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI2FP2); + LL_TIM_SetSlaveMode(TIM2, LL_TIM_SLAVEMODE_RESET); + LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET); + LL_TIM_EnableMasterSlaveMode(TIM2); + } LL_TIM_DisableDMAReq_TRIG(TIM2); LL_TIM_DisableIT_TRIG(TIM2); - // Timer: channel 1 indirect - LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI); - LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); - LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_FALLING); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); + if(furi_hal_subghz.radio_type == SubGhzRadioInternal) { + // Timer: channel 1 indirect + LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI); + LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_FALLING); + LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); - // Timer: channel 2 direct - LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI); - LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); - LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV32_N8); + // Timer: channel 2 direct + LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV32_N8); - // ISR setup - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_capture_ISR, NULL); + // ISR setup + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_capture_ISR, NULL); - // Interrupts and channels - LL_TIM_EnableIT_CC1(TIM2); - LL_TIM_EnableIT_CC2(TIM2); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); + // Interrupts and channels + LL_TIM_EnableIT_CC1(TIM2); + LL_TIM_EnableIT_CC2(TIM2); + LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); + } // Start timer LL_TIM_SetCounter(TIM2, 0); @@ -549,6 +641,9 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* // Switch to RX furi_hal_subghz_rx(); + + // Clear the variable after the end of the session + furi_hal_subghz_capture_delta_duration = 0; } void furi_hal_subghz_stop_async_rx() { @@ -565,9 +660,11 @@ void furi_hal_subghz_stop_async_rx() { furi_hal_subghz_stop_debug(); FURI_CRITICAL_EXIT(); - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); + if(furi_hal_subghz.radio_type == SubGhzRadioInternal) { + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); + } - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } typedef struct { @@ -601,8 +698,8 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { *buffer = 0; buffer++; samples--; - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableIT_HT(SUBGHZ_DMA_CH1_DEF); + LL_DMA_DisableIT_TC(SUBGHZ_DMA_CH1_DEF); LL_TIM_EnableIT_UPDATE(TIM2); break; } else { @@ -643,17 +740,22 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { static void furi_hal_subghz_async_tx_dma_isr() { furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); - if(LL_DMA_IsActiveFlag_HT1(DMA1)) { - LL_DMA_ClearFlag_HT1(DMA1); + +#if SUBGHZ_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + if(LL_DMA_IsActiveFlag_HT1(SUBGHZ_DMA)) { + LL_DMA_ClearFlag_HT1(SUBGHZ_DMA); furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); } - if(LL_DMA_IsActiveFlag_TC1(DMA1)) { - LL_DMA_ClearFlag_TC1(DMA1); + if(LL_DMA_IsActiveFlag_TC1(SUBGHZ_DMA)) { + LL_DMA_ClearFlag_TC1(SUBGHZ_DMA); furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx.buffer + API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); } +#else +#error Update this code. Would you kindly? +#endif } static void furi_hal_subghz_async_tx_timer_isr() { @@ -662,11 +764,12 @@ static void furi_hal_subghz_async_tx_timer_isr() { if(LL_TIM_GetAutoReload(TIM2) == 0) { if(furi_hal_subghz.state == SubGhzStateAsyncTx) { furi_hal_subghz.state = SubGhzStateAsyncTxLast; - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(SUBGHZ_DMA_CH1_DEF); } else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) { furi_hal_subghz.state = SubGhzStateAsyncTxEnd; //forcibly pulls the pin to the ground so that there is no carrier - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullDown, GpioSpeedLow); LL_TIM_DisableCounter(TIM2); } else { furi_crash(NULL); @@ -680,7 +783,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_assert(callback); //If transmission is prohibited by regional settings - // if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false; + if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false; furi_hal_subghz_async_tx.callback = callback; furi_hal_subghz_async_tx.callback_context = context; @@ -693,9 +796,19 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_async_tx.buffer = malloc(API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); - // Connect CC1101_GD0 to TIM2 as output - furi_hal_gpio_init_ex( - &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullDown, GpioSpeedLow, GpioAltFn1TIM2); + if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, true); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else { + // Connect CC1101_GD0 to TIM2 as output + furi_hal_gpio_init_ex( + &gpio_cc1101_g0, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedLow, + GpioAltFn1TIM2); + } // Configure DMA LL_DMA_InitTypeDef dma_config = {0}; @@ -710,11 +823,11 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* dma_config.NbData = API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, furi_hal_subghz_async_tx_dma_isr, NULL); - LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); + furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, furi_hal_subghz_async_tx_dma_isr, NULL); + LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); // Configure TIM2 LL_TIM_InitTypeDef TIM_InitStruct = {0}; @@ -755,9 +868,20 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); - // Start debug - if(furi_hal_subghz_start_debug()) { - const GpioPin* gpio = furi_hal_subghz.async_mirror_pin; + //Signal generation for external module + + // Start debug (and speaker) + furi_hal_subghz_start_debug(); + + const GpioPin* gpio = furi_hal_subghz.cc1101_g0_pin; + + if((furi_hal_subghz.async_mirror_pin != NULL) && + (furi_hal_subghz.radio_type == SubGhzRadioInternal)) { + gpio = furi_hal_subghz.async_mirror_pin; + } + if(((furi_hal_subghz.async_mirror_pin != NULL) && + (furi_hal_subghz.radio_type == SubGhzRadioInternal)) || + (furi_hal_subghz.radio_type == SubGhzRadioExternal)) { furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; furi_hal_subghz_debug_gpio_buff[1] = gpio->pin; @@ -772,9 +896,9 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* dma_config.NbData = 2; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); - LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_Init(SUBGHZ_DMA_CH2_DEF, &dma_config); + LL_DMA_SetDataLength(SUBGHZ_DMA_CH2_DEF, 2); + LL_DMA_EnableChannel(SUBGHZ_DMA_CH2_DEF); } return true; @@ -792,9 +916,9 @@ void furi_hal_subghz_stop_async_tx() { // Shutdown radio furi_hal_subghz_idle(); -#ifdef FURI_HAL_SUBGHZ_TX_GPIO - furi_hal_gpio_write(&FURI_HAL_SUBGHZ_TX_GPIO, false); -#endif + if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + } // Deinitialize Timer FURI_CRITICAL_ENTER(); @@ -802,16 +926,20 @@ void furi_hal_subghz_stop_async_tx() { furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); // Deinitialize DMA - LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DeInit(SUBGHZ_DMA_CH1_DEF); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, NULL, NULL); // Deinitialize GPIO - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); // Stop debug - if(furi_hal_subghz_stop_debug()) { - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + furi_hal_subghz_stop_debug(); + + if(((furi_hal_subghz.async_mirror_pin != NULL) && + (furi_hal_subghz.radio_type == SubGhzRadioInternal)) || + (furi_hal_subghz.radio_type == SubGhzRadioExternal)) { + LL_DMA_DisableChannel(SUBGHZ_DMA_CH2_DEF); } FURI_CRITICAL_EXIT(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index b3319e226..b19a71f9a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -61,6 +62,25 @@ typedef enum { SubGhzRegulationTxRx, /**TxRx*/ } SubGhzRegulation; +/** SubGhz radio types */ +typedef enum { + SubGhzRadioInternal, + SubGhzRadioExternal, +} SubGhzRadioType; + +/** Structure for accessing SubGhz settings*/ +typedef struct { + volatile SubGhzState state; + volatile SubGhzRegulation regulation; + volatile FuriHalSubGhzPreset preset; + const GpioPin* async_mirror_pin; + SubGhzRadioType radio_type; + FuriHalSpiBusHandle* spi_bus_handle; + const GpioPin* cc1101_g0_pin; +} FuriHalSubGhz; + +extern volatile FuriHalSubGhz furi_hal_subghz; + /* Mirror RX/TX async modulation signal to specified pin * * @warning Configures pin to output mode. Make sure it is not connected @@ -76,6 +96,13 @@ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin); */ void furi_hal_subghz_init(); +/** Initialize and switch to power save mode Used by internal API-HAL + * initialization routine Can be used to reinitialize device to safe state and + * send it to sleep + * @return true if initialisation is successfully + */ +bool furi_hal_subghz_init_check(void); + /** Send device to sleep mode */ void furi_hal_subghz_sleep(); @@ -258,6 +285,30 @@ bool furi_hal_subghz_is_async_tx_complete(); */ void furi_hal_subghz_stop_async_tx(); +/** Switching between internal and external radio + * @param state SubGhzRadioInternal or SubGhzRadioExternal + * @return true if switching is successful + */ +bool furi_hal_subghz_set_radio_type(SubGhzRadioType state); + +/** Get current radio + * @return SubGhzRadioInternal or SubGhzRadioExternal + */ +SubGhzRadioType furi_hal_subghz_get_radio_type(void); + +/** Check for a radio module + * @return true if check is successful + */ +bool furi_hal_subghz_check_radio(void); + +/** Turn on the power of the external radio module + */ +void furi_hal_subghz_enable_ext_power(void); + +/** Turn off the power of the external radio module + */ +void furi_hal_subghz_disable_ext_power(void); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h b/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h index 5ea17b6dd..b2b5760fd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h @@ -273,16 +273,6 @@ static const uint8_t furi_hal_subghz_preset_ook_async_patable[8] = { 0x00, 0x00}; -static const uint8_t furi_hal_subghz_preset_ook_async_patable_au[8] = { - 0x00, - 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - static const uint8_t furi_hal_subghz_preset_2fsk_async_patable[8] = { 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 0x00, diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 6d091114b..8fbec94ad 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -11,6 +11,7 @@ env.Append( File("subghz_tx_rx_worker.h"), File("transmitter.h"), File("registry.h"), + File("protocols/protocol_items.h"), File("protocols/raw.h"), File("blocks/const.h"), File("blocks/decoder.h"), diff --git a/lib/subghz/blocks/encoder.c b/lib/subghz/blocks/encoder.c index e325be21c..49ec4f177 100644 --- a/lib/subghz/blocks/encoder.c +++ b/lib/subghz/blocks/encoder.c @@ -55,4 +55,4 @@ size_t subghz_protocol_blocks_get_upload_from_bit_array( upload[size_upload++] = level_duration_make( subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); return size_upload; -} \ No newline at end of file +} diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 94114676d..3d59adc82 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -100,7 +100,7 @@ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperForma FURI_LOG_E(TAG, "Missing Bit"); break; } - instance->data_count_bit = (uint8_t)temp_data; + instance->data_count_bit = (uint16_t)temp_data; uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 8839c0b54..e69de8b4f 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -20,7 +20,7 @@ struct SubGhzBlockGeneric { uint64_t data; uint64_t data_2; uint32_t serial; - uint8_t data_count_bit; + uint16_t data_count_bit; uint8_t btn; uint32_t cnt; uint8_t cnt_2; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 3fef76af2..67e0467ee 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -111,7 +111,7 @@ const SubGhzProtocolEncoder subghz_protocol_bin_raw_encoder = { const SubGhzProtocol subghz_protocol_bin_raw = { .name = SUBGHZ_PROTOCOL_BIN_RAW_NAME, - .type = SubGhzProtocolTypeStatic, + .type = SubGhzProtocolTypeBinRAW, #ifdef BIN_RAW_DEBUG .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | @@ -1117,4 +1117,4 @@ void subghz_protocol_decoder_bin_raw_get_string(void* context, FuriString* outpu } furi_string_cat_printf(output, "\r\nTe:%luus\r\n", instance->te); -} \ No newline at end of file +} diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 2c8de0d2d..5f2a83d77 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -23,7 +23,6 @@ struct SubGhzProtocolDecoderKingGates_stylo_4k { SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; - uint64_t data; uint16_t header_count; SubGhzKeystore* keystore; }; @@ -33,6 +32,7 @@ struct SubGhzProtocolEncoderKingGates_stylo_4k { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + SubGhzKeystore* keystore; }; typedef enum { @@ -57,23 +57,268 @@ const SubGhzProtocolDecoder subghz_protocol_kinggates_stylo_4k_decoder = { }; const SubGhzProtocolEncoder subghz_protocol_kinggates_stylo_4k_encoder = { - .alloc = NULL, - .free = NULL, + .alloc = subghz_protocol_encoder_kinggates_stylo_4k_alloc, + .free = subghz_protocol_encoder_kinggates_stylo_4k_free, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, + .deserialize = subghz_protocol_encoder_kinggates_stylo_4k_deserialize, + .stop = subghz_protocol_encoder_kinggates_stylo_4k_stop, + .yield = subghz_protocol_encoder_kinggates_stylo_4k_yield, }; const SubGhzProtocol subghz_protocol_kinggates_stylo_4k = { .name = SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME, .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_kinggates_stylo_4k_decoder, .encoder = &subghz_protocol_kinggates_stylo_4k_encoder, }; +// +// Encoder +// + +// Pre define function +static void subghz_protocol_kinggates_stylo_4k_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore); + +void* subghz_protocol_encoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderKingGates_stylo_4k* instance = + malloc(sizeof(SubGhzProtocolEncoderKingGates_stylo_4k)); + + instance->base.protocol = &subghz_protocol_kinggates_stylo_4k; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + 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_kinggates_stylo_4k_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderKingGates_stylo_4k* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void subghz_protocol_encoder_kinggates_stylo_4k_stop(void* context) { + SubGhzProtocolEncoderKingGates_stylo_4k* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_kinggates_stylo_4k_yield(void* context) { + SubGhzProtocolEncoderKingGates_stylo_4k* 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; +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k* instance + * @param btn Button number, 4 bit + */ +static bool subghz_protocol_kinggates_stylo_4k_gen_data( + SubGhzProtocolEncoderKingGates_stylo_4k* instance, + uint8_t btn) { + UNUSED(btn); + uint32_t hop = subghz_protocol_blocks_reverse_key(instance->generic.data_2 >> 4, 32); + uint64_t fix = subghz_protocol_blocks_reverse_key(instance->generic.data, 53); + int res = 0; + uint32_t decrypt = 0; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k"); + if(res == 0) { + //Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + break; + } + } + instance->generic.cnt = decrypt & 0xFFFF; + + if(instance->generic.cnt < 0xFFFF) { + instance->generic.cnt++; + } else if(instance->generic.cnt >= 0xFFFF) { + instance->generic.cnt = 0; + } + + instance->generic.btn = (fix >> 17) & 0x0F; + instance->generic.serial = ((fix >> 5) & 0xFFFF0000) | (fix & 0xFFFF); + + uint32_t data = (decrypt & 0xFFFF0000) | instance->generic.cnt; + + uint64_t encrypt = 0; + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k"); + if(res == 0) { + //Simple Learning + encrypt = subghz_protocol_keeloq_common_encrypt(data, manufacture_code->key); + encrypt = subghz_protocol_blocks_reverse_key(encrypt, 32); + instance->generic.data_2 = encrypt << 4; + return true; + } + } + + return false; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + * @return true On success + */ +static bool subghz_protocol_encoder_kinggates_stylo_4k_get_upload( + SubGhzProtocolEncoderKingGates_stylo_4k* instance, + uint8_t btn) { + furi_assert(instance); + + // Gen new key + if(subghz_protocol_kinggates_stylo_4k_gen_data(instance, btn)) { + //ToDo if you need to add a callback to automatically update the data on the display + } else { + return false; + } + + size_t index = 0; + + // Start + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)9500); + + // Send header + for(uint8_t i = 12; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + } + + // After header + instance->encoder.upload[index - 1].duration = + (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long * 2; + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short * 2); + + // Send key fix + for(uint8_t i = 53; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + } + } + + // Send key hop + for(uint8_t i = 36; i > 0; i--) { + if(bit_read(instance->generic.data_2, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_short); + } + } + + // Set upload size after generating upload, fix it later + + instance->encoder.size_upload = index; + + return true; +} + +bool subghz_protocol_encoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderKingGates_stylo_4k* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + subghz_protocol_kinggates_stylo_4k_remote_controller( + &instance->generic, instance->keystore); + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + + subghz_protocol_encoder_kinggates_stylo_4k_get_upload(instance, instance->generic.btn); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +// +// Decoder +// void* subghz_protocol_decoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment) { SubGhzProtocolDecoderKingGates_stylo_4k* instance = malloc(sizeof(SubGhzProtocolDecoderKingGates_stylo_4k)); @@ -130,7 +375,7 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) { instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; instance->decoder.decode_data = 0; - instance->data = 0; + instance->generic.data_2 = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; } @@ -140,8 +385,8 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, if(duration >= ((uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long * 3)) { if(instance->decoder.decode_count_bit == subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found) { - instance->generic.data = instance->data; - instance->data = instance->decoder.decode_data; + instance->generic.data = instance->generic.data_2; + instance->generic.data_2 = instance->decoder.decode_data; instance->generic.data_count_bit = instance->decoder.decode_count_bit; if(instance->base.callback) @@ -150,7 +395,7 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; instance->decoder.decode_data = 0; - instance->data = 0; + instance->generic.data_2 = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; break; @@ -185,7 +430,7 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, instance->header_count = 0; } if(instance->decoder.decode_count_bit == 53) { - instance->data = instance->decoder.decode_data; + instance->generic.data_2 = instance->decoder.decode_data; instance->decoder.decode_data = 0; } } else { @@ -199,11 +444,11 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, /** * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance - * @param file_name Full path to rainbow table the file + * @param data Input encrypted data + * @param keystore Pointer to a SubGhzKeystore* instance */ static void subghz_protocol_kinggates_stylo_4k_remote_controller( SubGhzBlockGeneric* instance, - uint64_t data, SubGhzKeystore* keystore) { /** * 9500us 12*(400/400) 2200/800|1-bit|0-bit| @@ -226,7 +471,7 @@ static void subghz_protocol_kinggates_stylo_4k_remote_controller( * */ - uint32_t hop = subghz_protocol_blocks_reverse_key(data >> 4, 32); + uint32_t hop = subghz_protocol_blocks_reverse_key(instance->data_2 >> 4, 32); uint64_t fix = subghz_protocol_blocks_reverse_key(instance->data, 53); bool ret = false; uint32_t decrypt = 0; @@ -270,7 +515,7 @@ bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( 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->data >> (i * 8)) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; } if(res && !flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { @@ -306,8 +551,9 @@ bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( FURI_LOG_E(TAG, "Missing Data"); break; } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { - instance->data = instance->data << 8 | key_data[i]; + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; } ret = true; } while(false); @@ -317,8 +563,7 @@ bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; - subghz_protocol_kinggates_stylo_4k_remote_controller( - &instance->generic, instance->data, instance->keystore); + subghz_protocol_kinggates_stylo_4k_remote_controller(&instance->generic, instance->keystore); furi_string_cat_printf( output, @@ -328,9 +573,9 @@ void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriSt "Cnt:0x%04lX\r\n", instance->generic.protocol_name, instance->generic.data, - instance->data, + instance->generic.data_2, instance->generic.data_count_bit, instance->generic.serial, instance->generic.btn, instance->generic.cnt); -} +} \ No newline at end of file diff --git a/lib/subghz/protocols/kinggates_stylo_4k.h b/lib/subghz/protocols/kinggates_stylo_4k.h index c9f1cf380..9717f6715 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.h +++ b/lib/subghz/protocols/kinggates_stylo_4k.h @@ -10,6 +10,42 @@ extern const SubGhzProtocolDecoder subghz_protocol_kinggates_stylo_4k_decoder; extern const SubGhzProtocolEncoder subghz_protocol_kinggates_stylo_4k_encoder; extern const SubGhzProtocol subghz_protocol_kinggates_stylo_4k; +/** + * Allocate SubGhzProtocolEncoderKingGates_stylo_4k. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderKingGates_stylo_4k* pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + */ +void* subghz_protocol_encoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + */ +void subghz_protocol_encoder_kinggates_stylo_4k_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + */ +void subghz_protocol_encoder_kinggates_stylo_4k_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderKingGates_stylo_4k instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_kinggates_stylo_4k_yield(void* context); + /** * Allocate SubGhzProtocolDecoderKingGates_stylo_4k. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 96717e56f..74244c5ff 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -36,13 +36,13 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_intertechno_v3, &subghz_protocol_clemsa, &subghz_protocol_ansonic, - &subghz_protocol_pocsag, &subghz_protocol_smc5326, &subghz_protocol_holtek_th12x, &subghz_protocol_linear_delta3, &subghz_protocol_dooya, &subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k, + &subghz_protocol_bin_raw, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 362b0459a..b7e082bf5 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -37,12 +37,12 @@ #include "intertechno_v3.h" #include "clemsa.h" #include "ansonic.h" -#include "pocsag.h" #include "smc5326.h" #include "holtek_ht12x.h" #include "dooya.h" #include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" +#include "bin_raw.h" #ifdef __cplusplus extern "C" { diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 393d7f360..01a229047 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -8,16 +8,11 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#include #include #include -#include #define TAG "SubGhzProtocolRAW" #define SUBGHZ_DOWNLOAD_MAX_SIZE 512 -#define SUBGHZ_AUTO_DETECT_DOWNLOAD_MAX_SIZE 2048 -#define SUBGHZ_AUTO_DETECT_RAW_THRESHOLD -72.0f -#define SUBGHZ_AUTO_DETECT_RAW_POSTROLL_FRAMES 40 static const SubGhzBlockConst subghz_protocol_raw_const = { .te_short = 50, @@ -29,8 +24,6 @@ static const SubGhzBlockConst subghz_protocol_raw_const = { struct SubGhzProtocolDecoderRAW { SubGhzProtocolDecoderBase base; - SubGhzBlockDecoder decoder; - int32_t* upload_raw; uint16_t ind_write; Storage* storage; @@ -39,10 +32,6 @@ struct SubGhzProtocolDecoderRAW { FuriString* file_name; size_t sample_write; bool last_level; - bool auto_mode; - bool has_rssi_above_threshold; - int rssi_threshold; - uint8_t postroll_frames; bool pause; }; @@ -67,8 +56,8 @@ const SubGhzProtocolDecoder subghz_protocol_raw_decoder = { .feed = subghz_protocol_decoder_raw_feed, .reset = subghz_protocol_decoder_raw_reset, - .get_hash_data = subghz_protocol_decoder_raw_get_hash_data, - .serialize = subghz_protocol_decoder_raw_serialize, + .get_hash_data = NULL, + .serialize = NULL, .deserialize = subghz_protocol_decoder_raw_deserialize, .get_string = subghz_protocol_decoder_raw_get_string, }; @@ -213,36 +202,6 @@ void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance) { instance->file_is_open = RAWFileIsOpenClose; } -void subghz_protocol_decoder_raw_set_rssi_threshold(void* context, int rssi_threshold) { - furi_assert(context); - SubGhzProtocolDecoderRAW* instance = context; - - FURI_LOG_D(TAG, "RSSI set: (%d)", rssi_threshold); - - instance->rssi_threshold = rssi_threshold; - - subghz_protocol_decoder_raw_reset(context); -} - -void subghz_protocol_decoder_raw_set_auto_mode(void* context, bool auto_mode) { - furi_assert(context); - SubGhzProtocolDecoderRAW* instance = context; - instance->auto_mode = auto_mode; - - if(auto_mode) { - if(instance->upload_raw == NULL) { - instance->upload_raw = malloc(SUBGHZ_AUTO_DETECT_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); - } - } else { - if(instance->upload_raw != NULL) { - free(instance->upload_raw); - instance->upload_raw = NULL; - } - } - - subghz_protocol_decoder_raw_reset(context); -} - void subghz_protocol_raw_save_to_file_pause(SubGhzProtocolDecoderRAW* instance, bool pause) { furi_assert(instance); @@ -263,8 +222,6 @@ void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment) { instance->ind_write = 0; instance->last_level = false; instance->file_is_open = RAWFileIsOpenClose; - instance->postroll_frames = 0; - instance->rssi_threshold = SUBGHZ_AUTO_DETECT_RAW_THRESHOLD; instance->file_name = furi_string_alloc(); return instance; @@ -274,10 +231,6 @@ void subghz_protocol_decoder_raw_free(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; furi_string_free(instance->file_name); - if(instance->upload_raw != NULL) { - free(instance->upload_raw); - instance->upload_raw = NULL; - } free(instance); } @@ -285,66 +238,23 @@ void subghz_protocol_decoder_raw_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; instance->ind_write = 0; - instance->has_rssi_above_threshold = false; instance->last_level = false; - instance->postroll_frames = 0; -} - -bool subghz_protocol_decoder_raw_write_data(void* context, bool level, uint32_t duration) { - furi_assert(context); - SubGhzProtocolDecoderRAW* instance = context; - - bool wrote_data = false; - - if(instance->last_level != level) { - instance->last_level = (level ? true : false); - instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); - subghz_protocol_blocks_add_bit(&instance->decoder, (level) ? 1 : 0); - wrote_data = true; - } - - if(instance->ind_write == SUBGHZ_AUTO_DETECT_DOWNLOAD_MAX_SIZE) { - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - - return false; - } - - return wrote_data; } void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; - if(instance->upload_raw != NULL && !instance->pause && - duration > subghz_protocol_raw_const.te_short) { - if(instance->auto_mode) { - float rssi = furi_hal_subghz_get_rssi(); - - if(rssi >= instance->rssi_threshold) { - subghz_protocol_decoder_raw_write_data(context, level, duration); - instance->has_rssi_above_threshold = true; - instance->postroll_frames = 0; - } else if(instance->has_rssi_above_threshold) { - subghz_protocol_decoder_raw_write_data(instance, level, duration); - instance->postroll_frames++; - - if(instance->postroll_frames >= SUBGHZ_AUTO_DETECT_RAW_POSTROLL_FRAMES) { - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - } - } else { + if(!instance->pause && (instance->upload_raw != NULL)) { + if(duration > subghz_protocol_raw_const.te_short) { if(instance->last_level != level) { instance->last_level = (level ? true : false); instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); - subghz_protocol_blocks_add_bit(&instance->decoder, (level) ? 1 : 0); } + } - if(instance->ind_write == SUBGHZ_DOWNLOAD_MAX_SIZE) { - subghz_protocol_raw_save_to_file_write(instance); - } + if(instance->ind_write == SUBGHZ_DOWNLOAD_MAX_SIZE) { + subghz_protocol_raw_save_to_file_write(instance); } } } @@ -357,13 +267,6 @@ bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipp return true; } -uint8_t subghz_protocol_decoder_raw_get_hash_data(void* context) { - furi_assert(context); - SubGhzProtocolDecoderRAW* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { furi_assert(context); //SubGhzProtocolDecoderRAW* instance = context; @@ -382,13 +285,6 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { return instance; } -int subghz_protocol_encoder_get_rssi_threshold(void* context) { - furi_assert(context); - SubGhzProtocolDecoderRAW* instance = context; - - return instance->rssi_threshold; -} - void subghz_protocol_encoder_raw_stop(void* context) { SubGhzProtocolEncoderRAW* instance = context; instance->is_running = false; @@ -446,70 +342,6 @@ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* } while(false); } -bool subghz_protocol_decoder_raw_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - SubGhzProtocolDecoderRAW* instance = context; - if(instance->auto_mode) { - furi_assert(instance); - bool res = false; - FuriString* temp_str; - temp_str = furi_string_alloc(); - - do { - stream_clean(flipper_format_get_raw_stream(flipper_format)); - if(!flipper_format_write_header_cstr( - flipper_format, SUBGHZ_RAW_FILE_TYPE, SUBGHZ_RAW_FILE_VERSION)) { - FURI_LOG_E(TAG, "Unable to add header"); - break; - } - - if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { - FURI_LOG_E(TAG, "Unable to add Frequency"); - break; - } - subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); - if(!flipper_format_write_string_cstr( - flipper_format, "Preset", furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Unable to add Preset"); - break; - } - if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { - if(!flipper_format_write_string_cstr( - flipper_format, "Custom_preset_module", "CC1101")) { - FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); - break; - } - if(!flipper_format_write_hex( - flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { - FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); - break; - } - } - if(!flipper_format_write_string_cstr( - flipper_format, "Protocol", instance->base.protocol->name)) { - FURI_LOG_E(TAG, "Unable to add Protocol"); - break; - } - - if(!flipper_format_write_int32( - flipper_format, "RAW_Data", instance->upload_raw, instance->ind_write)) { - FURI_LOG_E(TAG, "Unable to add Raw Data"); - break; - } else { - instance->ind_write = 0; - } - res = true; - } while(false); - furi_string_free(temp_str); - return res; - } else { - return false; - } -} - bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderRAW* instance = context; diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 08efff50e..44c7faec5 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -2,8 +2,6 @@ #include "base.h" -#include - #define SUBGHZ_PROTOCOL_RAW_NAME "RAW" #ifdef __cplusplus @@ -31,27 +29,6 @@ bool subghz_protocol_raw_save_to_file_init( const char* dev_name, SubGhzRadioPreset* preset); -/** - * Set SubGhzProtocolDecoderRAW to auto mode, which allows subghz_scene_receiver to capture RAW. - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @param auto_mode Whether or not to enable auto mode - */ -void subghz_protocol_decoder_raw_set_auto_mode(void* context, bool auto_mode); - -/** - * Set RSSI threshold ("sensitivity" level). - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @param rssi_threshold The desired RSSI threshold - */ -void subghz_protocol_decoder_raw_set_rssi_threshold(void* context, int rssi_threshold); - -/** - * Get RSSI threshold ("sensitivity" level). - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @return rssi threshold in db - */ -int subghz_protocol_encoder_get_rssi_threshold(void* context); - /** * Stop writing file to flash * @param instance Pointer to a SubGhzProtocolDecoderRAW instance @@ -84,15 +61,6 @@ void subghz_protocol_decoder_raw_free(void* context); */ void subghz_protocol_decoder_raw_reset(void* context); -/** - * Write raw data to the instance's internal buffer. - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - * @return whether or not data was written - */ -bool subghz_protocol_decoder_raw_write_data(void* context, bool level, uint32_t duration); - /** * Parse a raw sequence of levels and durations received from the air. * @param context Pointer to a SubGhzProtocolDecoderRAW instance @@ -103,19 +71,12 @@ void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t durati /** * Deserialize data SubGhzProtocolDecoderRAW. - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @param flipper_format Pointer to a FlipperFormat instance + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + * @param flipper_format Pointer to a FlipperFormat instance * @return true On success */ bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format); -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @return hash Hash sum - */ -uint8_t subghz_protocol_decoder_raw_get_hash_data(void* context); - /** * Getting a textual representation of the received data. * @param context Pointer to a SubGhzProtocolDecoderRAW instance @@ -167,19 +128,6 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( */ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path); -/** - * Serialize data SubGhzProtocolDecoderRAW. - * @param context Pointer to a SubGhzProtocolDecoderRAW instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param frequency The frequency at which the signal was received, Hz - * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset - * @return true On success - */ -bool subghz_protocol_decoder_raw_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderRAW instance @@ -191,7 +139,7 @@ bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipp /** * Getting the level and duration of the upload to be loaded into DMA. * @param context Pointer to a SubGhzProtocolEncoderRAW instance - * @return LevelDuration + * @return LevelDuration */ LevelDuration subghz_protocol_encoder_raw_yield(void* context); diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 27a82beab..bcef90dad 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -261,16 +261,16 @@ static bool data = order << 4 | invert; int k = 0; for(int i = 6; i >= 0; i -= 2) { - roll_array[k++] = (data >> i) & 0x03; - if(roll_array[k] == 3) { + roll_array[k] = (data >> i) & 0x03; + if(roll_array[k++] == 3) { FURI_LOG_E(TAG, "Roll_Array FAIL"); return false; } } for(int i = 8; i >= 0; i -= 2) { - roll_array[k++] = (p[2] >> i) & 0x03; - if(roll_array[k] == 3) { + roll_array[k] = (p[2] >> i) & 0x03; + if(roll_array[k++] == 3) { FURI_LOG_E(TAG, "Roll_Array FAIL"); return false; } diff --git a/lib/subghz/receiver.c b/lib/subghz/receiver.c index d7b422170..698fe098e 100644 --- a/lib/subghz/receiver.c +++ b/lib/subghz/receiver.c @@ -108,11 +108,6 @@ void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag fil instance->filter = filter; } -SubGhzProtocolFlag subghz_receiver_get_filter(SubGhzReceiver* instance) { - furi_assert(instance); - return instance->filter; -} - SubGhzProtocolDecoderBase* subghz_receiver_search_decoder_base_by_name( SubGhzReceiver* instance, const char* decoder_name) { diff --git a/lib/subghz/receiver.h b/lib/subghz/receiver.h index 15fc455fd..2ef722d1f 100644 --- a/lib/subghz/receiver.h +++ b/lib/subghz/receiver.h @@ -59,13 +59,6 @@ void subghz_receiver_set_rx_callback( */ void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag filter); -/** - * Get the filter of receivers that will work at the moment. - * @param instance Pointer to a SubGhzReceiver instance - * @return filter Filter, SubGhzProtocolFlag - */ -SubGhzProtocolFlag subghz_receiver_get_filter(SubGhzReceiver* instance); - /** * Search for a cattery by his name. * @param instance Pointer to a SubGhzReceiver instance diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 42124bebc..e380fc967 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -70,7 +70,7 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* furi_delay_tick(1); } //waiting for reception to complete - while(furi_hal_gpio_read(&gpio_cc1101_g0)) { + while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin)) { furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "RX cc1101_g0 timeout"); @@ -104,14 +104,16 @@ void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t si furi_hal_subghz_write_packet(data, size); furi_hal_subghz_tx(); //start send instance->status = SubGhzTxRxWorkerStatusTx; - while(!furi_hal_gpio_read(&gpio_cc1101_g0)) { // Wait for GDO0 to be set -> sync transmitted + while(!furi_hal_gpio_read( + furi_hal_subghz.cc1101_g0_pin)) { // Wait for GDO0 to be set -> sync transmitted furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "TX !cc1101_g0 timeout"); break; } } - while(furi_hal_gpio_read(&gpio_cc1101_g0)) { // Wait for GDO0 to be cleared -> end of packet + while(furi_hal_gpio_read( + furi_hal_subghz.cc1101_g0_pin)) { // Wait for GDO0 to be cleared -> end of packet furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "TX cc1101_g0 timeout"); @@ -134,7 +136,7 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { furi_hal_subghz_idle(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetGFSK9_99KbAsync); //furi_hal_subghz_load_preset(FuriHalSubGhzPresetMSK99_97KbAsync); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_set_frequency_and_path(instance->frequency); furi_hal_subghz_flush_rx(); @@ -233,7 +235,7 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { instance->worker_running = true; - if(furi_hal_region_is_frequency_allowed(frequency)) { + if(furi_hal_subghz_is_tx_allowed(frequency)) { instance->frequency = frequency; res = true; } diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 1b8ef6a14..9d121dc3c 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -77,6 +77,7 @@ typedef enum { SubGhzProtocolTypeRAW, SubGhzProtocolWeatherStation, SubGhzProtocolCustom, + SubGhzProtocolTypeBinRAW, } SubGhzProtocolType; typedef enum {