mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-30 02:18:11 -07:00
Merge remote-tracking branch 'ofw/dev' into feat/nfc-type-4-final
This commit is contained in:
@@ -38,6 +38,8 @@ static bool rw1990_read_and_compare(OneWireHost* host, const uint8_t* data, size
|
||||
}
|
||||
|
||||
bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
onewire_host_set_timings_default(host);
|
||||
|
||||
// Unlock sequence
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG);
|
||||
@@ -67,6 +69,8 @@ bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
}
|
||||
|
||||
bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
onewire_host_set_timings_default(host);
|
||||
|
||||
// Unlock sequence
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <core/kernel.h>
|
||||
#include <one_wire/one_wire_host.h>
|
||||
#include <one_wire/maxim_crc.h>
|
||||
#include "tm01x.h"
|
||||
|
||||
// Commands for TM01x
|
||||
#define TM01X_CMD_WRITE_FLAG 0xC1
|
||||
#define TM01X_CMD_WRITE_ROM 0xC5
|
||||
#define TM01X_CMD_READ_ROM 0x33
|
||||
|
||||
#define TM01X_CMD_FINALIZE_CYFRAL 0xCA
|
||||
#define TM01X_CMD_FINALIZE_METAKOM 0xCB
|
||||
|
||||
static void tm01x_write_byte(OneWireHost* host, uint8_t value) {
|
||||
for(uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
onewire_host_write_bit(host, (bool)(bitMask & value));
|
||||
furi_delay_us(5000); // 5ms pause after each bit
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to read and verify written data
|
||||
static bool tm01x_read_and_verify(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
bool success = false;
|
||||
|
||||
if(onewire_host_reset(host)) {
|
||||
success = true;
|
||||
onewire_host_write(host, TM01X_CMD_READ_ROM);
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
if(data[i] != onewire_host_read(host)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
// Set TM01x specific timings
|
||||
onewire_host_set_timings_tm01x(host);
|
||||
|
||||
// Write sequence
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, TM01X_CMD_WRITE_FLAG);
|
||||
onewire_host_write_bit(host, true);
|
||||
furi_delay_us(5000);
|
||||
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, TM01X_CMD_WRITE_ROM);
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
tm01x_write_byte(host, data[i]);
|
||||
}
|
||||
|
||||
return tm01x_read_and_verify(host, data, data_size);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/kernel.h>
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size);
|
||||
@@ -9,6 +9,8 @@
|
||||
#define TM2004_ANSWER_READ_MEMORY 0xF5
|
||||
|
||||
bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) {
|
||||
onewire_host_set_timings_default(host);
|
||||
|
||||
onewire_host_reset(host);
|
||||
onewire_host_write(host, TM2004_CMD_WRITE_ROM);
|
||||
// Starting writing from address 0x0000
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "../blanks/rw1990.h"
|
||||
#include "../blanks/tm2004.h"
|
||||
|
||||
#include "../blanks/tm01x.h"
|
||||
#define DS1990_FAMILY_CODE 0x01U
|
||||
#define DS1990_FAMILY_NAME "DS1990"
|
||||
|
||||
@@ -66,7 +66,8 @@ bool dallas_ds1990_write_id(OneWireHost* host, iButtonProtocolData* protocol_dat
|
||||
|
||||
return rw1990_write_v1(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) ||
|
||||
rw1990_write_v2(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) ||
|
||||
tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData));
|
||||
tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) ||
|
||||
tm01x_write_dallas(host, data->rom_data.bytes, sizeof(DallasCommonRomData));
|
||||
}
|
||||
|
||||
static bool dallas_ds1990_reset_callback(bool is_short, void* context) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include "lfrfid_protocols.h"
|
||||
#include <furi_hal_rtc.h>
|
||||
#include <tools/iso_3166.h>
|
||||
|
||||
#define FDX_B_ENCODED_BIT_SIZE (128)
|
||||
#define FDX_B_ENCODED_BYTE_SIZE (((FDX_B_ENCODED_BIT_SIZE) / 8))
|
||||
@@ -287,15 +288,21 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) {
|
||||
uint8_t user_info = bit_lib_get_bits(protocol->data, 55, 5);
|
||||
uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3);
|
||||
bool animal_flag = bit_lib_get_bit(protocol->data, 63);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FuriString* country_full_name = furi_string_alloc();
|
||||
bool country_found = iso_3166_get_full_name(storage, country_code, country_full_name);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
furi_string_printf(
|
||||
result,
|
||||
"ID: %03hu-%012llu\n"
|
||||
"Country Code: %hu\n"
|
||||
"Country: %s\n"
|
||||
"Temperature: ",
|
||||
country_code,
|
||||
national_code,
|
||||
country_code);
|
||||
country_code,
|
||||
(country_found) ? furi_string_get_cstr(country_full_name) : "Unknown");
|
||||
|
||||
float temperature;
|
||||
if(protocol_fdx_b_get_temp(protocol->data, &temperature)) {
|
||||
@@ -320,6 +327,8 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) {
|
||||
reserved,
|
||||
user_info,
|
||||
replacement_number);
|
||||
|
||||
furi_string_free(country_full_name);
|
||||
}
|
||||
|
||||
void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result) {
|
||||
@@ -328,14 +337,18 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result
|
||||
|
||||
// 10 bit of country code
|
||||
uint16_t country_code = protocol_fdx_b_get_country_code(protocol->data);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FuriString* country_two_letter = furi_string_alloc();
|
||||
bool country_found = iso_3166_get_two_letter(storage, country_code, country_two_letter);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_printf(
|
||||
result,
|
||||
"ID: %03hu-%012llu\n"
|
||||
"Country: %hu; Temp.: ",
|
||||
"Country: %hu %s; Temp.: ",
|
||||
country_code,
|
||||
national_code,
|
||||
country_code);
|
||||
country_code,
|
||||
(country_found) ? furi_string_get_cstr(country_two_letter) : "Unknown");
|
||||
|
||||
float temperature;
|
||||
if(protocol_fdx_b_get_temp(protocol->data, &temperature)) {
|
||||
@@ -348,6 +361,8 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result
|
||||
} else {
|
||||
furi_string_cat(result, "---");
|
||||
}
|
||||
|
||||
furi_string_free(country_two_letter);
|
||||
}
|
||||
|
||||
bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "iso_3166.h"
|
||||
|
||||
#include <flipper_format.h>
|
||||
|
||||
#define RESOURCE_FILE_PATH (EXT_PATH("lfrfid/assets/iso3166.lfrfid"))
|
||||
|
||||
static bool lfrfid_search_data(Storage* storage, uint16_t country_code, FuriString* out_line) {
|
||||
static const char* lfrfid_resources_header = "Flipper LFRFID resources";
|
||||
static const uint32_t lfrfid_resources_file_version = 1;
|
||||
|
||||
FuriString* key = furi_string_alloc_printf("%04d", country_code);
|
||||
bool found = false;
|
||||
furi_string_reset(out_line);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
// Open file
|
||||
if(!flipper_format_file_open_existing(file, RESOURCE_FILE_PATH)) break;
|
||||
// Read file header and version
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
if(furi_string_cmp_str(temp_str, lfrfid_resources_header) ||
|
||||
(version != lfrfid_resources_file_version))
|
||||
break;
|
||||
if(!flipper_format_read_string(file, furi_string_get_cstr(key), out_line)) break;
|
||||
found = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(key);
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
|
||||
if(found) {
|
||||
furi_string_replace_all(out_line, " ", "");
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool iso_3166_get_two_letter(Storage* storage, uint16_t country_code, FuriString* out_two_letter) {
|
||||
// We'll fetch the entire line from iso3166.lfrfid
|
||||
FuriString* line = furi_string_alloc();
|
||||
bool found = lfrfid_search_data(storage, country_code, line);
|
||||
|
||||
if(found) {
|
||||
if(furi_string_size(line) < 2) {
|
||||
furi_string_free(line);
|
||||
FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for two-letter code");
|
||||
return false; // Not enough data for a two-letter code
|
||||
}
|
||||
// AFAFGAfghanistan
|
||||
furi_string_left(line, 2); // AF
|
||||
furi_string_set(out_two_letter, line);
|
||||
}
|
||||
furi_string_free(line);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool iso_3166_get_three_letter(
|
||||
Storage* storage,
|
||||
uint16_t country_code,
|
||||
FuriString* out_three_letter) {
|
||||
FuriString* line = furi_string_alloc();
|
||||
bool found = lfrfid_search_data(storage, country_code, line);
|
||||
|
||||
if(found) {
|
||||
if(furi_string_size(line) < 5) {
|
||||
furi_string_free(line);
|
||||
FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for three-letter code");
|
||||
return false; // Not enough data for a three-letter code
|
||||
}
|
||||
// AFAFGAfghanistan
|
||||
furi_string_left(line, 5); // AFAFG
|
||||
furi_string_right(line, 2); // AFG
|
||||
furi_string_set(out_three_letter, line);
|
||||
}
|
||||
furi_string_free(line);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool iso_3166_get_full_name(Storage* storage, uint16_t country_code, FuriString* out_full_name) {
|
||||
FuriString* line = furi_string_alloc();
|
||||
bool found = lfrfid_search_data(storage, country_code, line);
|
||||
|
||||
if(found) {
|
||||
if(furi_string_size(line) < 6) {
|
||||
furi_string_free(line);
|
||||
FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for full name");
|
||||
return false; // Not enough data for a full name
|
||||
}
|
||||
// AFAFGAfghanistan
|
||||
furi_string_right(line, 5); // Afghanistan
|
||||
furi_string_set(out_full_name, line);
|
||||
}
|
||||
furi_string_free(line);
|
||||
return found;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Look up a country's 2-letter code (ISO 3166-1 Alpha-2).
|
||||
*
|
||||
* @param storage Pointer to a valid Storage.
|
||||
* @param country_code Numeric ISO3166 code. e.g., 4 for Afghanistan.
|
||||
* @param out_two_letter A caller-allocated FuriString to fill with the 2-letter code, if found.
|
||||
*/
|
||||
bool iso_3166_get_two_letter(Storage* storage, uint16_t country_code, FuriString* out_two_letter);
|
||||
|
||||
/**
|
||||
* @brief Look up a country's 3-letter code (ISO 3166-1 Alpha-3).
|
||||
*
|
||||
* @param storage Pointer to a valid Storage.
|
||||
* @param country_code Numeric ISO3166 code.
|
||||
* @param out_three_letter A caller-allocated FuriString to fill with the 3-letter code, if found.
|
||||
*/
|
||||
bool iso_3166_get_three_letter(
|
||||
Storage* storage,
|
||||
uint16_t country_code,
|
||||
FuriString* out_three_letter);
|
||||
|
||||
/**
|
||||
* @brief Look up a country's full name.
|
||||
*
|
||||
* @param storage Pointer to a valid Storage.
|
||||
* @param country_code Numeric ISO3166 code.
|
||||
* @param out_full_name A caller-allocated FuriString to fill with the country's full name.
|
||||
*/
|
||||
bool iso_3166_get_full_name(Storage* storage, uint16_t country_code, FuriString* out_full_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -61,6 +61,7 @@ env.Append(
|
||||
File("protocols/st25tb/st25tb_poller_sync.h"),
|
||||
# Misc
|
||||
File("helpers/nfc_util.h"),
|
||||
File("helpers/felica_crc.h"),
|
||||
File("helpers/iso14443_crc.h"),
|
||||
File("helpers/iso13239_crc.h"),
|
||||
File("helpers/nfc_data_generator.h"),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "felica.h"
|
||||
#include "felica_i.h"
|
||||
#include <lib/toolbox/hex.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
@@ -11,7 +12,7 @@
|
||||
#define FELICA_MANUFACTURE_ID "Manufacture id"
|
||||
#define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter"
|
||||
|
||||
static const uint32_t felica_data_format_version = 1;
|
||||
static const uint32_t felica_data_format_version = 2;
|
||||
|
||||
/** @brief This is used in felica_prepare_first_block to define which
|
||||
* type of block needs to be prepared.
|
||||
@@ -39,24 +40,71 @@ const NfcDeviceBase nfc_device_felica = {
|
||||
|
||||
FelicaData* felica_alloc(void) {
|
||||
FelicaData* data = malloc(sizeof(FelicaData));
|
||||
furi_check(data);
|
||||
|
||||
data->services = simple_array_alloc(&felica_service_array_cfg);
|
||||
data->areas = simple_array_alloc(&felica_area_array_cfg);
|
||||
data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg);
|
||||
furi_check(data->services);
|
||||
furi_check(data->areas);
|
||||
furi_check(data->public_blocks);
|
||||
return data;
|
||||
}
|
||||
|
||||
void felica_free(FelicaData* data) {
|
||||
furi_check(data);
|
||||
|
||||
furi_check(data->services);
|
||||
simple_array_free(data->services);
|
||||
|
||||
furi_check(data->areas);
|
||||
simple_array_free(data->areas);
|
||||
|
||||
furi_check(data->public_blocks);
|
||||
simple_array_free(data->public_blocks);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void felica_reset(FelicaData* data) {
|
||||
furi_check(data);
|
||||
memset(data, 0, sizeof(FelicaData));
|
||||
|
||||
if(data->services) {
|
||||
simple_array_reset(data->services);
|
||||
}
|
||||
|
||||
if(data->areas) {
|
||||
simple_array_reset(data->areas);
|
||||
}
|
||||
|
||||
if(data->public_blocks) {
|
||||
simple_array_reset(data->public_blocks);
|
||||
}
|
||||
|
||||
data->blocks_read = 0;
|
||||
data->blocks_total = 0;
|
||||
data->workflow_type = FelicaUnknown;
|
||||
memset(&data->idm, 0, sizeof(data->idm));
|
||||
memset(&data->pmm, 0, sizeof(data->pmm));
|
||||
memset(&data->data, 0, sizeof(data->data));
|
||||
}
|
||||
|
||||
void felica_copy(FelicaData* data, const FelicaData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
*data = *other;
|
||||
felica_reset(data);
|
||||
|
||||
data->idm = other->idm;
|
||||
data->pmm = other->pmm;
|
||||
data->blocks_total = other->blocks_total;
|
||||
data->blocks_read = other->blocks_read;
|
||||
data->data = other->data;
|
||||
data->workflow_type = other->workflow_type;
|
||||
|
||||
simple_array_copy(data->services, other->services);
|
||||
simple_array_copy(data->areas, other->areas);
|
||||
simple_array_copy(data->public_blocks, other->public_blocks);
|
||||
}
|
||||
|
||||
bool felica_verify(FelicaData* data, const FuriString* device_type) {
|
||||
@@ -70,42 +118,164 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_check(data);
|
||||
|
||||
bool parsed = false;
|
||||
FuriString* str_key_buffer = furi_string_alloc();
|
||||
FuriString* str_data_buffer = furi_string_alloc();
|
||||
|
||||
// Header
|
||||
do {
|
||||
if(version < NFC_UNIFIED_FORMAT_VERSION) break;
|
||||
|
||||
uint32_t data_format_version = 0;
|
||||
if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1))
|
||||
break;
|
||||
if(data_format_version != felica_data_format_version) break;
|
||||
|
||||
// V1 saving function always treated everything as Felica Lite
|
||||
// So we load the blocks as if everything is Felica Lite
|
||||
|
||||
if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
uint32_t blocks_total = 0;
|
||||
uint32_t blocks_read = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
data->blocks_total = (uint8_t)blocks_total;
|
||||
data->blocks_read = (uint8_t)blocks_read;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < data->blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
parsed = false;
|
||||
break;
|
||||
}
|
||||
felica_get_workflow_type(data);
|
||||
if(data_format_version == 1) {
|
||||
data->workflow_type = FelicaLite;
|
||||
}
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
if(!parsed) {
|
||||
furi_string_free(str_key_buffer);
|
||||
furi_string_free(str_data_buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(data->workflow_type) {
|
||||
case FelicaLite:
|
||||
// Blocks data
|
||||
do {
|
||||
uint32_t blocks_total = 0;
|
||||
uint32_t blocks_read = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
data->blocks_total = (uint8_t)blocks_total;
|
||||
data->blocks_read = (uint8_t)blocks_read;
|
||||
|
||||
for(uint8_t i = 0; i < data->blocks_total; i++) {
|
||||
furi_string_printf(str_data_buffer, "Block %d", i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(str_data_buffer),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
case FelicaStandard:
|
||||
// Areas
|
||||
do {
|
||||
uint32_t area_count = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break;
|
||||
simple_array_init(data->areas, area_count);
|
||||
|
||||
furi_string_reset(str_key_buffer);
|
||||
furi_string_reset(str_data_buffer);
|
||||
for(uint16_t i = 0; i < area_count; i++) {
|
||||
furi_string_printf(str_key_buffer, "Area %03X", i);
|
||||
if(!flipper_format_read_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) {
|
||||
break;
|
||||
}
|
||||
FelicaArea* area = simple_array_get(data->areas, i);
|
||||
if(sscanf(
|
||||
furi_string_get_cstr(str_data_buffer),
|
||||
"| Code %04hX | Services #%03hX-#%03hX |",
|
||||
&area->code,
|
||||
&area->first_idx,
|
||||
&area->last_idx) != 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
// Services
|
||||
do {
|
||||
uint32_t service_count = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break;
|
||||
simple_array_init(data->services, service_count);
|
||||
|
||||
furi_string_reset(str_key_buffer);
|
||||
furi_string_reset(str_data_buffer);
|
||||
for(uint16_t i = 0; i < service_count; i++) {
|
||||
furi_string_printf(str_key_buffer, "Service %03X", i);
|
||||
if(!flipper_format_read_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) {
|
||||
break;
|
||||
}
|
||||
FelicaService* service = simple_array_get(data->services, i);
|
||||
|
||||
// all unread in the beginning. reserved for future block load
|
||||
if(!sscanf(
|
||||
furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) {
|
||||
break;
|
||||
}
|
||||
service->attr = service->code & 0x3F;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
// Public blocks
|
||||
do {
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
uint32_t public_block_count = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1))
|
||||
break;
|
||||
simple_array_init(data->public_blocks, public_block_count);
|
||||
for(uint16_t i = 0; i < public_block_count; i++) {
|
||||
furi_string_printf(str_key_buffer, "Block %04X", i);
|
||||
if(!flipper_format_read_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) {
|
||||
break;
|
||||
}
|
||||
|
||||
FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i);
|
||||
if(sscanf(
|
||||
furi_string_get_cstr(str_data_buffer),
|
||||
"| Service code %04hX | Block index %02hhX |",
|
||||
&public_block->service_code,
|
||||
&public_block->block_idx) != 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t needle = furi_string_search_str(str_data_buffer, "Data: ");
|
||||
if(needle == FURI_STRING_FAILURE) {
|
||||
break;
|
||||
}
|
||||
needle += 6; // length of "Data: " = 6
|
||||
furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE);
|
||||
furi_string_replace_all(str_data_buffer, " ", "");
|
||||
if(!hex_chars_to_uint8(
|
||||
furi_string_get_cstr(str_data_buffer), public_block->block.data)) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_reset(str_data_buffer);
|
||||
for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
|
||||
furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(str_key_buffer);
|
||||
furi_string_free(str_data_buffer);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -113,8 +283,10 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
furi_check(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
FuriString* str_data_buffer = furi_string_alloc();
|
||||
FuriString* str_key_buffer = furi_string_alloc();
|
||||
do {
|
||||
// Header
|
||||
if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break;
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1))
|
||||
@@ -125,27 +297,134 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
uint32_t blocks_total = data->blocks_total;
|
||||
uint32_t blocks_read = data->blocks_read;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
|
||||
saved = true;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
felica_get_ic_name(data, str_data_buffer);
|
||||
furi_string_replace_all(str_data_buffer, "\n", " ");
|
||||
if(!flipper_format_write_string(ff, "IC Type", str_data_buffer)) break;
|
||||
if(!flipper_format_write_empty_line(ff)) break;
|
||||
} while(false);
|
||||
|
||||
switch(data->workflow_type) {
|
||||
case FelicaLite:
|
||||
if(!flipper_format_write_comment_cstr(ff, "Felica Lite specific data")) break;
|
||||
// Blocks count
|
||||
do {
|
||||
uint32_t blocks_total = data->blocks_total;
|
||||
uint32_t blocks_read = data->blocks_read;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
|
||||
// Blocks data
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint8_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(str_key_buffer, "Block %d", i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(str_key_buffer),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
|
||||
case FelicaStandard:
|
||||
if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break;
|
||||
|
||||
do {
|
||||
uint32_t area_count = simple_array_get_count(data->areas);
|
||||
uint32_t service_count = simple_array_get_count(data->services);
|
||||
// Note: The theoretical max area/service count is 2^10
|
||||
// So uint16_t is already enough for practical usage
|
||||
// The following key index print will use %03X because 12 bits are enough to cover 0-1023
|
||||
|
||||
// Area count
|
||||
if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break;
|
||||
|
||||
// Area data
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint16_t i = 0; i < area_count; i++) {
|
||||
FelicaArea* area = simple_array_get(data->areas, i);
|
||||
furi_string_printf(str_key_buffer, "Area %03X", i);
|
||||
furi_string_printf(
|
||||
str_data_buffer,
|
||||
"| Code %04X | Services #%03X-#%03X |",
|
||||
area->code,
|
||||
area->first_idx,
|
||||
area->last_idx);
|
||||
if(!flipper_format_write_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer))
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_empty_line(ff)) break;
|
||||
|
||||
// Service count
|
||||
if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break;
|
||||
|
||||
// Service data
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint16_t i = 0; i < service_count; i++) {
|
||||
FelicaService* service = simple_array_get(data->services, i);
|
||||
furi_string_printf(str_key_buffer, "Service %03X", i);
|
||||
furi_string_printf(
|
||||
str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr);
|
||||
felica_service_get_attribute_string(service, str_data_buffer);
|
||||
if(!flipper_format_write_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer))
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_empty_line(ff)) break;
|
||||
|
||||
// Directory tree
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
furi_string_printf(
|
||||
str_data_buffer, "\n::: ... are public services\n||| ... are private services");
|
||||
felica_write_directory_tree(data, str_data_buffer);
|
||||
furi_string_replace_all(str_data_buffer, ":", "+");
|
||||
// We use a clearer marker in saved text files
|
||||
if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break;
|
||||
} while(false);
|
||||
|
||||
// Public blocks
|
||||
do {
|
||||
uint32_t public_block_count = simple_array_get_count(data->public_blocks);
|
||||
if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1))
|
||||
break;
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint16_t i = 0; i < public_block_count; i++) {
|
||||
FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i);
|
||||
furi_string_printf(str_key_buffer, "Block %04X", i);
|
||||
furi_string_printf(
|
||||
str_data_buffer,
|
||||
"| Service code %04X | Block index %02X | Data: ",
|
||||
public_block->service_code,
|
||||
public_block->block_idx);
|
||||
for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
|
||||
furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]);
|
||||
}
|
||||
furi_string_cat_printf(str_data_buffer, "|");
|
||||
if(!flipper_format_write_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer))
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
furi_string_free(str_data_buffer);
|
||||
furi_string_free(str_key_buffer);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
@@ -153,7 +432,13 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
return memcmp(data, other, sizeof(FelicaData)) == 0;
|
||||
return memcmp(data->idm.data, other->idm.data, sizeof(FelicaIDm)) == 0 &&
|
||||
memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 &&
|
||||
data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read &&
|
||||
memcmp(&data->data, &other->data, sizeof(data->data)) == 0 &&
|
||||
simple_array_is_equal(data->services, other->services) &&
|
||||
simple_array_is_equal(data->areas, other->areas) &&
|
||||
simple_array_is_equal(data->public_blocks, other->public_blocks);
|
||||
}
|
||||
|
||||
const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) {
|
||||
@@ -354,3 +639,251 @@ void felica_calculate_mac_write(
|
||||
memcpy(session_swapped + 8, session_key, 8);
|
||||
felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac);
|
||||
}
|
||||
|
||||
void felica_write_directory_tree(const FelicaData* data, FuriString* str) {
|
||||
furi_check(data);
|
||||
furi_check(str);
|
||||
|
||||
furi_string_cat_str(str, "\n");
|
||||
|
||||
uint16_t area_last_stack[8];
|
||||
uint8_t depth = 0;
|
||||
|
||||
size_t area_iter = 0;
|
||||
const size_t area_count = simple_array_get_count(data->areas);
|
||||
const size_t service_count = simple_array_get_count(data->services);
|
||||
|
||||
for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) {
|
||||
while(area_iter < area_count) {
|
||||
const FelicaArea* next_area = simple_array_get(data->areas, area_iter);
|
||||
if(next_area->first_idx != svc_idx) break;
|
||||
|
||||
for(uint8_t i = 0; i < depth - 1; ++i)
|
||||
furi_string_cat_printf(str, "| ");
|
||||
furi_string_cat_printf(str, depth ? "|" : "");
|
||||
furi_string_cat_printf(str, "- AREA_%04X/\n", next_area->code >> 6);
|
||||
|
||||
area_last_stack[depth++] = next_area->last_idx;
|
||||
area_iter++;
|
||||
}
|
||||
|
||||
const FelicaService* service = simple_array_get(data->services, svc_idx);
|
||||
bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0;
|
||||
|
||||
for(uint8_t i = 0; i < depth - 1; ++i)
|
||||
furi_string_cat_printf(str, is_public ? ": " : "| ");
|
||||
furi_string_cat_printf(str, is_public ? ":" : "|");
|
||||
furi_string_cat_printf(str, "- serv_%04X\n", service->code);
|
||||
|
||||
if(depth && svc_idx >= area_last_stack[depth - 1]) depth--;
|
||||
}
|
||||
}
|
||||
|
||||
void felica_get_workflow_type(FelicaData* data) {
|
||||
// Reference: Proxmark3 repo
|
||||
uint8_t rom_type = data->pmm.data[0];
|
||||
uint8_t workflow_type = data->pmm.data[1];
|
||||
if(workflow_type <= 0x48) {
|
||||
// More liberal check because most of these should be treated as FeliCa Standard, regardless of mobile or not.
|
||||
data->workflow_type = FelicaStandard;
|
||||
} else {
|
||||
switch(workflow_type) {
|
||||
case 0xA2:
|
||||
data->workflow_type = FelicaStandard;
|
||||
break;
|
||||
case 0xF0:
|
||||
case 0xF1:
|
||||
case 0xF2: // 0xF2 => FeliCa Link RC-S967 in Lite-S Mode or Lite-S HT Mode
|
||||
data->workflow_type = FelicaLite;
|
||||
break;
|
||||
case 0xE1: // Felica Link
|
||||
case 0xE0: // Felica Plug
|
||||
data->workflow_type = FelicaUnknown;
|
||||
break;
|
||||
case 0xFF:
|
||||
if(rom_type == 0xFF) {
|
||||
data->workflow_type = FelicaUnknown; // Felica Link
|
||||
}
|
||||
break;
|
||||
default:
|
||||
data->workflow_type = FelicaUnknown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void felica_get_ic_name(const FelicaData* data, FuriString* ic_name) {
|
||||
// Reference: Proxmark3 repo
|
||||
uint8_t rom_type = data->pmm.data[0];
|
||||
uint8_t ic_type = data->pmm.data[1];
|
||||
|
||||
switch(ic_type) {
|
||||
// FeliCa Standard Products:
|
||||
// odd findings
|
||||
case 0x00:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S830");
|
||||
break;
|
||||
case 0x01:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S915");
|
||||
break;
|
||||
case 0x02:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S919");
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V1.0");
|
||||
break;
|
||||
case 0x08:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S952");
|
||||
break;
|
||||
case 0x09:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S953");
|
||||
break;
|
||||
case 0x0B:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S9X4,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x0C:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S954");
|
||||
break;
|
||||
case 0x0D:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S960");
|
||||
break;
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V2.0");
|
||||
break;
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V3.0");
|
||||
break;
|
||||
case 0x16:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x17:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.0");
|
||||
break;
|
||||
case 0x18:
|
||||
case 0x19:
|
||||
case 0x1A:
|
||||
case 0x1B:
|
||||
case 0x1C:
|
||||
case 0x1D:
|
||||
case 0x1E:
|
||||
case 0x1F:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.1");
|
||||
break;
|
||||
case 0x20:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S962");
|
||||
// RC-S962 has been extensively found in Japan Transit ICs, despite model number not ending in 4
|
||||
break;
|
||||
case 0x31:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S104,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x32:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/1");
|
||||
break;
|
||||
case 0x33:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/2");
|
||||
break;
|
||||
case 0x34:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/1");
|
||||
break;
|
||||
case 0x35:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/2");
|
||||
break;
|
||||
case 0x36:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA04/1,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x3E:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA08/1");
|
||||
break;
|
||||
case 0x43:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1");
|
||||
break;
|
||||
case 0x44:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/1");
|
||||
break;
|
||||
case 0x45:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/2");
|
||||
break;
|
||||
case 0x46:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2");
|
||||
break;
|
||||
case 0x47:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1x1");
|
||||
break;
|
||||
case 0x48:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2x1");
|
||||
break;
|
||||
case 0xA2:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA14");
|
||||
break;
|
||||
// NFC Dynamic Tag (FeliCa Plug) Products:
|
||||
case 0xE0:
|
||||
furi_string_set_str(ic_name, "FeliCa Plug RC-S926,\nNFC Dynamic Tag");
|
||||
break;
|
||||
case 0xE1:
|
||||
furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nPlug Mode");
|
||||
break;
|
||||
case 0xF0:
|
||||
furi_string_set_str(ic_name, "FeliCa Lite RC-S965");
|
||||
break;
|
||||
case 0xF1:
|
||||
furi_string_set_str(ic_name, "FeliCa Lite-S RC-S966");
|
||||
break;
|
||||
case 0xF2:
|
||||
furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nLite-S Mode or Lite-S HT Mode");
|
||||
break;
|
||||
case 0xFF:
|
||||
if(rom_type == 0xFF) { // from FeliCa Link User's Manual
|
||||
furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nNFC-DEP Mode");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
furi_string_printf(
|
||||
ic_name,
|
||||
"Unknown IC %02X ROM %02X:\nPlease submit an issue on\nGitHub and help us identify.",
|
||||
ic_type,
|
||||
rom_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void felica_service_get_attribute_string(const FelicaService* service, FuriString* str) {
|
||||
furi_check(service);
|
||||
furi_check(str);
|
||||
|
||||
bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0;
|
||||
furi_string_cat_str(str, is_public ? "| Public " : "| Private ");
|
||||
|
||||
bool is_purse = (service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE) != 0;
|
||||
// Subfield bitwise attributes are applicable depending on is PURSE or not
|
||||
|
||||
if(is_purse) {
|
||||
furi_string_cat_str(str, "| Purse |");
|
||||
switch((service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD) >> 1) {
|
||||
case 0:
|
||||
furi_string_cat_str(str, " Direct |");
|
||||
break;
|
||||
case 1:
|
||||
furi_string_cat_str(str, " Cashback |");
|
||||
break;
|
||||
case 2:
|
||||
furi_string_cat_str(str, " Decrement |");
|
||||
break;
|
||||
case 3:
|
||||
furi_string_cat_str(str, " Read Only |");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_str(str, " Unknown |");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
bool is_random = (service->attr & FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS) != 0;
|
||||
furi_string_cat_str(str, is_random ? "| Random |" : "| Cyclic |");
|
||||
bool is_readonly = (service->attr & FELICA_SERVICE_ATTRIBUTE_READ_ONLY) != 0;
|
||||
furi_string_cat_str(str, is_readonly ? " Read Only |" : " Read/Write |");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <toolbox/bit_buffer.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
#include <mbedtls/include/mbedtls/des.h>
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -34,6 +35,8 @@ extern "C" {
|
||||
#define FELICA_BLOCK_INDEX_STATE (0x92U)
|
||||
#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U)
|
||||
|
||||
#define FELICA_STANDARD_MAX_BLOCK_COUNT (0xFFU)
|
||||
|
||||
#define FELICA_GUARD_TIME_US (20000U)
|
||||
#define FELICA_FDT_POLL_FC (10000U)
|
||||
#define FELICA_POLL_POLL_MIN_US (1280U)
|
||||
@@ -47,6 +50,16 @@ extern "C" {
|
||||
#define FELICA_TIME_SLOT_8 (0x07U)
|
||||
#define FELICA_TIME_SLOT_16 (0x0FU)
|
||||
|
||||
#define FELICA_CMD_LIST_SERVICE_CODE 0x0A
|
||||
#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B
|
||||
|
||||
#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_CYCLIC (0b001100)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_PURSE (0b010000)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD (0b000110)
|
||||
|
||||
/** @brief Type of possible Felica errors */
|
||||
typedef enum {
|
||||
FelicaErrorNone,
|
||||
@@ -61,6 +74,12 @@ typedef enum {
|
||||
FelicaErrorFeatureUnsupported,
|
||||
} FelicaError;
|
||||
|
||||
typedef enum {
|
||||
FelicaUnknown,
|
||||
FelicaStandard,
|
||||
FelicaLite,
|
||||
} FelicaWorkflowType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaBlockData;
|
||||
@@ -146,6 +165,23 @@ typedef union {
|
||||
uint8_t dump[sizeof(FelicaFileSystem)];
|
||||
} FelicaFSUnion;
|
||||
|
||||
typedef struct {
|
||||
uint16_t code;
|
||||
uint8_t attr;
|
||||
} FelicaService;
|
||||
|
||||
typedef struct {
|
||||
uint16_t code;
|
||||
uint16_t first_idx;
|
||||
uint16_t last_idx;
|
||||
} FelicaArea;
|
||||
|
||||
typedef struct {
|
||||
FelicaBlock block;
|
||||
uint16_t service_code;
|
||||
uint8_t block_idx;
|
||||
} FelicaPublicBlock;
|
||||
|
||||
/** @brief Structure used to store Felica data and additional values about reading */
|
||||
typedef struct {
|
||||
FelicaIDm idm;
|
||||
@@ -153,17 +189,20 @@ typedef struct {
|
||||
uint8_t blocks_total;
|
||||
uint8_t blocks_read;
|
||||
FelicaFSUnion data;
|
||||
|
||||
SimpleArray* services;
|
||||
SimpleArray* areas;
|
||||
SimpleArray* public_blocks;
|
||||
FelicaWorkflowType workflow_type;
|
||||
} FelicaData;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
typedef struct FURI_PACKED {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
uint8_t service_num;
|
||||
uint16_t service_code;
|
||||
uint8_t block_count;
|
||||
} FelicaCommandHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
@@ -173,6 +212,14 @@ typedef struct {
|
||||
uint8_t SF2;
|
||||
} FelicaCommandResponseHeader;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t command;
|
||||
FelicaIDm idm;
|
||||
} FelicaCommandHeaderRaw;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t service_code : 4;
|
||||
uint8_t access_mode : 3;
|
||||
@@ -196,6 +243,11 @@ typedef struct {
|
||||
uint8_t data[];
|
||||
} FelicaListenerReadCommandResponse;
|
||||
|
||||
typedef struct {
|
||||
FelicaCommandHeaderRaw header;
|
||||
uint8_t data[];
|
||||
} FelicaListServiceCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse;
|
||||
@@ -256,6 +308,15 @@ void felica_calculate_mac_write(
|
||||
const uint8_t* wcnt,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac);
|
||||
|
||||
void felica_write_directory_tree(const FelicaData* data, FuriString* str);
|
||||
|
||||
void felica_get_workflow_type(FelicaData* data);
|
||||
|
||||
void felica_get_ic_name(const FelicaData* data, FuriString* ic_name);
|
||||
|
||||
void felica_service_get_attribute_string(const FelicaService* service, FuriString* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "felica_i.h"
|
||||
|
||||
const SimpleArrayConfig felica_service_array_cfg = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(FelicaService),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig felica_area_array_cfg = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(FelicaArea),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig felica_public_block_array_cfg = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(FelicaPublicBlock),
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "felica.h"
|
||||
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
extern const SimpleArrayConfig felica_service_array_cfg;
|
||||
extern const SimpleArrayConfig felica_area_array_cfg;
|
||||
extern const SimpleArrayConfig felica_public_block_array_cfg;
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "felica_poller_i.h"
|
||||
#include <mlib/m-array.h>
|
||||
#include <mlib/m-core.h>
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
@@ -7,6 +9,10 @@
|
||||
|
||||
#define TAG "FelicaPoller"
|
||||
|
||||
ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658
|
||||
ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658
|
||||
ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658
|
||||
|
||||
typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance);
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance) {
|
||||
@@ -79,15 +85,30 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) {
|
||||
FelicaError error = felica_poller_activate(instance, instance->data);
|
||||
if(error == FelicaErrorNone) {
|
||||
furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE);
|
||||
felica_get_workflow_type(instance->data);
|
||||
|
||||
instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext;
|
||||
instance->felica_event_data.auth_context = &instance->auth.context;
|
||||
|
||||
instance->callback(instance->general_event, instance->context);
|
||||
|
||||
switch(instance->data->workflow_type) {
|
||||
case FelicaStandard:
|
||||
instance->state = FelicaPollerStateTraverseStandardSystem;
|
||||
break;
|
||||
case FelicaLite:
|
||||
instance->state = FelicaPollerStateReadLiteBlocks;
|
||||
break;
|
||||
default:
|
||||
// Unimplemented
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
bool skip_auth = instance->auth.context.skip_auth;
|
||||
instance->state = skip_auth ? FelicaPollerStateReadBlocks :
|
||||
FelicaPollerStateAuthenticateInternal;
|
||||
if(!skip_auth) {
|
||||
instance->state = FelicaPollerStateAuthenticateInternal;
|
||||
}
|
||||
} else if(error != FelicaErrorTimeout) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
@@ -105,7 +126,18 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) {
|
||||
instance->data->data.fs.rc.data,
|
||||
instance->auth.session_key.data);
|
||||
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
switch(instance->data->workflow_type) {
|
||||
case FelicaStandard:
|
||||
instance->state = FelicaPollerStateTraverseStandardSystem;
|
||||
break;
|
||||
case FelicaLite:
|
||||
instance->state = FelicaPollerStateReadLiteBlocks;
|
||||
break;
|
||||
default:
|
||||
// Unimplemented
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0};
|
||||
FelicaPollerWriteCommandResponse* tx_resp;
|
||||
@@ -145,7 +177,6 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) {
|
||||
|
||||
NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Auth External");
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
uint8_t blocks[2];
|
||||
|
||||
instance->data->data.fs.state.data[0] = 1;
|
||||
@@ -183,12 +214,177 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) {
|
||||
memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE);
|
||||
instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0];
|
||||
} while(false);
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
|
||||
switch(instance->data->workflow_type) {
|
||||
case FelicaStandard:
|
||||
instance->state = FelicaPollerStateTraverseStandardSystem;
|
||||
break;
|
||||
case FelicaLite:
|
||||
instance->state = FelicaPollerStateReadLiteBlocks;
|
||||
break;
|
||||
default:
|
||||
// Unimplemented
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Blocks");
|
||||
NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Traverse Standard System");
|
||||
|
||||
FelicaListServiceCommandResponse* response;
|
||||
|
||||
felica_service_array_t service_buffer;
|
||||
felica_service_array_init(service_buffer);
|
||||
felica_area_array_t area_buffer;
|
||||
felica_area_array_init(area_buffer);
|
||||
|
||||
for(uint16_t cursor = 0; cursor < 0xFFFF; cursor++) {
|
||||
FelicaError error = felica_poller_list_service_by_cursor(instance, cursor, &response);
|
||||
if(error != FelicaErrorNone) {
|
||||
FURI_LOG_E(TAG, "Error %d at cursor %04X", error, cursor);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t len = response->header.length;
|
||||
const uint8_t* list_service_payload = response->data;
|
||||
uint16_t code_begin = (uint16_t)(list_service_payload[0] | (list_service_payload[1] << 8));
|
||||
|
||||
if(len != 0x0C && len != 0x0E) {
|
||||
FURI_LOG_E(TAG, "Bad command resp length 0x%02X at cursor 0x%04X", len, cursor);
|
||||
break;
|
||||
}
|
||||
|
||||
if(code_begin == 0xFFFF) {
|
||||
FURI_LOG_D(TAG, "Traverse complete");
|
||||
break;
|
||||
}
|
||||
|
||||
if(len == 0x0E) {
|
||||
FelicaArea* area = felica_area_array_push_raw(area_buffer);
|
||||
memset(area, 0, sizeof *area);
|
||||
area->code = code_begin;
|
||||
area->first_idx = (uint16_t)felica_service_array_size(service_buffer);
|
||||
area->last_idx = 0;
|
||||
} else {
|
||||
FelicaService* service = felica_service_array_push_raw(service_buffer);
|
||||
memset(service, 0, sizeof *service);
|
||||
service->code = code_begin;
|
||||
service->attr = (uint8_t)(code_begin & 0x3F);
|
||||
|
||||
if(felica_area_array_size(area_buffer)) {
|
||||
FelicaArea* current_area = felica_area_array_back(area_buffer);
|
||||
current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t service_num = felica_service_array_size(service_buffer);
|
||||
const size_t area_num = felica_area_array_size(area_buffer);
|
||||
|
||||
if(service_num) {
|
||||
simple_array_init(instance->data->services, (uint32_t)service_num);
|
||||
memcpy(
|
||||
simple_array_get(instance->data->services, 0),
|
||||
service_buffer->ptr,
|
||||
service_num * sizeof(FelicaService));
|
||||
} else {
|
||||
simple_array_reset(instance->data->services);
|
||||
}
|
||||
|
||||
if(area_num) {
|
||||
simple_array_init(instance->data->areas, (uint32_t)area_num);
|
||||
memcpy(
|
||||
simple_array_get(instance->data->areas, 0),
|
||||
area_buffer->ptr,
|
||||
area_num * sizeof(FelicaArea));
|
||||
} else {
|
||||
simple_array_reset(instance->data->areas);
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Services found: %lu, Areas found: %lu",
|
||||
simple_array_get_count(instance->data->services),
|
||||
simple_array_get_count(instance->data->areas));
|
||||
|
||||
felica_service_array_clear(service_buffer);
|
||||
felica_area_array_clear(area_buffer);
|
||||
|
||||
instance->state = FelicaPollerStateReadStandardBlocks;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Standard Blocks");
|
||||
|
||||
const uint32_t service_count = simple_array_get_count(instance->data->services);
|
||||
|
||||
felica_public_block_array_t public_block_buffer;
|
||||
felica_public_block_array_init(public_block_buffer);
|
||||
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
bool have_read_anything = false;
|
||||
|
||||
for(uint32_t i = 0; i < service_count; i++) {
|
||||
const FelicaService* service = simple_array_get(instance->data->services, i);
|
||||
|
||||
if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue;
|
||||
|
||||
uint8_t block_count = 1;
|
||||
uint8_t block_list[1] = {0};
|
||||
FelicaError error = FelicaErrorNone;
|
||||
FelicaPollerReadCommandResponse* response;
|
||||
do {
|
||||
error = felica_poller_read_blocks(
|
||||
instance, block_count, block_list, service->code, &response);
|
||||
|
||||
if(error != FelicaErrorNone) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(response->SF1 == 0 && response->SF2 == 0) {
|
||||
FelicaPublicBlock* public_block =
|
||||
felica_public_block_array_push_raw(public_block_buffer);
|
||||
memset(public_block, 0, sizeof *public_block);
|
||||
memcpy(public_block->block.data, response->data, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
public_block->service_code = service->code;
|
||||
public_block->block_idx = block_list[0];
|
||||
|
||||
have_read_anything = true;
|
||||
block_list[0]++;
|
||||
} else {
|
||||
break; // No more blocks to read in this service, ok to continue for loop
|
||||
}
|
||||
} while(block_list[0] < FELICA_STANDARD_MAX_BLOCK_COUNT);
|
||||
|
||||
if(error != FelicaErrorNone) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
instance->state = FelicaPollerStateReadFailed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(have_read_anything) {
|
||||
const size_t n = felica_public_block_array_size(public_block_buffer);
|
||||
simple_array_init(instance->data->public_blocks, (uint32_t)n);
|
||||
memcpy(
|
||||
simple_array_get(instance->data->public_blocks, 0),
|
||||
public_block_buffer->ptr,
|
||||
n * sizeof(FelicaPublicBlock));
|
||||
}
|
||||
|
||||
felica_public_block_array_clear(public_block_buffer);
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Lite Blocks");
|
||||
|
||||
uint8_t block_count = 1;
|
||||
uint8_t block_list[4] = {0, 0, 0, 0};
|
||||
@@ -266,7 +462,10 @@ static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum]
|
||||
[FelicaPollerStateActivated] = felica_poller_state_handler_activate,
|
||||
[FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal,
|
||||
[FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external,
|
||||
[FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks,
|
||||
[FelicaPollerStateTraverseStandardSystem] =
|
||||
felica_poller_state_handler_traverse_standard_system,
|
||||
[FelicaPollerStateReadStandardBlocks] = felica_poller_state_handler_read_standard_blocks,
|
||||
[FelicaPollerStateReadLiteBlocks] = felica_poller_state_handler_read_lite_blocks,
|
||||
[FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success,
|
||||
[FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed,
|
||||
};
|
||||
|
||||
@@ -93,6 +93,7 @@ FelicaError felica_poller_polling(
|
||||
return error;
|
||||
}
|
||||
|
||||
// This is in fact a buffer preparer for a specified service. It should be have the _ex suffix. The prepare_tx_buffer_raw should have this name.
|
||||
static void felica_poller_prepare_tx_buffer(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t command,
|
||||
@@ -221,3 +222,45 @@ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void felica_poller_prepare_tx_buffer_raw(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t command,
|
||||
const uint8_t* data,
|
||||
const uint8_t data_length) {
|
||||
FelicaCommandHeaderRaw cmd = {.length = 0x00, .command = command, .idm = instance->data->idm};
|
||||
|
||||
cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length;
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, data, data_length);
|
||||
}
|
||||
|
||||
FelicaError felica_poller_list_service_by_cursor(
|
||||
FelicaPoller* instance,
|
||||
uint16_t cursor,
|
||||
FelicaListServiceCommandResponse** const response_ptr) {
|
||||
furi_assert(instance);
|
||||
furi_assert(response_ptr);
|
||||
|
||||
const uint8_t data[2] = {(uint8_t)(cursor & 0xFF), (uint8_t)((cursor >> 8) & 0xFF)};
|
||||
|
||||
felica_poller_prepare_tx_buffer_raw(
|
||||
instance, FELICA_CMD_LIST_SERVICE_CODE, data, sizeof(data));
|
||||
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
FelicaError error = felica_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT);
|
||||
if(error != FelicaErrorNone) {
|
||||
FURI_LOG_E(TAG, "List service by cursor failed with error: %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer);
|
||||
if(rx_len < sizeof(FelicaCommandHeaderRaw) + 2) return FelicaErrorProtocol;
|
||||
|
||||
// error is known to be FelicaErrorNone here
|
||||
*response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ typedef enum {
|
||||
FelicaPollerStateActivated,
|
||||
FelicaPollerStateAuthenticateInternal,
|
||||
FelicaPollerStateAuthenticateExternal,
|
||||
FelicaPollerStateReadBlocks,
|
||||
FelicaPollerStateTraverseStandardSystem,
|
||||
FelicaPollerStateReadStandardBlocks,
|
||||
FelicaPollerStateReadLiteBlocks,
|
||||
FelicaPollerStateReadSuccess,
|
||||
FelicaPollerStateReadFailed,
|
||||
|
||||
@@ -55,6 +57,10 @@ typedef struct {
|
||||
uint8_t request_data[2];
|
||||
} FelicaPollerPollingResponse;
|
||||
|
||||
typedef union {
|
||||
FelicaData* data;
|
||||
} FelicaPollerContextData;
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
||||
|
||||
/**
|
||||
@@ -105,6 +111,11 @@ FelicaError felica_poller_frame_exchange(
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
FelicaError felica_poller_list_service_by_cursor(
|
||||
FelicaPoller* instance,
|
||||
uint16_t cursor,
|
||||
FelicaListServiceCommandResponse** response_ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -11,8 +11,8 @@ typedef struct {
|
||||
FelicaAuthenticationContext auth_ctx;
|
||||
FuriThreadId thread_id;
|
||||
FelicaError error;
|
||||
FelicaData data;
|
||||
} Felica_PollerContext;
|
||||
FelicaPollerContextData data;
|
||||
} FelicaPollerContext;
|
||||
|
||||
NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
@@ -20,14 +20,14 @@ NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolFelica);
|
||||
|
||||
Felica_PollerContext* poller_context = context;
|
||||
FelicaPollerContext* poller_context = context;
|
||||
FelicaPoller* felica_poller = event.instance;
|
||||
|
||||
FelicaPollerEvent* felica_event = event.event_data;
|
||||
|
||||
if(felica_event->type == FelicaPollerEventTypeReady ||
|
||||
felica_event->type == FelicaPollerEventTypeIncomplete) {
|
||||
felica_copy(&poller_context->data, felica_poller->data);
|
||||
felica_copy(poller_context->data.data, felica_poller->data);
|
||||
} else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
|
||||
felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth;
|
||||
memcpy(
|
||||
@@ -45,7 +45,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard
|
||||
furi_check(nfc);
|
||||
furi_check(data);
|
||||
|
||||
Felica_PollerContext poller_context = {};
|
||||
FelicaPollerContext poller_context = {};
|
||||
if(card_key == NULL) {
|
||||
poller_context.auth_ctx.skip_auth = true;
|
||||
} else {
|
||||
@@ -54,6 +54,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard
|
||||
}
|
||||
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
poller_context.data.data = felica_alloc();
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolFelica);
|
||||
nfc_poller_start(poller, felica_poller_read_callback, &poller_context);
|
||||
furi_thread_flags_wait(FELICA_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever);
|
||||
@@ -63,8 +64,10 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(poller_context.error == FelicaErrorNone) {
|
||||
*data = poller_context.data;
|
||||
felica_copy(data, poller_context.data.data);
|
||||
}
|
||||
|
||||
felica_free(poller_context.data.data);
|
||||
|
||||
return poller_context.error;
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller*
|
||||
instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version);
|
||||
instance->state = MfUltralightPollerStateGetFeatureSet;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Didn't response. Check Ultralight C");
|
||||
FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C");
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
instance->state = MfUltralightPollerStateDetectMfulC;
|
||||
}
|
||||
@@ -266,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo
|
||||
instance->data->type = MfUltralightTypeMfulC;
|
||||
instance->state = MfUltralightPollerStateGetFeatureSet;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Didn't response. Check NTAG 203");
|
||||
FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203");
|
||||
instance->state = MfUltralightPollerStateDetectNtag203;
|
||||
}
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
@@ -452,7 +452,48 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfu_event.data->auth_context.skip_auth) {
|
||||
FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
|
||||
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
|
||||
// Only use the key if it was actually provided
|
||||
if(instance->mfu_event.data->key_request_data.key_provided) {
|
||||
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
|
||||
} else if(instance->mode == MfUltralightPollerModeDictAttack) {
|
||||
// TODO: -nofl Can logic be rearranged to request this key
|
||||
// before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
|
||||
FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary");
|
||||
// Trigger dictionary key request
|
||||
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfu_event.data->key_request_data.key_provided) {
|
||||
instance->state = MfUltralightPollerStateReadPages;
|
||||
return command;
|
||||
} else {
|
||||
instance->auth_context.tdes_key =
|
||||
instance->mfu_event.data->key_request_data.key;
|
||||
}
|
||||
} else {
|
||||
// Fallback: use key from auth context (for sync poller compatibility)
|
||||
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
|
||||
}
|
||||
instance->auth_context.auth_success = false;
|
||||
// For debugging
|
||||
FURI_LOG_D(
|
||||
"TAG",
|
||||
"Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
instance->auth_context.tdes_key.data[0],
|
||||
instance->auth_context.tdes_key.data[1],
|
||||
instance->auth_context.tdes_key.data[2],
|
||||
instance->auth_context.tdes_key.data[3],
|
||||
instance->auth_context.tdes_key.data[4],
|
||||
instance->auth_context.tdes_key.data[5],
|
||||
instance->auth_context.tdes_key.data[6],
|
||||
instance->auth_context.tdes_key.data[7],
|
||||
instance->auth_context.tdes_key.data[8],
|
||||
instance->auth_context.tdes_key.data[9],
|
||||
instance->auth_context.tdes_key.data[10],
|
||||
instance->auth_context.tdes_key.data[11],
|
||||
instance->auth_context.tdes_key.data[12],
|
||||
instance->auth_context.tdes_key.data[13],
|
||||
instance->auth_context.tdes_key.data[14],
|
||||
instance->auth_context.tdes_key.data[15]);
|
||||
do {
|
||||
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
|
||||
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
|
||||
@@ -469,20 +510,40 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
|
||||
mf_ultralight_3des_shift_data(RndA);
|
||||
instance->auth_context.auth_success =
|
||||
(memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
|
||||
|
||||
if(instance->auth_context.auth_success) {
|
||||
FURI_LOG_D(TAG, "Auth success");
|
||||
FURI_LOG_E(TAG, "Auth success");
|
||||
if(instance->mode == MfUltralightPollerModeDictAttack) {
|
||||
memcpy(
|
||||
&instance->data->page[44],
|
||||
instance->auth_context.tdes_key.data,
|
||||
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
|
||||
// Continue to read pages after successful authentication
|
||||
instance->state = MfUltralightPollerStateReadPages;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) {
|
||||
FURI_LOG_D(TAG, "Auth failed");
|
||||
FURI_LOG_E(TAG, "Auth failed");
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
if(instance->mode == MfUltralightPollerModeDictAttack) {
|
||||
// Not needed? We already do a callback earlier?
|
||||
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfu_event.data->key_request_data.key_provided) {
|
||||
instance->state = MfUltralightPollerStateReadPages;
|
||||
} else {
|
||||
instance->auth_context.tdes_key =
|
||||
instance->mfu_event.data->key_request_data.key;
|
||||
instance->state = MfUltralightPollerStateAuthMfulC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->state = MfUltralightPollerStateReadPages;
|
||||
|
||||
// Regression review
|
||||
if(instance->mode != MfUltralightPollerModeDictAttack) {
|
||||
instance->state = MfUltralightPollerStateReadPages;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
@@ -505,12 +566,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
|
||||
instance->error = mf_ultralight_poller_read_page(instance, start_page, &data);
|
||||
}
|
||||
|
||||
// Regression review
|
||||
const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4;
|
||||
if(instance->error == MfUltralightErrorNone) {
|
||||
if(start_page < instance->pages_total) {
|
||||
FURI_LOG_D(TAG, "Read page %d success", start_page);
|
||||
instance->data->page[start_page] = data.page[0];
|
||||
instance->pages_read++;
|
||||
instance->data->pages_read = instance->pages_read;
|
||||
for(size_t i = 0; i < read_cnt; i++) {
|
||||
if(start_page + i < instance->pages_total) {
|
||||
FURI_LOG_D(TAG, "Read page %d success", start_page + i);
|
||||
instance->data->page[start_page + i] = data.page[i];
|
||||
instance->pages_read++;
|
||||
instance->data->pages_read = instance->pages_read;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->pages_read == instance->pages_total) {
|
||||
@@ -753,7 +818,6 @@ static const MfUltralightPollerReadHandler
|
||||
[MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages,
|
||||
[MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail,
|
||||
[MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success,
|
||||
|
||||
};
|
||||
|
||||
static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) {
|
||||
|
||||
@@ -27,6 +27,7 @@ typedef enum {
|
||||
MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */
|
||||
MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */
|
||||
MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */
|
||||
MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */
|
||||
} MfUltralightPollerEventType;
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,7 @@ typedef enum {
|
||||
typedef enum {
|
||||
MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */
|
||||
MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */
|
||||
MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */
|
||||
} MfUltralightPollerMode;
|
||||
|
||||
/**
|
||||
@@ -42,20 +44,29 @@ typedef enum {
|
||||
*/
|
||||
typedef struct {
|
||||
MfUltralightAuthPassword password; /**< Password to be used for authentication. */
|
||||
MfUltralightC3DesAuthKey tdes_key;
|
||||
MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */
|
||||
MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */
|
||||
MfUltralightAuthPack pack; /**< Pack received on successful authentication. */
|
||||
bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */
|
||||
bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */
|
||||
} MfUltralightPollerAuthContext;
|
||||
|
||||
/**
|
||||
* @brief MfUltralight poller key request data.
|
||||
*/
|
||||
typedef struct {
|
||||
MfUltralightC3DesAuthKey key; /**< Key to try. */
|
||||
bool key_provided; /**< Set to true if key was provided, false to stop attack. */
|
||||
} MfUltralightPollerKeyRequestData;
|
||||
|
||||
/**
|
||||
* @brief MfUltralight poller event data.
|
||||
*/
|
||||
typedef union {
|
||||
MfUltralightPollerAuthContext auth_context; /**< Authentication context. */
|
||||
MfUltralightError error; /**< Error code indicating reading fail reason. */
|
||||
const MfUltralightData* write_data;
|
||||
MfUltralightPollerMode poller_mode;
|
||||
const MfUltralightData* write_data; /**< Data to be written to card. */
|
||||
MfUltralightPollerMode poller_mode; /**< Mode to operate in. */
|
||||
MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */
|
||||
} MfUltralightPollerEventData;
|
||||
|
||||
/**
|
||||
@@ -64,7 +75,7 @@ typedef union {
|
||||
* Upon emission of an event, an instance of this struct will be passed to the callback.
|
||||
*/
|
||||
typedef struct {
|
||||
MfUltralightPollerEventType type; /**< Type of emmitted event. */
|
||||
MfUltralightPollerEventType type; /**< Type of emitted event. */
|
||||
MfUltralightPollerEventData* data; /**< Pointer to event specific data. */
|
||||
} MfUltralightPollerEvent;
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start(
|
||||
uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
|
||||
mf_ultralight_3des_decrypt(
|
||||
&instance->des_context,
|
||||
instance->mfu_event.data->auth_context.tdes_key.data,
|
||||
instance->auth_context.tdes_key.data,
|
||||
iv,
|
||||
encRndB,
|
||||
sizeof(encRndB),
|
||||
@@ -145,7 +145,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start(
|
||||
|
||||
mf_ultralight_3des_encrypt(
|
||||
&instance->des_context,
|
||||
instance->mfu_event.data->auth_context.tdes_key.data,
|
||||
instance->auth_context.tdes_key.data,
|
||||
encRndB,
|
||||
output,
|
||||
MF_ULTRALIGHT_C_AUTH_DATA_SIZE,
|
||||
@@ -179,7 +179,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end(
|
||||
|
||||
mf_ultralight_3des_decrypt(
|
||||
&instance->des_context,
|
||||
instance->mfu_event.data->auth_context.tdes_key.data,
|
||||
instance->auth_context.tdes_key.data,
|
||||
RndB,
|
||||
bit_buffer_get_data(instance->rx_buffer) + 1,
|
||||
MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE,
|
||||
|
||||
@@ -248,7 +248,8 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
|
||||
poller_context->error = MfUltralightErrorNone;
|
||||
command = NfcCommandStop;
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) {
|
||||
poller_context->error = mfu_event->data->error;
|
||||
poller_context->error = mf_ultralight_process_error(
|
||||
mfu_poller->iso14443_3a_poller->iso14443_3a_event_data.error);
|
||||
command = NfcCommandStop;
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
|
||||
if(poller_context->auth_context != NULL) {
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
#include "one_wire_host.h"
|
||||
|
||||
typedef struct {
|
||||
uint16_t a;
|
||||
uint16_t b;
|
||||
uint16_t c;
|
||||
uint16_t d;
|
||||
uint16_t e;
|
||||
uint16_t f;
|
||||
uint16_t g;
|
||||
uint16_t h;
|
||||
uint16_t i;
|
||||
uint16_t j;
|
||||
uint16_t a; // Write 1 low time
|
||||
uint16_t b; // Write 1 high time
|
||||
uint16_t c; // Write 0 low time
|
||||
uint16_t d; // Write 0 high time
|
||||
uint16_t e; // Read low time
|
||||
uint16_t f; // Read high time
|
||||
uint16_t g; // Reset pre-delay
|
||||
uint16_t h; // Reset pulse
|
||||
uint16_t i; // Presence detect
|
||||
uint16_t j; // Reset post-delay
|
||||
} OneWireHostTimings;
|
||||
|
||||
static const OneWireHostTimings onewire_host_timings_normal = {
|
||||
@@ -46,6 +46,20 @@ static const OneWireHostTimings onewire_host_timings_overdrive = {
|
||||
.j = 40,
|
||||
};
|
||||
|
||||
// TM01x specific timings
|
||||
static const OneWireHostTimings onewire_host_timings_tm01x = {
|
||||
.a = 5,
|
||||
.b = 80,
|
||||
.c = 70,
|
||||
.d = 10,
|
||||
.e = 5,
|
||||
.f = 70,
|
||||
.g = 0,
|
||||
.h = 740,
|
||||
.i = 140,
|
||||
.j = 410,
|
||||
};
|
||||
|
||||
struct OneWireHost {
|
||||
const GpioPin* gpio_pin;
|
||||
const OneWireHostTimings* timings;
|
||||
@@ -354,3 +368,15 @@ void onewire_host_set_overdrive(OneWireHost* host, bool set) {
|
||||
|
||||
host->timings = set ? &onewire_host_timings_overdrive : &onewire_host_timings_normal;
|
||||
}
|
||||
|
||||
void onewire_host_set_timings_default(OneWireHost* host) {
|
||||
furi_check(host);
|
||||
|
||||
host->timings = &onewire_host_timings_normal;
|
||||
}
|
||||
|
||||
void onewire_host_set_timings_tm01x(OneWireHost* host) {
|
||||
furi_check(host);
|
||||
|
||||
host->timings = &onewire_host_timings_tm01x;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,10 @@ bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearch
|
||||
*/
|
||||
void onewire_host_set_overdrive(OneWireHost* host, bool set);
|
||||
|
||||
void onewire_host_set_timings_default(OneWireHost* host);
|
||||
|
||||
void onewire_host_set_timings_tm01x(OneWireHost* host);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech at-4n"
|
||||
#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech AT-4N"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderAlutech_at_4n SubGhzProtocolDecoderAlutech_at_4n;
|
||||
typedef struct SubGhzProtocolEncoderAlutech_at_4n SubGhzProtocolEncoderAlutech_at_4n;
|
||||
|
||||
@@ -244,8 +244,11 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat
|
||||
switch(instance->decoder.parser_step) {
|
||||
case CameDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 56) <
|
||||
subghz_protocol_came_const.te_delta * 47)) {
|
||||
subghz_protocol_came_const.te_delta * 63)) {
|
||||
// 17920 us + 7050 us = 24970 us max possible value old one
|
||||
// delta = 150 us x 63 = 9450 us + 17920 us = 27370 us max possible value
|
||||
//Found header CAME
|
||||
// 26700 us or 24000 us max possible values
|
||||
instance->decoder.parser_step = CameDecoderStepFoundStartBit;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -260,6 +260,7 @@ SubGhzProtocolStatus
|
||||
|
||||
subghz_protocol_came_twee_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_came_twee_get_upload(instance);
|
||||
instance->encoder.front = 0; // reset position before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -269,6 +270,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_came_twee_stop(void* context) {
|
||||
SubGhzProtocolEncoderCameTwee* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) {
|
||||
|
||||
@@ -308,7 +308,7 @@ void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t dura
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
/*
|
||||
* serial s/m ch key
|
||||
* long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011
|
||||
@@ -416,7 +416,7 @@ void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output)
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDooya* instance = context;
|
||||
|
||||
subghz_protocol_somfy_telis_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_dooya_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
#include "feron.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolFeron"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_feron_const = {
|
||||
.te_short = 350,
|
||||
.te_long = 750,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 32,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderFeron {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderFeron {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FeronDecoderStepReset = 0,
|
||||
FeronDecoderStepSaveDuration,
|
||||
FeronDecoderStepCheckDuration,
|
||||
} FeronDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_feron_decoder = {
|
||||
.alloc = subghz_protocol_decoder_feron_alloc,
|
||||
.free = subghz_protocol_decoder_feron_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_feron_feed,
|
||||
.reset = subghz_protocol_decoder_feron_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_feron_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_feron_serialize,
|
||||
.deserialize = subghz_protocol_decoder_feron_deserialize,
|
||||
.get_string = subghz_protocol_decoder_feron_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_feron_encoder = {
|
||||
.alloc = subghz_protocol_encoder_feron_alloc,
|
||||
.free = subghz_protocol_encoder_feron_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_feron_deserialize,
|
||||
.stop = subghz_protocol_encoder_feron_stop,
|
||||
.yield = subghz_protocol_encoder_feron_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_feron = {
|
||||
.name = SUBGHZ_PROTOCOL_FERON_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_feron_decoder,
|
||||
.encoder = &subghz_protocol_feron_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFeron* instance = malloc(sizeof(SubGhzProtocolEncoderFeron));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_feron;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_feron_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_feron_get_upload(SubGhzProtocolEncoderFeron* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send 500/500 and gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
true, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
// Gap
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send 500/500 and gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
true, (uint32_t)subghz_protocol_feron_const.te_short + 150);
|
||||
// Gap
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_feron_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->serial = instance->data >> 16;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_feron_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_feron_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_feron_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_feron_stop(void* context) {
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_feron_yield(void* context) {
|
||||
SubGhzProtocolEncoderFeron* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFeron* instance = malloc(sizeof(SubGhzProtocolDecoderFeron));
|
||||
instance->base.protocol = &subghz_protocol_feron;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
|
||||
// Feron Decoder
|
||||
// 2025.04 - @xMasterX (MMX)
|
||||
|
||||
// Key samples
|
||||
/*
|
||||
0110001100111000 1000010101111010 - ON
|
||||
0110001100111000 1000010001111011 - OFF
|
||||
|
||||
0110001100111000 1000011001111001 - brightness up
|
||||
0110001100111000 1000011101111000 - brightness down
|
||||
|
||||
0110001100111000 1000001001111101 - scroll mode command
|
||||
|
||||
------------------------------------------
|
||||
0110001100111000 0111000010001111 - R
|
||||
0110001100111000 0001101011100101 - B
|
||||
0110001100111000 0100000010111111 - G
|
||||
*/
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FeronDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long * 6) <
|
||||
subghz_protocol_feron_const.te_delta * 4)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case FeronDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = FeronDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case FeronDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short and long timing = 350us HIGH (te_last) and 750us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) <
|
||||
subghz_protocol_feron_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_feron_const.te_long) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
|
||||
// Bit 1 is long and short timing = 750us HIGH (te_last) and 350us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) <
|
||||
subghz_protocol_feron_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_feron_const.te_short) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key 500Low(we are here)/500High us
|
||||
DURATION_DIFF(
|
||||
duration, (uint16_t)(subghz_protocol_feron_const.te_short + (uint16_t)150)) <
|
||||
subghz_protocol_feron_const.te_delta) {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) <
|
||||
subghz_protocol_feron_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 32 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_feron_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = FeronDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_feron_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderFeron* instance = context;
|
||||
|
||||
subghz_protocol_feron_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%08lX\r\n"
|
||||
"Serial: 0x%04lX\r\n"
|
||||
"Command: 0x%04lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
(uint32_t)(instance->generic.data & 0xFFFF));
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_FERON_NAME "Feron"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFeron SubGhzProtocolDecoderFeron;
|
||||
typedef struct SubGhzProtocolEncoderFeron SubGhzProtocolEncoderFeron;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_feron_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_feron_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_feron;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderFeron.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderFeron* pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_encoder_feron_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_encoder_feron_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_feron_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderFeron.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderFeron* pointer to a SubGhzProtocolDecoderFeron instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderFeron.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,407 @@
|
||||
#include "gangqi.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolGangQi"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_gangqi_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1200,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 34,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderGangQi {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderGangQi {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GangQiDecoderStepReset = 0,
|
||||
GangQiDecoderStepSaveDuration,
|
||||
GangQiDecoderStepCheckDuration,
|
||||
} GangQiDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder = {
|
||||
.alloc = subghz_protocol_decoder_gangqi_alloc,
|
||||
.free = subghz_protocol_decoder_gangqi_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_gangqi_feed,
|
||||
.reset = subghz_protocol_decoder_gangqi_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_gangqi_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_gangqi_serialize,
|
||||
.deserialize = subghz_protocol_decoder_gangqi_deserialize,
|
||||
.get_string = subghz_protocol_decoder_gangqi_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder = {
|
||||
.alloc = subghz_protocol_encoder_gangqi_alloc,
|
||||
.free = subghz_protocol_encoder_gangqi_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_gangqi_deserialize,
|
||||
.stop = subghz_protocol_encoder_gangqi_stop,
|
||||
.yield = subghz_protocol_encoder_gangqi_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_gangqi = {
|
||||
.name = SUBGHZ_PROTOCOL_GANGQI_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_gangqi_decoder,
|
||||
.encoder = &subghz_protocol_gangqi_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderGangQi* instance = malloc(sizeof(SubGhzProtocolEncoderGangQi));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_gangqi;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 1024;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_gangqi_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQi* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
// Generate new key
|
||||
uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF);
|
||||
uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn);
|
||||
uint8_t serial_high = (uint8_t)(serial >> 8);
|
||||
uint8_t serial_low = (uint8_t)(serial & 0xFF);
|
||||
uint8_t bytesum = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button);
|
||||
|
||||
instance->generic.data = (instance->generic.data >> 14) << 14 | (instance->generic.btn << 10) |
|
||||
(bytesum << 2);
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long * 2);
|
||||
|
||||
for(size_t r = 0; r < 5; r++) {
|
||||
// Send key and GAP between parcels
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
|
||||
subghz_protocol_gangqi_const.te_delta);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_gangqi_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
|
||||
subghz_protocol_gangqi_const.te_delta);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data and parsing serial number
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_gangqi_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->btn = (instance->data >> 10) & 0xF;
|
||||
instance->serial = (instance->data & 0xFFFFF0000) >> 16;
|
||||
|
||||
// GangQi Decoder
|
||||
// 09.2024 - @xMasterX (MMX) (last update - bytesum calculation at 02.2025)
|
||||
// Thanks @Skorpionm for support!
|
||||
// Thanks @Drone1950 and @mishamyte (who spent 2 weeks on this) for making this work properly
|
||||
|
||||
// Example of correct bytesum calculation
|
||||
// 0xC8 - serial_high - serial_low - constant_and_button
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_gangqi_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_gangqi_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_gangqi_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Key");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_gangqi_stop(void* context) {
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) {
|
||||
SubGhzProtocolEncoderGangQi* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderGangQi* instance = malloc(sizeof(SubGhzProtocolDecoderGangQi));
|
||||
instance->base.protocol = &subghz_protocol_gangqi;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case GangQiDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
|
||||
subghz_protocol_gangqi_const.te_delta * 5)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case GangQiDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = GangQiDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case GangQiDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short and long timing
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
|
||||
subghz_protocol_gangqi_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
|
||||
// Bit 1 is long and short timing
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
|
||||
subghz_protocol_gangqi_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
|
||||
subghz_protocol_gangqi_const.te_delta * 5) {
|
||||
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
|
||||
subghz_protocol_gangqi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 34 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_gangqi_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = GangQiDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button name.
|
||||
* @param btn Button number, 4 bit
|
||||
*/
|
||||
static const char* subghz_protocol_gangqi_get_button_name(uint8_t btn) {
|
||||
const char* name_btn[16] = {
|
||||
"Unknown",
|
||||
"Exit settings",
|
||||
"Volume setting",
|
||||
"0x3",
|
||||
"Vibro sens. setting",
|
||||
"Settings mode",
|
||||
"Ringtone setting",
|
||||
"Ring", // D
|
||||
"0x8",
|
||||
"0x9",
|
||||
"0xA",
|
||||
"Alarm", // C
|
||||
"0xC",
|
||||
"Arm", // A
|
||||
"Disarm", // B
|
||||
"0xF"};
|
||||
return btn <= 0xf ? name_btn[btn] : name_btn[0];
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_gangqi_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderGangQi* instance = context;
|
||||
|
||||
// Parse serial
|
||||
subghz_protocol_gangqi_remote_controller(&instance->generic);
|
||||
|
||||
// Get byte sum
|
||||
uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF);
|
||||
uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn);
|
||||
uint8_t serial_high = (uint8_t)(serial >> 8);
|
||||
uint8_t serial_low = (uint8_t)(serial & 0xFF);
|
||||
// Type 1 is what original remotes use, type 2 is "backdoor" sum that receiver accepts too
|
||||
uint8_t sum_type1 = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button);
|
||||
uint8_t sum_type2 = (uint8_t)(0x02 + serial_high + serial_low + const_and_button);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%X%08lX\r\n"
|
||||
"Serial: 0x%05lX\r\n"
|
||||
"Sum: 0x%02X Sum2: 0x%02X\r\n"
|
||||
"Btn: 0x%01X - %s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint8_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
sum_type1,
|
||||
sum_type2,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_gangqi_get_button_name(instance->generic.btn));
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_GANGQI_NAME "GangQi"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderGangQi SubGhzProtocolDecoderGangQi;
|
||||
typedef struct SubGhzProtocolEncoderGangQi SubGhzProtocolEncoderGangQi;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_gangqi;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderGangQi.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderGangQi* pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_encoder_gangqi_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_encoder_gangqi_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderGangQi.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderGangQi* pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderGangQi.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,269 @@
|
||||
#include "hay21.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolHay21"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_hay21_const = {
|
||||
.te_short = 300,
|
||||
.te_long = 700,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 21,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderHay21 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHay21 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Hay21DecoderStepReset = 0,
|
||||
Hay21DecoderStepSaveDuration,
|
||||
Hay21DecoderStepCheckDuration,
|
||||
} Hay21DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_hay21_decoder = {
|
||||
.alloc = subghz_protocol_decoder_hay21_alloc,
|
||||
.free = subghz_protocol_decoder_hay21_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_hay21_feed,
|
||||
.reset = subghz_protocol_decoder_hay21_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_hay21_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_hay21_serialize,
|
||||
.deserialize = subghz_protocol_decoder_hay21_deserialize,
|
||||
.get_string = subghz_protocol_decoder_hay21_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_hay21_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_hay21 = {
|
||||
.name = SUBGHZ_PROTOCOL_HAY21_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_hay21_decoder,
|
||||
.encoder = &subghz_protocol_hay21_encoder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Analysis of received data and parsing serial number
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_hay21_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->btn = (instance->data >> 13) & 0xFF;
|
||||
instance->serial = (instance->data >> 5) & 0xFF;
|
||||
instance->cnt = (instance->data >> 1) & 0xF;
|
||||
|
||||
// Hay21 Decoder
|
||||
// 09.2024 - @xMasterX (MMX)
|
||||
|
||||
// Key samples (inverted)
|
||||
// button serial CNT (goes lower since 0/1 are inverted)
|
||||
//14A84A = 000 10100101 01000010 0101 0 (cnt 5)
|
||||
//14A848 = 000 10100101 01000010 0100 0 (cnt 4)
|
||||
//14A846 = 000 10100101 01000010 0011 0 (cnt 3)
|
||||
//14A844 = 000 10100101 01000010 0010 0 (cnt 2)
|
||||
//14A842 = 000 10100101 01000010 0001 0 (cnt 1)
|
||||
//14A840 = 000 10100101 01000010 0000 0 (cnt 0)
|
||||
//14A85E = 000 10100101 01000010 1111 0 (cnt F)
|
||||
//14A85C = 000 10100101 01000010 1110 0 (cnt E)
|
||||
//14A85A = 000 10100101 01000010 1101 0 (cnt D)
|
||||
//14A858 = 000 10100101 01000010 1100 0 (cnt C)
|
||||
//14A856 = 000 10100101 01000010 1011 0 (cnt B)
|
||||
// 0xA5 (Labeled as On/Off on the remote board)
|
||||
// 0x3C (Labeled as Mode on the remote board)
|
||||
// 0x42 (Serial)
|
||||
// BTN Serial CNT
|
||||
//078854 = 000 00111100 01000010 1010 0 (cnt A)
|
||||
//078852 = 000 00111100 01000010 1001 0 (cnt 9)
|
||||
//078850 = 000 00111100 01000010 1000 0 (cnt 8)
|
||||
//07884E = 000 00111100 01000010 0111 0 (cnt 7)
|
||||
// Inverted back
|
||||
//1877B9 = 000 11000011 10111101 1100 1
|
||||
//1877BB = 000 11000011 10111101 1101 1
|
||||
//1877BD = 000 11000011 10111101 1110 1
|
||||
//0B57BF = 000 01011010 10111101 1111 1
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderHay21* instance = malloc(sizeof(SubGhzProtocolDecoderHay21));
|
||||
instance->base.protocol = &subghz_protocol_hay21;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Hay21DecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
|
||||
subghz_protocol_hay21_const.te_delta * 3)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case Hay21DecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Hay21DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case Hay21DecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 1 is long + short timing
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
|
||||
subghz_protocol_hay21_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_short) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
|
||||
// Bit 0 is short + long timing
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
|
||||
subghz_protocol_hay21_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
|
||||
subghz_protocol_hay21_const.te_delta * 2) {
|
||||
//Found next GAP and add bit 0 or 1
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
|
||||
subghz_protocol_hay21_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
// If got 21 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_hay21_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Hay21DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button name.
|
||||
* @param btn Button number, 4 bit
|
||||
*/
|
||||
static const char* subghz_protocol_hay21_get_button_name(uint8_t btn) {
|
||||
const char* btn_name;
|
||||
switch(btn) {
|
||||
case 0x5A:
|
||||
btn_name = "On/Off";
|
||||
break;
|
||||
case 0xC3:
|
||||
btn_name = "Mode";
|
||||
break;
|
||||
case 0x88:
|
||||
btn_name = "Hold";
|
||||
break;
|
||||
default:
|
||||
btn_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
return btn_name;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_hay21_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHay21* instance = context;
|
||||
|
||||
// Parse serial, button, counter
|
||||
subghz_protocol_hay21_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s - %dbit\r\n"
|
||||
"Key: 0x%06lX\r\n"
|
||||
"Serial: 0x%02X\r\n"
|
||||
"Btn: 0x%01X - %s\r\n"
|
||||
"Cnt: 0x%01X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
(uint8_t)(instance->generic.serial & 0xFF),
|
||||
instance->generic.btn,
|
||||
subghz_protocol_hay21_get_button_name(instance->generic.btn),
|
||||
(uint8_t)(instance->generic.cnt & 0xF));
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_HAY21_NAME "Hay21"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHay21 SubGhzProtocolDecoderHay21;
|
||||
typedef struct SubGhzProtocolEncoderHay21 SubGhzProtocolEncoderHay21;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_hay21_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_hay21_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_hay21;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderHay21.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderHay21* pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderHay21.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,408 @@
|
||||
#include "hollarm.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolHollarm"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_hollarm_const = {
|
||||
.te_short = 200,
|
||||
.te_long = 1000,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 42,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderHollarm {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHollarm {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
HollarmDecoderStepReset = 0,
|
||||
HollarmDecoderStepSaveDuration,
|
||||
HollarmDecoderStepCheckDuration,
|
||||
} HollarmDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder = {
|
||||
.alloc = subghz_protocol_decoder_hollarm_alloc,
|
||||
.free = subghz_protocol_decoder_hollarm_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_hollarm_feed,
|
||||
.reset = subghz_protocol_decoder_hollarm_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_hollarm_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_hollarm_serialize,
|
||||
.deserialize = subghz_protocol_decoder_hollarm_deserialize,
|
||||
.get_string = subghz_protocol_decoder_hollarm_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder = {
|
||||
.alloc = subghz_protocol_encoder_hollarm_alloc,
|
||||
.free = subghz_protocol_encoder_hollarm_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_hollarm_deserialize,
|
||||
.stop = subghz_protocol_encoder_hollarm_stop,
|
||||
.yield = subghz_protocol_encoder_hollarm_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_hollarm = {
|
||||
.name = SUBGHZ_PROTOCOL_HOLLARM_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_hollarm_decoder,
|
||||
.encoder = &subghz_protocol_hollarm_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderHollarm* instance = malloc(sizeof(SubGhzProtocolEncoderHollarm));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_hollarm;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_hollarm_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_hollarm_get_upload(SubGhzProtocolEncoderHollarm* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
// Generate new key
|
||||
uint64_t new_key = (instance->generic.data >> 12) << 12 | (instance->generic.btn << 8);
|
||||
|
||||
uint8_t bytesum = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) +
|
||||
((new_key >> 16) & 0xFF) + ((new_key >> 8) & 0xFF);
|
||||
|
||||
instance->generic.data = (new_key | bytesum);
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP between parcels
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
// Read and prepare levels with 2 bit (was saved for better parsing) to the left offset to fit with the original remote transmission
|
||||
if(bit_read((instance->generic.data << 2), i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 8);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_hollarm_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data and parsing serial number
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_hollarm_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->btn = (instance->data >> 8) & 0xF;
|
||||
instance->serial = (instance->data & 0xFFFFFFF0000) >> 16;
|
||||
|
||||
// Hollarm Decoder
|
||||
// 09.2024 - @xMasterX (MMX)
|
||||
// Thanks @Skorpionm for support!
|
||||
|
||||
// F0B93422FF = FF 8bit Sum
|
||||
// F0B93421FE = FE 8bit Sum
|
||||
// F0B9342401 = 01 8bit Sum
|
||||
// F0B9342805 = 05 8bit Sum
|
||||
|
||||
// Serial (moved 2bit to right) | Btn | 8b previous 4 bytes sum
|
||||
// 00001111000010111001001101000010 0010 11111111 btn = (0x2)
|
||||
// 00001111000010111001001101000010 0001 11111110 btn = (0x1)
|
||||
// 00001111000010111001001101000010 0100 00000001 btn = (0x4)
|
||||
// 00001111000010111001001101000010 1000 00000101 btn = (0x8)
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_hollarm_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_hollarm_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_hollarm_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Key");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_hollarm_stop(void* context) {
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) {
|
||||
SubGhzProtocolEncoderHollarm* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderHollarm* instance = malloc(sizeof(SubGhzProtocolDecoderHollarm));
|
||||
instance->base.protocol = &subghz_protocol_hollarm;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case HollarmDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) <
|
||||
subghz_protocol_hollarm_const.te_delta * 2)) {
|
||||
//Found GAP between parcels
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case HollarmDecoderStepSaveDuration:
|
||||
// Save HIGH level timing for next step
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = HollarmDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case HollarmDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short 200us HIGH + long 1000us LOW timing
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) <
|
||||
subghz_protocol_hollarm_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_long) <
|
||||
subghz_protocol_hollarm_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
|
||||
// Bit 1 is short 200us HIGH + short x8 = 1600us LOW timing
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) <
|
||||
subghz_protocol_hollarm_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 8) <
|
||||
subghz_protocol_hollarm_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) <
|
||||
subghz_protocol_hollarm_const.te_delta) {
|
||||
// When next GAP is found add bit 0 and do check for read finish
|
||||
// (we have 42 high level pulses, last or first one may be a stop/start bit but we will parse it as zero)
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
|
||||
// If got 42 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_hollarm_const.min_count_bit_for_found) {
|
||||
// Saving with 2bit to the right offset for proper parsing
|
||||
instance->generic.data = (instance->decoder.decode_data >> 2);
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
uint8_t bytesum = ((instance->generic.data >> 32) & 0xFF) +
|
||||
((instance->generic.data >> 24) & 0xFF) +
|
||||
((instance->generic.data >> 16) & 0xFF) +
|
||||
((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
if(bytesum != (instance->generic.data & 0xFF)) {
|
||||
// Check if the key is valid by verifying the sum
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = HollarmDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button name.
|
||||
* @param btn Button number, 4 bit
|
||||
*/
|
||||
static const char* subghz_protocol_hollarm_get_button_name(uint8_t btn) {
|
||||
const char* name_btn[16] = {
|
||||
"Unknown",
|
||||
"Disarm", // B (2)
|
||||
"Arm", // A (1)
|
||||
"0x3",
|
||||
"Ringtone/Alarm", // C (3)
|
||||
"0x5",
|
||||
"0x6",
|
||||
"0x7",
|
||||
"Ring", // D (4)
|
||||
"Settings mode",
|
||||
"Exit settings",
|
||||
"Vibro sens. setting",
|
||||
"Not used\n(in settings)",
|
||||
"Volume setting",
|
||||
"0xE",
|
||||
"0xF"};
|
||||
return btn <= 0xf ? name_btn[btn] : name_btn[0];
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_hollarm_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHollarm* instance = context;
|
||||
|
||||
// Parse serial
|
||||
subghz_protocol_hollarm_remote_controller(&instance->generic);
|
||||
// Get byte sum
|
||||
uint8_t bytesum =
|
||||
((instance->generic.data >> 32) & 0xFF) + ((instance->generic.data >> 24) & 0xFF) +
|
||||
((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%02lX%08lX\r\n"
|
||||
"Serial: 0x%06lX Sum: %02X\r\n"
|
||||
"Btn: 0x%01X - %s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)instance->generic.data,
|
||||
instance->generic.serial,
|
||||
bytesum,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_hollarm_get_button_name(instance->generic.btn));
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_HOLLARM_NAME "Hollarm"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHollarm SubGhzProtocolDecoderHollarm;
|
||||
typedef struct SubGhzProtocolEncoderHollarm SubGhzProtocolEncoderHollarm;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_hollarm;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderHollarm.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderHollarm* pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_encoder_hollarm_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_encoder_hollarm_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderHollarm.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderHollarm* pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderHollarm.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output);
|
||||
@@ -234,8 +234,10 @@ void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Holtek_HT12XDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 36) <
|
||||
subghz_protocol_holtek_th12x_const.te_delta * 36)) {
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 28) <
|
||||
subghz_protocol_holtek_th12x_const.te_delta * 20)) {
|
||||
// 18720 us old max value
|
||||
// 12960 us corrected max value
|
||||
//Found Preambula
|
||||
instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit;
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ SubGhzProtocolStatus
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_hormann_get_upload(instance)) {
|
||||
instance->encoder.front = 0; // reset position before start
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
@@ -170,6 +171,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_hormann_stop(void* context) {
|
||||
SubGhzProtocolEncoderHormann* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_hormann_yield(void* context) {
|
||||
|
||||
@@ -193,6 +193,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize(
|
||||
void subghz_protocol_encoder_intertechno_v3_stop(void* context) {
|
||||
SubGhzProtocolEncoderIntertechno_V3* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) {
|
||||
|
||||
@@ -299,7 +299,7 @@ SubGhzProtocolStatus
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -309,6 +309,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_keeloq_stop(void* context) {
|
||||
SubGhzProtocolEncoderKeeloq* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) {
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
#include "legrand.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolLegrand"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_legrand_const = {
|
||||
.te_short = 375,
|
||||
.te_long = 1125,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 18,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderLegrand {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t te;
|
||||
uint32_t last_data;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderLegrand {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t te;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LegrandDecoderStepReset = 0,
|
||||
LegrandDecoderStepFirstBit,
|
||||
LegrandDecoderStepSaveDuration,
|
||||
LegrandDecoderStepCheckDuration,
|
||||
} LegrandDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_legrand_decoder = {
|
||||
.alloc = subghz_protocol_decoder_legrand_alloc,
|
||||
.free = subghz_protocol_decoder_legrand_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_legrand_feed,
|
||||
.reset = subghz_protocol_decoder_legrand_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_legrand_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_legrand_serialize,
|
||||
.deserialize = subghz_protocol_decoder_legrand_deserialize,
|
||||
.get_string = subghz_protocol_decoder_legrand_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_legrand_encoder = {
|
||||
.alloc = subghz_protocol_encoder_legrand_alloc,
|
||||
.free = subghz_protocol_encoder_legrand_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_legrand_deserialize,
|
||||
.stop = subghz_protocol_encoder_legrand_stop,
|
||||
.yield = subghz_protocol_encoder_legrand_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_legrand = {
|
||||
.name = SUBGHZ_PROTOCOL_LEGRAND_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_legrand_decoder,
|
||||
.encoder = &subghz_protocol_legrand_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderLegrand* instance = malloc(sizeof(SubGhzProtocolEncoderLegrand));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_legrand;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload =
|
||||
(subghz_protocol_legrand_const.min_count_bit_for_found * 6) * 2 + 2;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_legrand_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
* @return true On success
|
||||
*/
|
||||
static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
//size_t size_upload = (instance->generic.data_count_bit * 2) + 1;
|
||||
//if(size_upload != instance->encoder.size_upload) {
|
||||
// FURI_LOG_E(TAG, "Invalid data bit count");
|
||||
// return false;
|
||||
//}
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for(size_t r = 0; r < 5; r++) {
|
||||
// Send sync
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)instance->te * 16); // 5728
|
||||
// Send key data
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// send bit 1
|
||||
if(i == instance->generic.data_count_bit) {
|
||||
//Send first bit
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te * 3);
|
||||
} else {
|
||||
// send bit 1 regular
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)instance->te);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te * 3);
|
||||
}
|
||||
} else {
|
||||
// send bit 0
|
||||
if(i == instance->generic.data_count_bit) {
|
||||
//Send first bit
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te);
|
||||
} else {
|
||||
// send bit 0 regular
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)instance->te * 3);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)instance->te);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->encoder.size_upload = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_legrand_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing TE");
|
||||
ret = SubGhzProtocolStatusErrorParserTe;
|
||||
break;
|
||||
}
|
||||
// optional parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_legrand_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_legrand_stop(void* context) {
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_legrand_yield(void* context) {
|
||||
SubGhzProtocolEncoderLegrand* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderLegrand* instance = malloc(sizeof(SubGhzProtocolDecoderLegrand));
|
||||
instance->base.protocol = &subghz_protocol_legrand;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
instance->last_data = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case LegrandDecoderStepReset:
|
||||
if(!level && DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) <
|
||||
subghz_protocol_legrand_const.te_delta * 8) { // 6000 +- 1200
|
||||
instance->decoder.parser_step = LegrandDecoderStepFirstBit;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->te = 0;
|
||||
}
|
||||
break;
|
||||
case LegrandDecoderStepFirstBit:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) <
|
||||
subghz_protocol_legrand_const.te_delta) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->te += duration * 4; // long low that is part of sync, then short high
|
||||
}
|
||||
|
||||
if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) <
|
||||
subghz_protocol_legrand_const.te_delta * 3) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->te += duration / 3 * 4; // short low that is part of sync, then long high
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit > 0) {
|
||||
// advance to the next step if either short or long is found
|
||||
instance->decoder.parser_step = LegrandDecoderStepSaveDuration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
break;
|
||||
case LegrandDecoderStepSaveDuration:
|
||||
if(!level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->te += duration;
|
||||
instance->decoder.parser_step = LegrandDecoderStepCheckDuration;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
break;
|
||||
case LegrandDecoderStepCheckDuration:
|
||||
if(level) {
|
||||
uint8_t found = 0;
|
||||
|
||||
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_long) <
|
||||
subghz_protocol_legrand_const.te_delta * 3 &&
|
||||
DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) <
|
||||
subghz_protocol_legrand_const.te_delta) {
|
||||
found = 1;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
|
||||
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_short) <
|
||||
subghz_protocol_legrand_const.te_delta &&
|
||||
DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) <
|
||||
subghz_protocol_legrand_const.te_delta * 3) {
|
||||
found = 1;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
|
||||
if(found) {
|
||||
instance->te += duration;
|
||||
|
||||
if(instance->decoder.decode_count_bit <
|
||||
subghz_protocol_legrand_const.min_count_bit_for_found) {
|
||||
instance->decoder.parser_step = LegrandDecoderStepSaveDuration;
|
||||
break;
|
||||
}
|
||||
|
||||
// enough bits for a packet found, save it only if there was a previous packet
|
||||
// with the same data
|
||||
if(instance->last_data && (instance->last_data == instance->decoder.decode_data)) {
|
||||
instance->te /= instance->decoder.decode_count_bit * 4;
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->last_data = instance->decoder.decode_data;
|
||||
// fallthrough to reset, the next bit is expected to be a sync
|
||||
// it also takes care of resetting the decoder state
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = LegrandDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if((ret == SubGhzProtocolStatusOk) &&
|
||||
!flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add TE");
|
||||
ret = SubGhzProtocolStatusErrorParserTe;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_legrand_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing TE");
|
||||
ret = SubGhzProtocolStatusErrorParserTe;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderLegrand* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%05lX\r\n"
|
||||
"Te:%luus\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFF),
|
||||
instance->te);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_LEGRAND_NAME "Legrand"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SubGhzProtocolDecoderLegrand SubGhzProtocolDecoderLegrand;
|
||||
typedef struct SubGhzProtocolEncoderLegrand SubGhzProtocolEncoderLegrand;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_legrand_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_legrand_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_legrand;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderLegrand.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderLegrand* pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_encoder_legrand_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_encoder_legrand_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderLegrand instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_legrand_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderLegrand.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderLegrand* pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderLegrand.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderLegrand instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -168,6 +168,7 @@ SubGhzProtocolStatus
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_magellan_get_upload(instance)) {
|
||||
instance->encoder.front = 0; // reset before start
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
@@ -180,6 +181,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_magellan_stop(void* context) {
|
||||
SubGhzProtocolEncoderMagellan* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_magellan_yield(void* context) {
|
||||
@@ -359,15 +361,38 @@ static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric*
|
||||
*
|
||||
* 0x1275EC => 0x12-event codes, 0x75EC-serial (dec 117236)
|
||||
*
|
||||
* event codes
|
||||
* bit_0: 1-Open/Motion, 0-close/ok
|
||||
* bit_1: 1-Tamper On (alarm), 0-Tamper Off (ok)
|
||||
* bit_2: ?
|
||||
* bit_3: 1-power on
|
||||
* bit_4: model type - wireless reed
|
||||
* bit_5: model type - motion sensor
|
||||
* bit_6: ?
|
||||
* bit_7: ?
|
||||
* Event codes consist of two parts:
|
||||
* - The upper nibble (bits 7-4) represents the event type:
|
||||
* - 0x00: Nothing
|
||||
* - 0x01: Door
|
||||
* - 0x02: Motion
|
||||
* - 0x03: Smoke Alarm
|
||||
* - 0x04: REM1
|
||||
* - 0x05: REM1 with subtype Off1
|
||||
* - 0x06: REM2
|
||||
* - 0x07: REM2 with subtype Off1
|
||||
* - Others: Unknown
|
||||
* - The lower nibble (bits 3-0) represents the event subtype, which varies based on the model type:
|
||||
* - If the model type is greater than 0x03 (e.g., REM1 or REM2):
|
||||
* - 0x00: Arm1
|
||||
* - 0x01: Btn1
|
||||
* - 0x02: Btn2
|
||||
* - 0x03: Btn3
|
||||
* - 0x08: Reset
|
||||
* - 0x09: LowBatt
|
||||
* - 0x0A: BattOk
|
||||
* - 0x0B: Learn
|
||||
* - Others: Unknown
|
||||
* - Otherwise:
|
||||
* - 0x00: Sealed
|
||||
* - 0x01: Alarm
|
||||
* - 0x02: Tamper
|
||||
* - 0x03: Alarm + Tamper
|
||||
* - 0x08: Reset
|
||||
* - 0x09: LowBatt
|
||||
* - 0x0A: BattOk
|
||||
* - 0x0B: Learn
|
||||
* - Others: Unknown
|
||||
*
|
||||
*/
|
||||
uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data >> 8, 24);
|
||||
@@ -376,18 +401,71 @@ static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric*
|
||||
}
|
||||
|
||||
static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriString* output) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s%s%s%s%s%s%s%s",
|
||||
((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") :
|
||||
(event & 0x1 ? " Motion" : " Ok")),
|
||||
((event >> 1) & 0x1 ? ", Tamper On\n(Alarm)" : ""),
|
||||
((event >> 2) & 0x1 ? ", ?" : ""),
|
||||
((event >> 3) & 0x1 ? ", Power On" : ""),
|
||||
((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""),
|
||||
((event >> 5) & 0x1 ? ", MT:Motion_Sensor" : ""),
|
||||
((event >> 6) & 0x1 ? ", ?" : ""),
|
||||
((event >> 7) & 0x1 ? ", ?" : ""));
|
||||
const char* event_type;
|
||||
const char* event_subtype;
|
||||
|
||||
switch((event >> 4) & 0x0F) {
|
||||
case 0x00:
|
||||
event_type = "Nothing";
|
||||
break;
|
||||
case 0x01:
|
||||
event_type = "Door";
|
||||
break;
|
||||
case 0x02:
|
||||
event_type = "Motion";
|
||||
break;
|
||||
case 0x03:
|
||||
event_type = "Smoke Alarm";
|
||||
break;
|
||||
case 0x04:
|
||||
event_type = "REM1";
|
||||
break;
|
||||
case 0x05:
|
||||
event_type = "REM1";
|
||||
event_subtype = "Off1";
|
||||
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
||||
return;
|
||||
case 0x06:
|
||||
event_type = "REM2";
|
||||
event_subtype = "Off1";
|
||||
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
||||
return;
|
||||
default:
|
||||
event_type = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
switch(event & 0x0F) {
|
||||
case 0x00:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Arm1" : "Sealed";
|
||||
break;
|
||||
case 0x01:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn1" : "Alarm";
|
||||
break;
|
||||
case 0x02:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn2" : "Tamper";
|
||||
break;
|
||||
case 0x03:
|
||||
event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn3" : "Alarm + Tamper";
|
||||
break;
|
||||
case 0x08:
|
||||
event_subtype = "Reset";
|
||||
break;
|
||||
case 0x09:
|
||||
event_subtype = "LowBatt";
|
||||
break;
|
||||
case 0x0A:
|
||||
event_subtype = "BattOk";
|
||||
break;
|
||||
case 0x0B:
|
||||
event_subtype = "Learn";
|
||||
break;
|
||||
default:
|
||||
event_subtype = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(output, "%s - %s", event_type, event_subtype);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) {
|
||||
|
||||
@@ -165,7 +165,7 @@ static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMar
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x08;
|
||||
uint8_t crc = 0x01;
|
||||
size_t i, j;
|
||||
for(i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
@@ -184,6 +184,18 @@ uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) {
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Key samples
|
||||
// 1307EDF6486C5 = 000 100110000 01111110110111110110 0100 10000110 11000101
|
||||
// 1303EFAFD8683 = 000 100110000 00111110111110101111 1101 10000110 10000011
|
||||
|
||||
// From unittests
|
||||
// 1300710DF869F
|
||||
|
||||
// const serial button serial crc
|
||||
// 130 7EDF6 4 86 C5
|
||||
// 130 3EFAF D 86 83
|
||||
// 130 0710D F 86 9F
|
||||
|
||||
instance->btn = (instance->data >> 16) & 0xF;
|
||||
instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF);
|
||||
}
|
||||
@@ -207,6 +219,7 @@ SubGhzProtocolStatus
|
||||
|
||||
subghz_protocol_marantec_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_marantec_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -216,6 +229,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_marantec_stop(void* context) {
|
||||
SubGhzProtocolEncoderMarantec* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_marantec_yield(void* context) {
|
||||
@@ -253,6 +267,7 @@ void subghz_protocol_decoder_marantec_free(void* context) {
|
||||
void subghz_protocol_decoder_marantec_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec* instance = context;
|
||||
instance->decoder.parser_step = MarantecDecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
@@ -367,16 +382,30 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp
|
||||
SubGhzProtocolDecoderMarantec* instance = context;
|
||||
subghz_protocol_marantec_remote_controller(&instance->generic);
|
||||
|
||||
uint8_t tdata[6] = {
|
||||
instance->generic.data >> 48,
|
||||
instance->generic.data >> 40,
|
||||
instance->generic.data >> 32,
|
||||
instance->generic.data >> 24,
|
||||
instance->generic.data >> 16,
|
||||
instance->generic.data >> 8};
|
||||
|
||||
uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata));
|
||||
bool crc_ok = (crc == (instance->generic.data & 0xFF));
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%07lX \r\n"
|
||||
"Btn:%X\r\n",
|
||||
"Key: 0x%lX%08lX\r\n"
|
||||
"Sn: 0x%07lX \r\n"
|
||||
"CRC: 0x%02X - %s\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
crc,
|
||||
crc_ok ? "Valid" : "Invalid",
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -107,3 +107,11 @@ SubGhzProtocolStatus
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output);
|
||||
|
||||
/**
|
||||
* Calculate CRC8 for Marantec protocol.
|
||||
* @param data Pointer to the data buffer
|
||||
* @param len Length of the data buffer
|
||||
* @return CRC8 value
|
||||
*/
|
||||
uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len);
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
#include "marantec24.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolMarantec24"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_marantec24_const = {
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 24,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderMarantec24 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderMarantec24 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Marantec24DecoderStepReset = 0,
|
||||
Marantec24DecoderStepSaveDuration,
|
||||
Marantec24DecoderStepCheckDuration,
|
||||
} Marantec24DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_marantec24_decoder = {
|
||||
.alloc = subghz_protocol_decoder_marantec24_alloc,
|
||||
.free = subghz_protocol_decoder_marantec24_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_marantec24_feed,
|
||||
.reset = subghz_protocol_decoder_marantec24_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_marantec24_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_marantec24_serialize,
|
||||
.deserialize = subghz_protocol_decoder_marantec24_deserialize,
|
||||
.get_string = subghz_protocol_decoder_marantec24_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_marantec24_encoder = {
|
||||
.alloc = subghz_protocol_encoder_marantec24_alloc,
|
||||
.free = subghz_protocol_encoder_marantec24_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_marantec24_deserialize,
|
||||
.stop = subghz_protocol_encoder_marantec24_stop,
|
||||
.yield = subghz_protocol_encoder_marantec24_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_marantec24 = {
|
||||
.name = SUBGHZ_PROTOCOL_MARANTEC24_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_marantec24_decoder,
|
||||
.encoder = &subghz_protocol_marantec24_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderMarantec24* instance = malloc(sizeof(SubGhzProtocolEncoderMarantec24));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_marantec24;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 512;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_marantec24_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
static void
|
||||
subghz_protocol_encoder_marantec24_get_upload(SubGhzProtocolEncoderMarantec24* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
// Send initial GAP to trigger decoder
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_marantec24_const.te_long * 9);
|
||||
for(size_t r = 0; r < 4; r++) {
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
|
||||
subghz_protocol_marantec24_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false,
|
||||
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
|
||||
subghz_protocol_marantec24_const.te_short); // 15200
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_marantec24_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->serial = instance->data >> 4;
|
||||
instance->btn = instance->data & 0xF;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_marantec24_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_marantec24_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_marantec24_get_upload(instance);
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_marantec24_stop(void* context) {
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_marantec24_yield(void* context) {
|
||||
SubGhzProtocolEncoderMarantec24* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_marantec24_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderMarantec24* instance = malloc(sizeof(SubGhzProtocolDecoderMarantec24));
|
||||
instance->base.protocol = &subghz_protocol_marantec24;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
|
||||
// Marantec24 Decoder
|
||||
// 2024 - @xMasterX (MMX)
|
||||
|
||||
// 2025 update - The protocol is not real marantec,
|
||||
// it comes from chinese remote that pretends to be replica of original marantec, actually it was a cloner
|
||||
// which had some thing written on it, which is uknown, but since its pretentding to be marantec,
|
||||
// it was decided to keep the name of the protocol as marantec24 (24 bits)
|
||||
|
||||
// Key samples
|
||||
// 101011000000010111001000 = AC05C8
|
||||
// 101011000000010111000100 = AC05C4
|
||||
// 101011000000010111001100 = AC05CC
|
||||
// 101011000000010111000000 = AC05C0
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Marantec24DecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
|
||||
subghz_protocol_marantec24_const.te_delta * 6)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case Marantec24DecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case Marantec24DecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is long and short x2 timing = 1600us HIGH (te_last) and 2400us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) <
|
||||
subghz_protocol_marantec24_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_short * 3) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Marantec24DecoderStepSaveDuration;
|
||||
// Bit 1 is short and long x2 timing = 800us HIGH (te_last) and 3200us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) <
|
||||
subghz_protocol_marantec24_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 2) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Marantec24DecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
|
||||
subghz_protocol_marantec24_const.te_delta * 6) {
|
||||
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) <
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 24 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_marantec24_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Marantec24DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_marantec24_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_marantec24_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_marantec24_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMarantec24* instance = context;
|
||||
|
||||
subghz_protocol_marantec24_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%06lX\r\n"
|
||||
"Serial: 0x%05lX\r\n"
|
||||
"Btn: %01X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.btn);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_MARANTEC24_NAME "Marantec24"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMarantec24 SubGhzProtocolDecoderMarantec24;
|
||||
typedef struct SubGhzProtocolEncoderMarantec24 SubGhzProtocolEncoderMarantec24;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_marantec24_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_marantec24_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_marantec24;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderMarantec24.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderMarantec24* pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_marantec24_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_marantec24_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_marantec24_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderMarantec24.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderMarantec24* pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_marantec24_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_marantec24_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_marantec24_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderMarantec24.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* output);
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolPhoenixV2"
|
||||
|
||||
//transmission only static mode
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = {
|
||||
@@ -91,6 +90,9 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
// Pre define functions
|
||||
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance);
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance
|
||||
@@ -107,6 +109,7 @@ static bool
|
||||
} else {
|
||||
instance->encoder.size_upload = size_upload;
|
||||
}
|
||||
|
||||
//Send header
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60);
|
||||
@@ -153,6 +156,7 @@ SubGhzProtocolStatus
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -274,16 +278,66 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) {
|
||||
uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF);
|
||||
|
||||
uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte
|
||||
uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte
|
||||
|
||||
uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial
|
||||
uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial
|
||||
|
||||
for(int i = 0; i < 16; i++) {
|
||||
// Store the most significant bit (MSB) of byte1.
|
||||
// The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys.
|
||||
uint8_t msb_of_byte1 = byte1 & 0x80;
|
||||
|
||||
// Store the least significant bit (LSB) of byte2.
|
||||
uint8_t lsb_of_byte2 = byte2 & 1;
|
||||
|
||||
// Perform a bit shuffle between the two bytes
|
||||
byte2 = (byte2 >> 1) | msb_of_byte1;
|
||||
byte1 = (byte1 << 1) | lsb_of_byte2;
|
||||
|
||||
// Conditionally apply the XOR keys based on the original MSB of byte1.
|
||||
if(msb_of_byte1 == 0) {
|
||||
byte1 ^= xor_key1;
|
||||
// The mask `& 0x7F` clears the MSB of byte2 after the XOR.
|
||||
byte2 = (byte2 ^ xor_key2) & 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint16_t)byte2 << 8 | byte1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// 2022.08 - @Skorpionm
|
||||
// 2025.07 - @xMasterX & @RocketGod-git
|
||||
// Fully supported now, with button switch and add manually
|
||||
//
|
||||
// Key samples
|
||||
// Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied
|
||||
// Serial - B9615720
|
||||
// Button - 01
|
||||
// Encrypted -> Decrypted counters
|
||||
// C63E - 025C
|
||||
// BCC1 - 025D
|
||||
// 3341 - 025E
|
||||
// 49BE - 025F
|
||||
// 99D3 - 0260
|
||||
// E32C - 0261
|
||||
|
||||
uint64_t data_rev =
|
||||
subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4);
|
||||
|
||||
instance->serial = data_rev & 0xFFFFFFFF;
|
||||
instance->cnt = (data_rev >> 40) & 0xFFFF;
|
||||
instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev);
|
||||
instance->btn = (data_rev >> 32) & 0xF;
|
||||
// encrypted cnt is (data_rev >> 40) & 0xFFFF
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) {
|
||||
@@ -318,14 +372,15 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou
|
||||
subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic);
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%02lX%08lX\r\n"
|
||||
"V2 Phoenix %dbit\r\n"
|
||||
"Key:%05lX%08lX\r\n"
|
||||
"Sn:0x%07lX \r\n"
|
||||
"Btn:%X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
"Cnt: 0x%04lX\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.cnt,
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ SubGhzProtocolStatus
|
||||
|
||||
subghz_protocol_power_smart_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_power_smart_get_upload(instance);
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -221,6 +222,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_power_smart_stop(void* context) {
|
||||
SubGhzProtocolEncoderPowerSmart* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_power_smart_yield(void* context) {
|
||||
|
||||
@@ -44,7 +44,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_kinggates_stylo_4k,
|
||||
&subghz_protocol_bin_raw,
|
||||
&subghz_protocol_mastercode,
|
||||
&subghz_protocol_legrand,
|
||||
&subghz_protocol_dickert_mahs,
|
||||
&subghz_protocol_gangqi,
|
||||
&subghz_protocol_marantec24,
|
||||
&subghz_protocol_hollarm,
|
||||
&subghz_protocol_hay21,
|
||||
&subghz_protocol_revers_rb2,
|
||||
&subghz_protocol_feron,
|
||||
&subghz_protocol_roger,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -45,4 +45,12 @@
|
||||
#include "kinggates_stylo_4k.h"
|
||||
#include "bin_raw.h"
|
||||
#include "mastercode.h"
|
||||
#include "legrand.h"
|
||||
#include "dickert_mahs.h"
|
||||
#include "gangqi.h"
|
||||
#include "marantec24.h"
|
||||
#include "hollarm.h"
|
||||
#include "hay21.h"
|
||||
#include "revers_rb2.h"
|
||||
#include "feron.h"
|
||||
#include "roger.h"
|
||||
|
||||
@@ -31,7 +31,7 @@ bool subghz_protocol_secplus_v2_create_data(
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param serial Serial number, 28 bit
|
||||
* @param btn Button number, 4 bit
|
||||
* @param cnt Container value, 16 bit
|
||||
* @param cnt Counter value, 16 bit
|
||||
* @param manufacture_name Name of manufacturer's key
|
||||
* @param preset Modulation, SubGhzRadioPreset
|
||||
* @return true On success
|
||||
|
||||
@@ -245,8 +245,8 @@ void subghz_protocol_decoder_raw_reset(void* context) {
|
||||
void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRAW* instance = context;
|
||||
|
||||
if(!instance->pause && (instance->upload_raw != NULL)) {
|
||||
// Add check if we got duration higher than 1 second, we skipping it, temp fix
|
||||
if((!instance->pause && (instance->upload_raw != NULL)) && (duration < ((uint32_t)1000000))) {
|
||||
if(duration > subghz_protocol_raw_const.te_short) {
|
||||
if(instance->last_level != level) {
|
||||
instance->last_level = (level ? true : false);
|
||||
@@ -273,7 +273,7 @@ void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
//SubGhzProtocolDecoderRAW* instance = context;
|
||||
UNUSED(context);
|
||||
furi_string_cat_printf(output, "RAW Date");
|
||||
furi_string_cat_printf(output, "RAW Data");
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) {
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
#include "revers_rb2.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolRevers_RB2"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_revers_rb2_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 160,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderRevers_RB2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_saved_state;
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderRevers_RB2 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Revers_RB2DecoderStepReset = 0,
|
||||
Revers_RB2DecoderStepHeader,
|
||||
Revers_RB2DecoderStepDecoderData,
|
||||
} Revers_RB2DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_revers_rb2_alloc,
|
||||
.free = subghz_protocol_decoder_revers_rb2_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_revers_rb2_feed,
|
||||
.reset = subghz_protocol_decoder_revers_rb2_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_revers_rb2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_revers_rb2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_revers_rb2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_revers_rb2_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder = {
|
||||
.alloc = subghz_protocol_encoder_revers_rb2_alloc,
|
||||
.free = subghz_protocol_encoder_revers_rb2_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_revers_rb2_deserialize,
|
||||
.stop = subghz_protocol_encoder_revers_rb2_stop,
|
||||
.yield = subghz_protocol_encoder_revers_rb2_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_revers_rb2 = {
|
||||
.name = SUBGHZ_PROTOCOL_REVERSRB2_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_revers_rb2_decoder,
|
||||
.encoder = &subghz_protocol_revers_rb2_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolEncoderRevers_RB2));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_revers_rb2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 1768;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_revers_rb2_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static LevelDuration
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(ManchesterEncoderResult result) {
|
||||
LevelDuration data = {.duration = 0, .level = 0};
|
||||
switch(result) {
|
||||
case ManchesterEncoderResultShortLow:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_short;
|
||||
data.level = false;
|
||||
break;
|
||||
case ManchesterEncoderResultLongLow:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_long;
|
||||
data.level = false;
|
||||
break;
|
||||
case ManchesterEncoderResultLongHigh:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_long;
|
||||
data.level = true;
|
||||
break;
|
||||
case ManchesterEncoderResultShortHigh:
|
||||
data.duration = subghz_protocol_revers_rb2_const.te_short;
|
||||
data.level = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
furi_crash("SubGhz: ManchesterEncoderResult is incorrect.");
|
||||
break;
|
||||
}
|
||||
return level_duration_make(data.level, data.duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
static void
|
||||
subghz_protocol_encoder_revers_rb2_get_upload(SubGhzProtocolEncoderRevers_RB2* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
for(size_t r = 0; r < 6; r++) {
|
||||
ManchesterEncoderState enc_state;
|
||||
manchester_encoder_reset(&enc_state);
|
||||
ManchesterEncoderResult result;
|
||||
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(!manchester_encoder_advance(
|
||||
&enc_state, bit_read(instance->generic.data, i - 1), &result)) {
|
||||
instance->encoder.upload[index++] =
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
|
||||
manchester_encoder_advance(
|
||||
&enc_state, bit_read(instance->generic.data, i - 1), &result);
|
||||
}
|
||||
instance->encoder.upload[index++] =
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
|
||||
}
|
||||
instance->encoder.upload[index] =
|
||||
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(
|
||||
manchester_encoder_finish(&enc_state));
|
||||
if(level_duration_get_level(instance->encoder.upload[index])) {
|
||||
index++;
|
||||
}
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320);
|
||||
}
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_revers_rb2_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Revers RB2 / RB2M Decoder
|
||||
// 02.2025 - @xMasterX (MMX)
|
||||
instance->serial = (((instance->data << 16) >> 16) >> 10);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_revers_rb2_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_revers_rb2_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_revers_rb2_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_revers_rb2_stop(void* context) {
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) {
|
||||
SubGhzProtocolEncoderRevers_RB2* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolDecoderRevers_RB2));
|
||||
instance->base.protocol = &subghz_protocol_revers_rb2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_addbit(void* context, bool data) {
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data;
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if(instance->decoder.decode_count_bit >= 65) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit <
|
||||
subghz_protocol_revers_rb2_const.min_count_bit_for_found) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Revers RB2 / RB2M Decoder
|
||||
// 02.2025 - @xMasterX (MMX)
|
||||
|
||||
uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFF;
|
||||
uint16_t stop_code = (instance->decoder.decode_data & 0x3FF);
|
||||
|
||||
if(preamble == 0xFF && stop_code == 0x200) {
|
||||
//Found header and stop code
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Revers_RB2DecoderStepReset:
|
||||
if((!level) &&
|
||||
(DURATION_DIFF(duration, 600) < subghz_protocol_revers_rb2_const.te_delta)) {
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepHeader;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
case Revers_RB2DecoderStepHeader:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
if(instance->decoder.te_last == 1) {
|
||||
instance->header_count++;
|
||||
}
|
||||
instance->decoder.te_last = level;
|
||||
} else {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
if(instance->decoder.te_last == 0) {
|
||||
instance->header_count++;
|
||||
}
|
||||
instance->decoder.te_last = level;
|
||||
} else {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->header_count == 4) {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0xF;
|
||||
instance->decoder.decode_count_bit = 4;
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepDecoderData;
|
||||
}
|
||||
break;
|
||||
case Revers_RB2DecoderStepDecoderData:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventShortLow;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventLongLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventShortHigh;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) <
|
||||
subghz_protocol_revers_rb2_const.te_delta) {
|
||||
event = ManchesterEventLongHigh;
|
||||
} else {
|
||||
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(
|
||||
instance->manchester_saved_state, event, &instance->manchester_saved_state, &data);
|
||||
|
||||
if(data_ok) {
|
||||
subghz_protocol_decoder_revers_rb2_addbit(instance, data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_revers_rb2_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRevers_RB2* instance = context;
|
||||
subghz_protocol_revers_rb2_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:%lX%08lX\r\n"
|
||||
"Sn:0x%08lX \r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_REVERSRB2_NAME "Revers_RB2"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderRevers_RB2 SubGhzProtocolDecoderRevers_RB2;
|
||||
typedef struct SubGhzProtocolEncoderRevers_RB2 SubGhzProtocolEncoderRevers_RB2;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_revers_rb2;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderRevers_RB2.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderRevers_RB2* pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_revers_rb2_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_revers_rb2_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderRevers_RB2* pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderRevers_RB2.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,341 @@
|
||||
#include "roger.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolRoger"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_roger_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 270,
|
||||
.min_count_bit_for_found = 28,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderRoger {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderRoger {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
RogerDecoderStepReset = 0,
|
||||
RogerDecoderStepSaveDuration,
|
||||
RogerDecoderStepCheckDuration,
|
||||
} RogerDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_roger_decoder = {
|
||||
.alloc = subghz_protocol_decoder_roger_alloc,
|
||||
.free = subghz_protocol_decoder_roger_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_roger_feed,
|
||||
.reset = subghz_protocol_decoder_roger_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_roger_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_roger_serialize,
|
||||
.deserialize = subghz_protocol_decoder_roger_deserialize,
|
||||
.get_string = subghz_protocol_decoder_roger_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_roger_encoder = {
|
||||
.alloc = subghz_protocol_encoder_roger_alloc,
|
||||
.free = subghz_protocol_encoder_roger_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_roger_deserialize,
|
||||
.stop = subghz_protocol_encoder_roger_stop,
|
||||
.yield = subghz_protocol_encoder_roger_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_roger = {
|
||||
.name = SUBGHZ_PROTOCOL_ROGER_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_roger_decoder,
|
||||
.encoder = &subghz_protocol_roger_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_roger;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_roger_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_roger_const.te_short * 19);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_roger_const.te_short * 19);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Roger Decoder
|
||||
// 2025.07 - @xMasterX (MMX)
|
||||
|
||||
// Key samples
|
||||
// 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 End: 0x20
|
||||
// 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 End: 0x23
|
||||
// 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 End: 0x01
|
||||
// 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 End: 0x02
|
||||
// 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 End: 0x01
|
||||
// 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 End: 0x04
|
||||
// 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 End: 0x02
|
||||
// 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 End: 0x08
|
||||
|
||||
instance->serial = instance->data >> 12;
|
||||
instance->btn = (instance->data >> 8) & 0xF;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_roger_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_roger_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_roger_get_upload(instance);
|
||||
instance->encoder.front = 0;
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_roger_stop(void* context) {
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_roger_yield(void* context) {
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger));
|
||||
instance->base.protocol = &subghz_protocol_roger;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case RogerDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) <
|
||||
subghz_protocol_roger_const.te_delta * 5)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case RogerDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = RogerDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case RogerDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
// Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) <
|
||||
subghz_protocol_roger_const.te_delta * 5) {
|
||||
//Found next GAP and add bit 1 or 0
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
// If got full 28 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_roger_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
|
||||
subghz_protocol_roger_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%07lX\r\n"
|
||||
"Serial: 0x%04lX\r\n"
|
||||
"End: 0x%02lX\r\n"
|
||||
"Btn: %01X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFF),
|
||||
instance->generic.serial,
|
||||
(uint32_t)(instance->generic.data & 0xFF),
|
||||
instance->generic.btn);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger;
|
||||
typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_roger;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderRoger.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_encoder_roger_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_encoder_roger_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_roger_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderRoger.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output);
|
||||
@@ -219,18 +219,43 @@ static void subghz_protocol_scher_khan_check_remote_controller(
|
||||
*/
|
||||
|
||||
switch(instance->data_count_bit) {
|
||||
// case 35: //MAGIC CODE, Static
|
||||
// instance->protocol_name = "MAGIC CODE, Static";
|
||||
// break;
|
||||
case 35: //MAGIC CODE, Static
|
||||
*protocol_name = "MAGIC CODE, Static";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 51: //MAGIC CODE, Dynamic
|
||||
*protocol_name = "MAGIC CODE, Dynamic";
|
||||
instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F);
|
||||
instance->btn = (instance->data >> 24) & 0x0F;
|
||||
instance->cnt = instance->data & 0xFFFF;
|
||||
break;
|
||||
// case 57: //MAGIC CODE PRO / PRO2
|
||||
// instance->protocol_name = "MAGIC CODE PRO / PRO2";
|
||||
// break;
|
||||
case 57: //MAGIC CODE PRO / PRO2
|
||||
*protocol_name = "MAGIC CODE PRO/PRO2";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 63: //MAGIC CODE, Dynamic Response
|
||||
*protocol_name = "MAGIC CODE, Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 64: //MAGICAR, Response ???
|
||||
*protocol_name = "MAGICAR, Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
case 81: // MAGIC CODE PRO / PRO2 Response ???
|
||||
case 82: // MAGIC CODE PRO / PRO2 Response ???
|
||||
*protocol_name = "MAGIC CODE PRO,\n Response";
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
*protocol_name = "Unknown";
|
||||
|
||||
@@ -554,6 +554,7 @@ SubGhzProtocolStatus
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0; // reset before start
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -563,6 +564,7 @@ SubGhzProtocolStatus
|
||||
void subghz_protocol_encoder_secplus_v2_stop(void* context) {
|
||||
SubGhzProtocolEncoderSecPlus_v2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0; // reset position
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "hex.h"
|
||||
#include "strint.h"
|
||||
#include "m-core.h"
|
||||
#include <errno.h>
|
||||
|
||||
size_t args_get_first_word_length(FuriString* args) {
|
||||
size_t ws = furi_string_search_char(args, ' ');
|
||||
@@ -34,6 +35,24 @@ bool args_read_int_and_trim(FuriString* args, int* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool args_read_float_and_trim(FuriString* args, float* value) {
|
||||
size_t cmd_length = args_get_first_word_length(args);
|
||||
if(cmd_length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* end_ptr;
|
||||
float temp = strtof(furi_string_get_cstr(args), &end_ptr);
|
||||
if(end_ptr == furi_string_get_cstr(args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = temp;
|
||||
furi_string_right(args, cmd_length);
|
||||
furi_string_trim(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool args_read_string_and_trim(FuriString* args, FuriString* word) {
|
||||
size_t cmd_length = args_get_first_word_length(args);
|
||||
|
||||
@@ -97,3 +116,38 @@ bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool args_read_duration(FuriString* args, uint32_t* value, const char* default_unit) {
|
||||
const char* args_cstr = furi_string_get_cstr(args);
|
||||
|
||||
const char* unit;
|
||||
errno = 0;
|
||||
double duration_ms = strtod(args_cstr, (char**)&unit);
|
||||
if(errno) return false;
|
||||
if(duration_ms < 0) return false;
|
||||
if(unit == args_cstr) return false;
|
||||
|
||||
if(strcmp(unit, "") == 0) {
|
||||
unit = default_unit;
|
||||
if(!unit) unit = "ms";
|
||||
}
|
||||
|
||||
uint32_t multiplier;
|
||||
if(strcasecmp(unit, "ms") == 0) {
|
||||
multiplier = 1;
|
||||
} else if(strcasecmp(unit, "s") == 0) {
|
||||
multiplier = 1000;
|
||||
} else if(strcasecmp(unit, "m") == 0) {
|
||||
multiplier = 60 * 1000;
|
||||
} else if(strcasecmp(unit, "h") == 0) {
|
||||
multiplier = 60 * 60 * 1000;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t max_pre_multiplication = UINT32_MAX / multiplier;
|
||||
if(duration_ms > max_pre_multiplication) return false;
|
||||
|
||||
*value = round(duration_ms * multiplier);
|
||||
return true;
|
||||
}
|
||||
|
||||
+30
-9
@@ -9,17 +9,26 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/** Extract int value and trim arguments string
|
||||
*
|
||||
* @param args - arguments string
|
||||
* @param word first argument, output
|
||||
*
|
||||
* @param args - arguments string
|
||||
* @param value first argument, output
|
||||
* @return true - success
|
||||
* @return false - arguments string does not contain int
|
||||
*/
|
||||
bool args_read_int_and_trim(FuriString* args, int* value);
|
||||
|
||||
/** Extract float value and trim arguments string
|
||||
*
|
||||
* @param [in, out] args arguments string
|
||||
* @param [out] value first argument
|
||||
* @return true - success
|
||||
* @return false - arguments string does not contain float
|
||||
*/
|
||||
bool args_read_float_and_trim(FuriString* args, float* value);
|
||||
|
||||
/**
|
||||
* @brief Extract first argument from arguments string and trim arguments string
|
||||
*
|
||||
*
|
||||
* @param args arguments string
|
||||
* @param word first argument, output
|
||||
* @return true - success
|
||||
@@ -29,7 +38,7 @@ bool args_read_string_and_trim(FuriString* args, FuriString* word);
|
||||
|
||||
/**
|
||||
* @brief Extract the first quoted argument from the argument string and trim the argument string. If the argument is not quoted, calls args_read_string_and_trim.
|
||||
*
|
||||
*
|
||||
* @param args arguments string
|
||||
* @param word first argument, output, without quotes
|
||||
* @return true - success
|
||||
@@ -39,7 +48,7 @@ bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* wor
|
||||
|
||||
/**
|
||||
* @brief Convert hex ASCII values to byte array
|
||||
*
|
||||
*
|
||||
* @param args arguments string
|
||||
* @param bytes byte array pointer, output
|
||||
* @param bytes_count needed bytes count
|
||||
@@ -48,11 +57,23 @@ bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* wor
|
||||
*/
|
||||
bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count);
|
||||
|
||||
/**
|
||||
* @brief Parses a duration value from a given string and converts it to milliseconds
|
||||
*
|
||||
* @param [in] args the input string containing the duration value. The string may include units (e.g., "10s", "0.5m").
|
||||
* @param [out] value pointer to store the parsed value in milliseconds
|
||||
* @param [in] default_unit A default unit to be used if the input string does not contain a valid suffix.
|
||||
* Supported units: `"ms"`, `"s"`, `"m"`, `"h"`
|
||||
* If NULL, the function will assume milliseconds by default.
|
||||
* @return `true` if the parsing and conversion succeeded, `false` otherwise.
|
||||
*/
|
||||
bool args_read_duration(FuriString* args, uint32_t* value, const char* default_unit);
|
||||
|
||||
/************************************ HELPERS ***************************************/
|
||||
|
||||
/**
|
||||
* @brief Get length of first word from arguments string
|
||||
*
|
||||
*
|
||||
* @param args arguments string
|
||||
* @return size_t length of first word
|
||||
*/
|
||||
@@ -60,7 +81,7 @@ size_t args_get_first_word_length(FuriString* args);
|
||||
|
||||
/**
|
||||
* @brief Get length of arguments string
|
||||
*
|
||||
*
|
||||
* @param args arguments string
|
||||
* @return size_t length of arguments string
|
||||
*/
|
||||
@@ -68,7 +89,7 @@ size_t args_length(FuriString* args);
|
||||
|
||||
/**
|
||||
* @brief Convert ASCII hex values to byte
|
||||
*
|
||||
*
|
||||
* @param hi_nibble ASCII hi nibble character
|
||||
* @param low_nibble ASCII low nibble character
|
||||
* @param byte byte pointer, output
|
||||
|
||||
@@ -15,3 +15,18 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
||||
|
||||
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
|
||||
}
|
||||
|
||||
bool cli_sleep(PipeSide* side, uint32_t duration_in_ms) {
|
||||
uint32_t passed_time = 0;
|
||||
bool is_interrupted = false;
|
||||
|
||||
do {
|
||||
uint32_t left_time = duration_in_ms - passed_time;
|
||||
uint32_t check_interval = left_time >= 100 ? 100 : left_time;
|
||||
furi_delay_ms(check_interval);
|
||||
passed_time += check_interval;
|
||||
is_interrupted = cli_is_pipe_broken_or_is_etx_next_char(side);
|
||||
} while(!is_interrupted && passed_time < duration_in_ms);
|
||||
|
||||
return !is_interrupted;
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ typedef enum {
|
||||
CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */
|
||||
} CliCommandFlag;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief CLI command execution callback pointer
|
||||
*
|
||||
*
|
||||
* This callback will be called from a separate thread spawned just for your
|
||||
* command. The pipe will be installed as the thread's stdio, so you can use
|
||||
* `printf`, `getchar` and other standard functions to communicate with the
|
||||
* user.
|
||||
*
|
||||
*
|
||||
* @param [in] pipe Pipe that can be used to send and receive data. If
|
||||
* `CliCommandFlagDontAttachStdio` was not set, you can
|
||||
* also use standard C functions (printf, getc, etc.) to
|
||||
@@ -64,7 +64,7 @@ typedef struct {
|
||||
|
||||
/**
|
||||
* @brief Detects if Ctrl+C has been pressed or session has been terminated
|
||||
*
|
||||
*
|
||||
* @param [in] side Pointer to pipe side given to the command thread
|
||||
* @warning This function also assumes that the pipe is installed as the
|
||||
* thread's stdio
|
||||
@@ -80,6 +80,21 @@ bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side);
|
||||
*/
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg);
|
||||
|
||||
/**
|
||||
* @brief Pause for a specified duration or until Ctrl+C is pressed or the
|
||||
* session is terminated.
|
||||
*
|
||||
* @param [in] side Pointer to pipe side given to the command thread.
|
||||
* @param [in] duration_in_ms Duration of sleep in milliseconds.
|
||||
* @return `true` if the sleep completed without interruption.
|
||||
* @return `false` if interrupted.
|
||||
*
|
||||
* @warning This function also assumes that the pipe is installed as the
|
||||
* thread's stdio.
|
||||
* @warning This function will consume 0 or 1 bytes from the pipe.
|
||||
*/
|
||||
bool cli_sleep(PipeSide* side, uint32_t duration_in_ms);
|
||||
|
||||
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth, app_id) \
|
||||
static const CliCommandDescriptor cli_##name##_desc = { \
|
||||
#name, \
|
||||
|
||||
@@ -442,7 +442,6 @@ static int32_t cli_shell_thread(void* context) {
|
||||
// ==========
|
||||
// Public API
|
||||
// ==========
|
||||
|
||||
CliShell* cli_shell_alloc(
|
||||
CliShellMotd motd,
|
||||
void* context,
|
||||
|
||||
+5
-14
@@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur
|
||||
furi_string_cat_printf(key_str, "%02X", key_int[i]);
|
||||
}
|
||||
|
||||
static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) {
|
||||
static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) {
|
||||
furi_assert(instance);
|
||||
furi_assert(key_str);
|
||||
furi_assert(key_int);
|
||||
furi_assert(key_out);
|
||||
|
||||
uint8_t key_byte_tmp;
|
||||
char h, l;
|
||||
|
||||
*key_int = 0ULL;
|
||||
|
||||
// Process two hex characters at a time to create each byte
|
||||
for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) {
|
||||
h = furi_string_get_char(key_str, i);
|
||||
l = furi_string_get_char(key_str, i + 1);
|
||||
|
||||
args_char_to_hex(h, l, &key_byte_tmp);
|
||||
*key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2));
|
||||
key_out[i / 2] = key_byte_tmp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) {
|
||||
bool key_read = keys_dict_get_next_key_str(instance, temp_key);
|
||||
|
||||
if(key_read) {
|
||||
size_t tmp_len = key_size;
|
||||
uint64_t key_int = 0;
|
||||
|
||||
keys_dict_str_to_int(instance, temp_key, &key_int);
|
||||
|
||||
while(tmp_len--) {
|
||||
key[tmp_len] = (uint8_t)key_int;
|
||||
key_int >>= 8;
|
||||
}
|
||||
keys_dict_str_to_int(instance, temp_key, key);
|
||||
}
|
||||
|
||||
furi_string_free(temp_key);
|
||||
|
||||
Reference in New Issue
Block a user