mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-25 01:38:11 -07:00
Merge branch 'dev-upstream' into shutdown_idle
This commit is contained in:
+13
-13
@@ -2,19 +2,19 @@ Import("env")
|
||||
|
||||
env.Append(
|
||||
LINT_SOURCES=[
|
||||
"lib/app-scened-template",
|
||||
"lib/digital_signal",
|
||||
"lib/drivers",
|
||||
"lib/flipper_format",
|
||||
"lib/infrared",
|
||||
"lib/nfc",
|
||||
"lib/one_wire",
|
||||
"lib/ST25RFAL002",
|
||||
"lib/subghz",
|
||||
"lib/toolbox",
|
||||
"lib/u8g2",
|
||||
"lib/update_util",
|
||||
"lib/print",
|
||||
Dir("app-scened-template"),
|
||||
Dir("digital_signal"),
|
||||
Dir("drivers"),
|
||||
Dir("flipper_format"),
|
||||
Dir("infrared"),
|
||||
Dir("nfc"),
|
||||
Dir("one_wire"),
|
||||
Dir("ST25RFAL002"),
|
||||
Dir("subghz"),
|
||||
Dir("toolbox"),
|
||||
Dir("u8g2"),
|
||||
Dir("update_util"),
|
||||
Dir("print"),
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("one_wire/one_wire_host_timing.h"),
|
||||
|
||||
@@ -45,11 +45,9 @@ void platformDisableIrqCallback() {
|
||||
|
||||
void platformSetIrqCallback(PlatformIrqCallback callback) {
|
||||
rfal_platform.callback = callback;
|
||||
rfal_platform.thread = furi_thread_alloc();
|
||||
|
||||
furi_thread_set_name(rfal_platform.thread, "RfalIrqDriver");
|
||||
furi_thread_set_callback(rfal_platform.thread, rfal_platform_irq_thread);
|
||||
furi_thread_set_stack_size(rfal_platform.thread, 1024);
|
||||
rfal_platform.thread =
|
||||
furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL);
|
||||
furi_thread_mark_as_service(rfal_platform.thread);
|
||||
furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr);
|
||||
furi_thread_start(rfal_platform.thread);
|
||||
|
||||
|
||||
@@ -132,6 +132,33 @@ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) {
|
||||
return bq25896_regs.r03.OTG_CONFIG;
|
||||
}
|
||||
|
||||
uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) {
|
||||
furi_hal_i2c_read_reg_8(
|
||||
handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT);
|
||||
return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840;
|
||||
}
|
||||
|
||||
void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) {
|
||||
if(vreg_voltage < 3840) {
|
||||
// Minimum value is 3840 mV
|
||||
bq25896_regs.r06.VREG = 0;
|
||||
} else {
|
||||
// Find the nearest voltage value (subtract offset, divide into sections)
|
||||
// Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
|
||||
bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16);
|
||||
}
|
||||
|
||||
// Do not allow values above 23 (0x17, 4208mV)
|
||||
// Exceeding 4.2v will overcharge the battery!
|
||||
if(bq25896_regs.r06.VREG > 23) {
|
||||
bq25896_regs.r06.VREG = 23;
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
furi_hal_i2c_write_reg_8(
|
||||
handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT);
|
||||
}
|
||||
|
||||
bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) {
|
||||
furi_hal_i2c_read_reg_8(
|
||||
handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT);
|
||||
|
||||
@@ -36,6 +36,15 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle);
|
||||
/** Is otg enabled */
|
||||
bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Get VREG (charging) voltage in mV */
|
||||
uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle);
|
||||
|
||||
/** Set VREG (charging) voltage in mV
|
||||
*
|
||||
* Valid range: 3840mV - 4208mV, in steps of 16mV
|
||||
*/
|
||||
void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage);
|
||||
|
||||
/** Check OTG BOOST Fault status */
|
||||
bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle);
|
||||
|
||||
|
||||
@@ -104,11 +104,8 @@ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
|
||||
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
|
||||
furi_check(manifest->stack_size > 0);
|
||||
|
||||
app->thread = furi_thread_alloc();
|
||||
furi_thread_set_stack_size(app->thread, manifest->stack_size);
|
||||
furi_thread_set_name(app->thread, manifest->name);
|
||||
furi_thread_set_callback(app->thread, flipper_application_thread);
|
||||
furi_thread_set_context(app->thread, args);
|
||||
app->thread = furi_thread_alloc_ex(
|
||||
manifest->name, manifest->stack_size, flipper_application_thread, args);
|
||||
|
||||
return app->thread;
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa
|
||||
furi_crash("Unknown FF type");
|
||||
}
|
||||
|
||||
if((size_t)(i + 1) < write_data->data_size) {
|
||||
if(((size_t)i + 1) < write_data->data_size) {
|
||||
furi_string_cat(value, " ");
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,8 @@ static InfraredStatus infrared_common_decode_bits(InfraredCommonDecoder* decoder
|
||||
if(timings->min_split_time && !level) {
|
||||
if(timing > timings->min_split_time) {
|
||||
/* long low timing - check if we're ready for any of protocol modification */
|
||||
for(size_t i = 0; decoder->protocol->databit_len[i] &&
|
||||
(i < COUNT_OF(decoder->protocol->databit_len));
|
||||
for(size_t i = 0; i < COUNT_OF(decoder->protocol->databit_len) &&
|
||||
decoder->protocol->databit_len[i];
|
||||
++i) {
|
||||
if(decoder->protocol->databit_len[i] == decoder->databit_cnt) {
|
||||
return InfraredStatusReady;
|
||||
@@ -199,7 +199,7 @@ InfraredMessage* infrared_common_decoder_check_ready(InfraredCommonDecoder* deco
|
||||
bool found_length = false;
|
||||
|
||||
for(size_t i = 0;
|
||||
decoder->protocol->databit_len[i] && (i < COUNT_OF(decoder->protocol->databit_len));
|
||||
i < COUNT_OF(decoder->protocol->databit_len) && decoder->protocol->databit_len[i];
|
||||
++i) {
|
||||
if(decoder->protocol->databit_len[i] == decoder->databit_cnt) {
|
||||
found_length = true;
|
||||
|
||||
@@ -115,3 +115,26 @@ const InfraredCommonProtocolSpec protocol_sirc = {
|
||||
.decode_repeat = NULL,
|
||||
.encode_repeat = infrared_encoder_sirc_encode_repeat,
|
||||
};
|
||||
|
||||
const InfraredCommonProtocolSpec protocol_kaseikyo = {
|
||||
.timings =
|
||||
{
|
||||
.preamble_mark = INFRARED_KASEIKYO_PREAMBLE_MARK,
|
||||
.preamble_space = INFRARED_KASEIKYO_PREAMBLE_SPACE,
|
||||
.bit1_mark = INFRARED_KASEIKYO_BIT1_MARK,
|
||||
.bit1_space = INFRARED_KASEIKYO_BIT1_SPACE,
|
||||
.bit0_mark = INFRARED_KASEIKYO_BIT0_MARK,
|
||||
.bit0_space = INFRARED_KASEIKYO_BIT0_SPACE,
|
||||
.preamble_tolerance = INFRARED_KASEIKYO_PREAMBLE_TOLERANCE,
|
||||
.bit_tolerance = INFRARED_KASEIKYO_BIT_TOLERANCE,
|
||||
.silence_time = INFRARED_KASEIKYO_SILENCE,
|
||||
.min_split_time = INFRARED_KASEIKYO_MIN_SPLIT_TIME,
|
||||
},
|
||||
.databit_len[0] = 48,
|
||||
.no_stop_bit = false,
|
||||
.decode = infrared_common_decode_pdwm,
|
||||
.encode = infrared_common_encode_pdwm,
|
||||
.interpret = infrared_decoder_kaseikyo_interpret,
|
||||
.decode_repeat = NULL,
|
||||
.encode_repeat = NULL,
|
||||
};
|
||||
|
||||
@@ -110,6 +110,20 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = {
|
||||
.free = infrared_encoder_sirc_free},
|
||||
.get_protocol_spec = infrared_sirc_get_spec,
|
||||
},
|
||||
{
|
||||
.decoder =
|
||||
{.alloc = infrared_decoder_kaseikyo_alloc,
|
||||
.decode = infrared_decoder_kaseikyo_decode,
|
||||
.reset = infrared_decoder_kaseikyo_reset,
|
||||
.check_ready = infrared_decoder_kaseikyo_check_ready,
|
||||
.free = infrared_decoder_kaseikyo_free},
|
||||
.encoder =
|
||||
{.alloc = infrared_encoder_kaseikyo_alloc,
|
||||
.encode = infrared_encoder_kaseikyo_encode,
|
||||
.reset = infrared_encoder_kaseikyo_reset,
|
||||
.free = infrared_encoder_kaseikyo_free},
|
||||
.get_protocol_spec = infrared_kaseikyo_get_spec,
|
||||
},
|
||||
};
|
||||
|
||||
static int infrared_find_index_by_protocol(InfraredProtocol protocol);
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef enum {
|
||||
InfraredProtocolSIRC,
|
||||
InfraredProtocolSIRC15,
|
||||
InfraredProtocolSIRC20,
|
||||
InfraredProtocolKaseikyo,
|
||||
InfraredProtocolMAX,
|
||||
} InfraredProtocol;
|
||||
|
||||
|
||||
@@ -267,3 +267,54 @@ InfraredStatus infrared_encoder_sirc_encode_repeat(
|
||||
bool* level);
|
||||
|
||||
extern const InfraredCommonProtocolSpec protocol_sirc;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Kaseikyo protocol description
|
||||
* https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_Kaseikyo.hpp
|
||||
****************************************************************************************************
|
||||
* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble
|
||||
* mark space Modulation up to period repeat repeat
|
||||
* mark space
|
||||
*
|
||||
* 3360 1665 48 bit ...130000 3456 1728
|
||||
* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________
|
||||
* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ___________
|
||||
*
|
||||
***************************************************************************************************/
|
||||
|
||||
#define INFRARED_KASEIKYO_UNIT 432
|
||||
#define INFRARED_KASEIKYO_PREAMBLE_MARK (8 * INFRARED_KASEIKYO_UNIT)
|
||||
#define INFRARED_KASEIKYO_PREAMBLE_SPACE (4 * INFRARED_KASEIKYO_UNIT)
|
||||
#define INFRARED_KASEIKYO_BIT1_MARK INFRARED_KASEIKYO_UNIT
|
||||
#define INFRARED_KASEIKYO_BIT1_SPACE (3 * INFRARED_KASEIKYO_UNIT)
|
||||
#define INFRARED_KASEIKYO_BIT0_MARK INFRARED_KASEIKYO_UNIT
|
||||
#define INFRARED_KASEIKYO_BIT0_SPACE INFRARED_KASEIKYO_UNIT
|
||||
#define INFRARED_KASEIKYO_REPEAT_PERIOD 130000
|
||||
#define INFRARED_KASEIKYO_SILENCE INFRARED_KASEIKYO_REPEAT_PERIOD
|
||||
#define INFRARED_KASEIKYO_MIN_SPLIT_TIME INFRARED_KASEIKYO_REPEAT_PAUSE_MIN
|
||||
#define INFRARED_KASEIKYO_REPEAT_PAUSE_MIN 4000
|
||||
#define INFRARED_KASEIKYO_REPEAT_PAUSE_MAX 150000
|
||||
#define INFRARED_KASEIKYO_REPEAT_MARK INFRARED_KASEIKYO_PREAMBLE_MARK
|
||||
#define INFRARED_KASEIKYO_REPEAT_SPACE (INFRARED_KASEIKYO_REPEAT_PERIOD - 56000)
|
||||
#define INFRARED_KASEIKYO_PREAMBLE_TOLERANCE 200 // us
|
||||
#define INFRARED_KASEIKYO_BIT_TOLERANCE 120 // us
|
||||
|
||||
void* infrared_decoder_kaseikyo_alloc(void);
|
||||
void infrared_decoder_kaseikyo_reset(void* decoder);
|
||||
void infrared_decoder_kaseikyo_free(void* decoder);
|
||||
InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* decoder);
|
||||
InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration);
|
||||
void* infrared_encoder_kaseikyo_alloc(void);
|
||||
InfraredStatus
|
||||
infrared_encoder_kaseikyo_encode(void* encoder_ptr, uint32_t* duration, bool* level);
|
||||
void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message);
|
||||
void infrared_encoder_kaseikyo_free(void* encoder_ptr);
|
||||
bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder);
|
||||
InfraredStatus infrared_decoder_kaseikyo_decode_repeat(InfraredCommonDecoder* decoder);
|
||||
InfraredStatus infrared_encoder_kaseikyo_encode_repeat(
|
||||
InfraredCommonEncoder* encoder,
|
||||
uint32_t* duration,
|
||||
bool* level);
|
||||
const InfraredProtocolSpecification* infrared_kaseikyo_get_spec(InfraredProtocol protocol);
|
||||
|
||||
extern const InfraredCommonProtocolSpec protocol_kaseikyo;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "infrared.h"
|
||||
#include "infrared_protocol_defs_i.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#include "../infrared_i.h"
|
||||
|
||||
InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* ctx) {
|
||||
return infrared_common_decoder_check_ready(ctx);
|
||||
}
|
||||
|
||||
bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder) {
|
||||
furi_assert(decoder);
|
||||
|
||||
bool result = false;
|
||||
uint16_t vendor_id = ((uint16_t)(decoder->data[1]) << 8) | (uint16_t)decoder->data[0];
|
||||
uint8_t vendor_parity = decoder->data[2] & 0x0f;
|
||||
uint8_t genre1 = decoder->data[2] >> 4;
|
||||
uint8_t genre2 = decoder->data[3] & 0x0f;
|
||||
uint16_t data = (uint16_t)(decoder->data[3] >> 4) | ((uint16_t)(decoder->data[4] & 0x3f) << 4);
|
||||
uint8_t id = decoder->data[4] >> 6;
|
||||
uint8_t parity = decoder->data[5];
|
||||
|
||||
uint8_t vendor_parity_check = decoder->data[0] ^ decoder->data[1];
|
||||
vendor_parity_check = (vendor_parity_check & 0xf) ^ (vendor_parity_check >> 4);
|
||||
uint8_t parity_check = decoder->data[2] ^ decoder->data[3] ^ decoder->data[4];
|
||||
|
||||
if(vendor_parity == vendor_parity_check && parity == parity_check) {
|
||||
decoder->message.command = (uint32_t)data;
|
||||
decoder->message.address = ((uint32_t)id << 24) | ((uint32_t)vendor_id << 8) |
|
||||
((uint32_t)genre1 << 4) | (uint32_t)genre2;
|
||||
decoder->message.protocol = InfraredProtocolKaseikyo;
|
||||
decoder->message.repeat = false;
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void* infrared_decoder_kaseikyo_alloc(void) {
|
||||
return infrared_common_decoder_alloc(&protocol_kaseikyo);
|
||||
}
|
||||
|
||||
InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration) {
|
||||
return infrared_common_decode(decoder, level, duration);
|
||||
}
|
||||
|
||||
void infrared_decoder_kaseikyo_free(void* decoder) {
|
||||
infrared_common_decoder_free(decoder);
|
||||
}
|
||||
|
||||
void infrared_decoder_kaseikyo_reset(void* decoder) {
|
||||
infrared_common_decoder_reset(decoder);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <core/check.h>
|
||||
#include "common/infrared_common_i.h"
|
||||
#include <stdint.h>
|
||||
#include "../infrared_i.h"
|
||||
#include "infrared_protocol_defs_i.h"
|
||||
#include <furi.h>
|
||||
|
||||
void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message) {
|
||||
furi_assert(encoder_ptr);
|
||||
|
||||
InfraredCommonEncoder* encoder = encoder_ptr;
|
||||
infrared_common_encoder_reset(encoder);
|
||||
|
||||
uint32_t address = message->address;
|
||||
uint16_t command = message->command;
|
||||
|
||||
uint8_t id = (address >> 24) & 3;
|
||||
uint16_t vendor_id = (address >> 8) & 0xffff;
|
||||
uint8_t genre1 = (address >> 4) & 0xf;
|
||||
uint8_t genre2 = address & 0xf;
|
||||
|
||||
encoder->data[0] = (uint8_t)(vendor_id & 0xff);
|
||||
encoder->data[1] = (uint8_t)(vendor_id >> 8);
|
||||
uint8_t vendor_parity = encoder->data[0] ^ encoder->data[1];
|
||||
vendor_parity = (vendor_parity & 0xf) ^ (vendor_parity >> 4);
|
||||
encoder->data[2] = (vendor_parity & 0xf) | (genre1 << 4);
|
||||
encoder->data[3] = (genre2 & 0xf) | ((uint8_t)(command & 0xf) << 4);
|
||||
encoder->data[4] = (id << 6) | (uint8_t)(command >> 4);
|
||||
encoder->data[5] = encoder->data[2] ^ encoder->data[3] ^ encoder->data[4];
|
||||
|
||||
encoder->bits_to_encode = encoder->protocol->databit_len[0];
|
||||
}
|
||||
|
||||
void* infrared_encoder_kaseikyo_alloc(void) {
|
||||
return infrared_common_encoder_alloc(&protocol_kaseikyo);
|
||||
}
|
||||
|
||||
void infrared_encoder_kaseikyo_free(void* encoder_ptr) {
|
||||
infrared_common_encoder_free(encoder_ptr);
|
||||
}
|
||||
|
||||
InfraredStatus
|
||||
infrared_encoder_kaseikyo_encode(void* encoder_ptr, uint32_t* duration, bool* level) {
|
||||
return infrared_common_encode(encoder_ptr, duration, level);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "../infrared_i.h"
|
||||
#include "infrared_protocol_defs_i.h"
|
||||
|
||||
static const InfraredProtocolSpecification infrared_kaseikyo_protocol_specification = {
|
||||
.name = "Kaseikyo",
|
||||
.address_length = 26,
|
||||
.command_length = 10,
|
||||
.frequency = INFRARED_COMMON_CARRIER_FREQUENCY,
|
||||
.duty_cycle = INFRARED_COMMON_DUTY_CYCLE,
|
||||
};
|
||||
|
||||
const InfraredProtocolSpecification* infrared_kaseikyo_get_spec(InfraredProtocol protocol) {
|
||||
if(protocol == InfraredProtocolKaseikyo)
|
||||
return &infrared_kaseikyo_protocol_specification;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
@@ -223,10 +223,7 @@ void infrared_worker_rx_set_received_signal_callback(
|
||||
InfraredWorker* infrared_worker_alloc() {
|
||||
InfraredWorker* instance = malloc(sizeof(InfraredWorker));
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "InfraredWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 2048);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
instance->thread = furi_thread_alloc_ex("InfraredWorker", 2048, NULL, instance);
|
||||
|
||||
size_t buffer_size =
|
||||
MAX(sizeof(InfraredWorkerTiming) * (MAX_TIMINGS_AMOUNT + 1),
|
||||
|
||||
@@ -2,7 +2,7 @@ Import("env")
|
||||
|
||||
env.Append(
|
||||
LINT_SOURCES=[
|
||||
"lib/lfrfid",
|
||||
Dir("."),
|
||||
],
|
||||
CPPPATH=[
|
||||
"#/lib/lfrfid",
|
||||
|
||||
@@ -61,10 +61,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context);
|
||||
LFRFIDRawWorker* lfrfid_raw_worker_alloc() {
|
||||
LFRFIDRawWorker* worker = malloc(sizeof(LFRFIDRawWorker));
|
||||
|
||||
worker->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(worker->thread, "lfrfid_raw_worker");
|
||||
furi_thread_set_context(worker->thread, worker);
|
||||
furi_thread_set_stack_size(worker->thread, 2048);
|
||||
worker->thread = furi_thread_alloc_ex("LfrfidRawWorker", 2048, NULL, worker);
|
||||
|
||||
worker->events = furi_event_flag_alloc(NULL);
|
||||
|
||||
|
||||
@@ -29,11 +29,7 @@ LFRFIDWorker* lfrfid_worker_alloc(ProtocolDict* dict) {
|
||||
worker->raw_filename = NULL;
|
||||
worker->mode_storage = NULL;
|
||||
|
||||
worker->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(worker->thread, "lfrfid_worker");
|
||||
furi_thread_set_callback(worker->thread, lfrfid_worker_thread);
|
||||
furi_thread_set_context(worker->thread, worker);
|
||||
furi_thread_set_stack_size(worker->thread, 2048);
|
||||
worker->thread = furi_thread_alloc_ex("LfrfidWorker", 2048, lfrfid_worker_thread, worker);
|
||||
|
||||
worker->protocols = dict;
|
||||
|
||||
@@ -140,9 +136,8 @@ size_t lfrfid_worker_dict_get_data_size(LFRFIDWorker* worker, LFRFIDProtocol pro
|
||||
|
||||
static int32_t lfrfid_worker_thread(void* thread_context) {
|
||||
LFRFIDWorker* worker = thread_context;
|
||||
bool running = true;
|
||||
|
||||
while(running) {
|
||||
while(true) {
|
||||
uint32_t flags = furi_thread_flags_wait(LFRFIDEventAll, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags != FuriFlagErrorTimeout) {
|
||||
// stop thread
|
||||
|
||||
+1
-1
Submodule lib/libusb_stm32 updated: 6a88ec4d77...9168e2a31d
+1
-1
@@ -5,7 +5,7 @@ env.Append(
|
||||
"#/lib/nfc",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("#/lib/nfc/nfc_device.h"),
|
||||
File("nfc_device.h"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -104,11 +104,8 @@ ReaderAnalyzer* reader_analyzer_alloc() {
|
||||
instance->stream =
|
||||
furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader));
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "ReaderAnalyzerWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 2048);
|
||||
furi_thread_set_callback(instance->thread, reader_analyzer_thread);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance);
|
||||
furi_thread_set_priority(instance->thread, FuriThreadPriorityLow);
|
||||
|
||||
return instance;
|
||||
|
||||
+55
-34
@@ -7,11 +7,12 @@
|
||||
#include <lib/nfc/protocols/nfc_util.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/cache")
|
||||
#define TAG "NfcDevice"
|
||||
#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache")
|
||||
#define NFC_DEVICE_KEYS_EXTENSION ".keys"
|
||||
|
||||
static const char* nfc_file_header = "Flipper NFC device";
|
||||
static const uint32_t nfc_file_version = 2;
|
||||
static const uint32_t nfc_file_version = 3;
|
||||
|
||||
static const char* nfc_keys_file_header = "Flipper NFC keys";
|
||||
static const uint32_t nfc_keys_file_version = 1;
|
||||
@@ -26,6 +27,11 @@ NfcDevice* nfc_device_alloc() {
|
||||
nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
nfc_dev->load_path = furi_string_alloc();
|
||||
nfc_dev->dev_data.parsed_data = furi_string_alloc();
|
||||
|
||||
// Rename cache folder name for backward compatibility
|
||||
if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) {
|
||||
storage_common_rename(nfc_dev->storage, "/ext/nfc/cache", NFC_DEVICE_KEYS_FOLDER);
|
||||
}
|
||||
return nfc_dev;
|
||||
}
|
||||
|
||||
@@ -213,6 +219,9 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
uint32_t auth_counter;
|
||||
if(!flipper_format_read_uint32(file, "Failed authentication attempts", &auth_counter, 1))
|
||||
auth_counter = 0;
|
||||
data->curr_authlim = auth_counter;
|
||||
|
||||
data->auth_success = mf_ul_is_full_capture(data);
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
@@ -627,7 +636,10 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
*app_head = app;
|
||||
app_head = &app->next;
|
||||
}
|
||||
if(!parsed_apps) break;
|
||||
if(!parsed_apps) {
|
||||
// accept non-parsed apps, just log a warning:
|
||||
FURI_LOG_W(TAG, "Non-parsed apps found!");
|
||||
}
|
||||
}
|
||||
parsed = true;
|
||||
} while(false);
|
||||
@@ -1006,12 +1018,7 @@ static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow
|
||||
furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION);
|
||||
}
|
||||
|
||||
static bool nfc_device_save_file(
|
||||
NfcDevice* dev,
|
||||
const char* dev_name,
|
||||
const char* folder,
|
||||
const char* extension,
|
||||
bool use_load_path) {
|
||||
bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
|
||||
furi_assert(dev);
|
||||
|
||||
bool saved = false;
|
||||
@@ -1021,19 +1028,10 @@ static bool nfc_device_save_file(
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(use_load_path && !furi_string_empty(dev->load_path)) {
|
||||
// Get directory name
|
||||
path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
|
||||
// Create nfc directory if necessary
|
||||
if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break;
|
||||
// Make path to file to save
|
||||
furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
|
||||
} else {
|
||||
// Create nfc directory if necessary
|
||||
if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break;
|
||||
// First remove nfc device file if it was saved
|
||||
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
|
||||
}
|
||||
// Create nfc directory if necessary
|
||||
if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break;
|
||||
// First remove nfc device file if it was saved
|
||||
furi_string_printf(temp_str, "%s", dev_name);
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
// Write header
|
||||
@@ -1048,7 +1046,9 @@ static bool nfc_device_save_file(
|
||||
if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break;
|
||||
if(!flipper_format_write_hex(file, "ATQA", data->atqa, 2)) break;
|
||||
// Save ATQA in MSB order for correct companion apps display
|
||||
uint8_t atqa[2] = {data->atqa[1], data->atqa[0]};
|
||||
if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break;
|
||||
if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break;
|
||||
// Save more data if necessary
|
||||
if(dev->format == NfcDeviceSaveFormatMifareUl) {
|
||||
@@ -1072,13 +1072,19 @@ static bool nfc_device_save_file(
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
|
||||
return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true);
|
||||
}
|
||||
|
||||
bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) {
|
||||
bool nfc_device_save_shadow(NfcDevice* dev, const char* path) {
|
||||
dev->shadow_file_exist = true;
|
||||
return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true);
|
||||
// Replace extension from .nfc to .shd if necessary
|
||||
FuriString* orig_path = furi_string_alloc();
|
||||
furi_string_set_str(orig_path, path);
|
||||
FuriString* shadow_path = furi_string_alloc();
|
||||
nfc_device_get_shadow_path(orig_path, shadow_path);
|
||||
|
||||
bool file_saved = nfc_device_save(dev, furi_string_get_cstr(shadow_path));
|
||||
furi_string_free(orig_path);
|
||||
furi_string_free(shadow_path);
|
||||
|
||||
return file_saved;
|
||||
}
|
||||
|
||||
static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) {
|
||||
@@ -1090,6 +1096,9 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia
|
||||
temp_str = furi_string_alloc();
|
||||
bool deprecated_version = false;
|
||||
|
||||
// Version 2 of file format had ATQA bytes swapped
|
||||
uint32_t version_with_lsb_atqa = 2;
|
||||
|
||||
if(dev->loading_cb) {
|
||||
dev->loading_cb(dev->loading_cb_ctx, true);
|
||||
}
|
||||
@@ -1108,9 +1117,12 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia
|
||||
// Read and verify file header
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
if(furi_string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) {
|
||||
deprecated_version = true;
|
||||
break;
|
||||
if(furi_string_cmp_str(temp_str, nfc_file_header)) break;
|
||||
if(version != nfc_file_version) {
|
||||
if(version < version_with_lsb_atqa) {
|
||||
deprecated_version = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Read Nfc device type
|
||||
if(!flipper_format_read_string(file, "Device type", temp_str)) break;
|
||||
@@ -1120,7 +1132,14 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia
|
||||
if(!(data_cnt == 4 || data_cnt == 7)) break;
|
||||
data->uid_len = data_cnt;
|
||||
if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break;
|
||||
if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break;
|
||||
if(version == version_with_lsb_atqa) {
|
||||
if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break;
|
||||
} else {
|
||||
uint8_t atqa[2] = {};
|
||||
if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break;
|
||||
data->atqa[0] = atqa[1];
|
||||
data->atqa[1] = atqa[0];
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break;
|
||||
// Load CUID
|
||||
uint8_t* cuid_start = data->uid;
|
||||
@@ -1188,14 +1207,16 @@ bool nfc_file_select(NfcDevice* dev) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = NFC_APP_EXTENSION,
|
||||
.skip_assets = true,
|
||||
.hide_dot_files = true,
|
||||
.icon = &I_Nfc_10px,
|
||||
.hide_ext = true,
|
||||
.item_loader_callback = NULL,
|
||||
.item_loader_context = NULL,
|
||||
.base_path = NFC_APP_FOLDER,
|
||||
};
|
||||
|
||||
bool res =
|
||||
dialog_file_browser_show(dev->dialogs, dev->load_path, nfc_app_folder, &browser_options);
|
||||
dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options);
|
||||
|
||||
furi_string_free(nfc_app_folder);
|
||||
if(res) {
|
||||
|
||||
@@ -51,12 +51,23 @@ typedef struct {
|
||||
MfClassicDict* dict;
|
||||
} NfcMfClassicDictAttackData;
|
||||
|
||||
typedef enum {
|
||||
NfcReadModeAuto,
|
||||
NfcReadModeMfClassic,
|
||||
NfcReadModeMfUltralight,
|
||||
NfcReadModeMfDesfire,
|
||||
NfcReadModeEMV,
|
||||
NfcReadModeNFCA,
|
||||
} NfcReadMode;
|
||||
|
||||
typedef struct {
|
||||
FuriHalNfcDevData nfc_data;
|
||||
NfcProtocol protocol;
|
||||
NfcReadMode read_mode;
|
||||
union {
|
||||
NfcReaderRequestData reader_data;
|
||||
NfcMfClassicDictAttackData mf_classic_dict_attack_data;
|
||||
MfUltralightAuth mf_ul_auth;
|
||||
};
|
||||
union {
|
||||
EmvData emv_data;
|
||||
|
||||
+103
-12
@@ -12,11 +12,7 @@ NfcWorker* nfc_worker_alloc() {
|
||||
NfcWorker* nfc_worker = malloc(sizeof(NfcWorker));
|
||||
|
||||
// Worker thread attributes
|
||||
nfc_worker->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(nfc_worker->thread, "NfcWorker");
|
||||
furi_thread_set_stack_size(nfc_worker->thread, 8192);
|
||||
furi_thread_set_callback(nfc_worker->thread, nfc_worker_task);
|
||||
furi_thread_set_context(nfc_worker->thread, nfc_worker);
|
||||
nfc_worker->thread = furi_thread_alloc_ex("NfcWorker", 8192, nfc_worker_task, nfc_worker);
|
||||
|
||||
nfc_worker->callback = NULL;
|
||||
nfc_worker->context = NULL;
|
||||
@@ -70,12 +66,12 @@ void nfc_worker_start(
|
||||
|
||||
void nfc_worker_stop(NfcWorker* nfc_worker) {
|
||||
furi_assert(nfc_worker);
|
||||
if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) {
|
||||
return;
|
||||
furi_assert(nfc_worker->thread);
|
||||
if(furi_thread_get_state(nfc_worker->thread) != FuriThreadStateStopped) {
|
||||
furi_hal_nfc_stop();
|
||||
nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
|
||||
furi_thread_join(nfc_worker->thread);
|
||||
}
|
||||
furi_hal_nfc_stop();
|
||||
nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
|
||||
furi_thread_join(nfc_worker->thread);
|
||||
}
|
||||
|
||||
void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) {
|
||||
@@ -90,7 +86,11 @@ int32_t nfc_worker_task(void* context) {
|
||||
furi_hal_nfc_exit_sleep();
|
||||
|
||||
if(nfc_worker->state == NfcWorkerStateRead) {
|
||||
nfc_worker_read(nfc_worker);
|
||||
if(nfc_worker->dev_data->read_mode == NfcReadModeAuto) {
|
||||
nfc_worker_read(nfc_worker);
|
||||
} else {
|
||||
nfc_worker_read_type(nfc_worker);
|
||||
}
|
||||
} else if(nfc_worker->state == NfcWorkerStateUidEmulate) {
|
||||
nfc_worker_emulate_uid(nfc_worker);
|
||||
} else if(nfc_worker->state == NfcWorkerStateEmulateApdu) {
|
||||
@@ -394,6 +394,81 @@ void nfc_worker_read(NfcWorker* nfc_worker) {
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_worker_read_type(NfcWorker* nfc_worker) {
|
||||
furi_assert(nfc_worker);
|
||||
furi_assert(nfc_worker->callback);
|
||||
|
||||
NfcReadMode read_mode = nfc_worker->dev_data->read_mode;
|
||||
nfc_device_data_clear(nfc_worker->dev_data);
|
||||
NfcDeviceData* dev_data = nfc_worker->dev_data;
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
FuriHalNfcTxRxContext tx_rx = {};
|
||||
NfcWorkerEvent event = 0;
|
||||
bool card_not_detected_notified = false;
|
||||
|
||||
while(nfc_worker->state == NfcWorkerStateRead) {
|
||||
if(furi_hal_nfc_detect(nfc_data, 300)) {
|
||||
FURI_LOG_D(TAG, "Card detected");
|
||||
furi_hal_nfc_sleep();
|
||||
// Process first found device
|
||||
nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
|
||||
card_not_detected_notified = false;
|
||||
if(nfc_data->type == FuriHalNfcTypeA) {
|
||||
if(read_mode == NfcReadModeMfClassic) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic;
|
||||
nfc_worker->dev_data->mf_classic_data.type = mf_classic_get_classic_type(
|
||||
nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
if(nfc_worker_read_mf_classic(nfc_worker, &tx_rx)) {
|
||||
FURI_LOG_D(TAG, "Card read");
|
||||
dev_data->protocol = NfcDeviceProtocolMifareClassic;
|
||||
event = NfcWorkerEventReadMfClassicDone;
|
||||
break;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Card read failed");
|
||||
dev_data->protocol = NfcDeviceProtocolMifareClassic;
|
||||
event = NfcWorkerEventReadMfClassicDictAttackRequired;
|
||||
break;
|
||||
}
|
||||
} else if(read_mode == NfcReadModeMfUltralight) {
|
||||
FURI_LOG_I(TAG, "Mifare Ultralight / NTAG");
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl;
|
||||
if(nfc_worker_read_mf_ultralight(nfc_worker, &tx_rx)) {
|
||||
event = NfcWorkerEventReadMfUltralight;
|
||||
break;
|
||||
}
|
||||
} else if(read_mode == NfcReadModeMfDesfire) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire;
|
||||
if(nfc_worker_read_mf_desfire(nfc_worker, &tx_rx)) {
|
||||
event = NfcWorkerEventReadMfDesfire;
|
||||
break;
|
||||
}
|
||||
} else if(read_mode == NfcReadModeEMV) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
|
||||
if(nfc_worker_read_bank_card(nfc_worker, &tx_rx)) {
|
||||
event = NfcWorkerEventReadBankCard;
|
||||
break;
|
||||
}
|
||||
} else if(read_mode == NfcReadModeNFCA) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||
event = NfcWorkerEventReadUidNfcA;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(!card_not_detected_notified) {
|
||||
nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
|
||||
card_not_detected_notified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
// Notify caller and exit
|
||||
if(event > NfcWorkerEventReserved) {
|
||||
nfc_worker->callback(event, nfc_worker->context);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_worker_emulate_uid(NfcWorker* nfc_worker) {
|
||||
FuriHalNfcTxRxContext tx_rx = {};
|
||||
FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data;
|
||||
@@ -452,10 +527,25 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcWorker* nfc_worker = context;
|
||||
nfc_worker->dev_data->mf_ul_auth = auth;
|
||||
if(nfc_worker->callback) {
|
||||
nfc_worker->callback(NfcWorkerEventMfUltralightPwdAuth, nfc_worker->context);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) {
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
MfUltralightEmulator emulator = {};
|
||||
mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data);
|
||||
|
||||
// TODO rework with reader analyzer
|
||||
emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback;
|
||||
emulator.context = nfc_worker;
|
||||
|
||||
while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) {
|
||||
mf_ul_reset_emulation(&emulator, true);
|
||||
furi_hal_nfc_emulate_nfca(
|
||||
@@ -830,7 +920,8 @@ void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) {
|
||||
if(furi_hal_nfc_detect(nfc_data, 300) && nfc_data->type == FuriHalNfcTypeA) {
|
||||
if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
|
||||
nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
|
||||
if(data->auth_method == MfUltralightAuthMethodManual) {
|
||||
if(data->auth_method == MfUltralightAuthMethodManual ||
|
||||
data->auth_method == MfUltralightAuthMethodAuto) {
|
||||
nfc_worker->callback(NfcWorkerEventMfUltralightPassKey, nfc_worker->context);
|
||||
key = nfc_util_bytes2num(data->auth_key, 4);
|
||||
} else if(data->auth_method == MfUltralightAuthMethodAmeebo) {
|
||||
|
||||
@@ -7,7 +7,6 @@ typedef struct NfcWorker NfcWorker;
|
||||
typedef enum {
|
||||
// Init states
|
||||
NfcWorkerStateNone,
|
||||
NfcWorkerStateBroken,
|
||||
NfcWorkerStateReady,
|
||||
// Main worker states
|
||||
NfcWorkerStateRead,
|
||||
@@ -66,8 +65,8 @@ typedef enum {
|
||||
NfcWorkerEventDetectReaderMfkeyCollected,
|
||||
|
||||
// Mifare Ultralight events
|
||||
NfcWorkerEventMfUltralightPassKey,
|
||||
|
||||
NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key
|
||||
NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command
|
||||
} NfcWorkerEvent;
|
||||
|
||||
typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
|
||||
|
||||
@@ -35,6 +35,8 @@ int32_t nfc_worker_task(void* context);
|
||||
|
||||
void nfc_worker_read(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_read_type(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_emulate_uid(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker);
|
||||
|
||||
@@ -82,7 +82,7 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t mf_classic_get_total_block_num(MfClassicType type) {
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
return 64;
|
||||
} else if(type == MfClassicType4k) {
|
||||
@@ -712,46 +712,16 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t sectors_read = 0;
|
||||
Crypto1 crypto = {};
|
||||
uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type);
|
||||
uint64_t key_a = 0;
|
||||
uint64_t key_b = 0;
|
||||
MfClassicSectorReader sec_reader = {};
|
||||
MfClassicSector temp_sector = {};
|
||||
|
||||
for(size_t i = 0; i < total_sectors; i++) {
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
|
||||
// Load key A
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
|
||||
sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
} else {
|
||||
sec_reader.key_a = MF_CLASSIC_NO_KEY;
|
||||
}
|
||||
// Load key B
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
|
||||
sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6);
|
||||
} else {
|
||||
sec_reader.key_b = MF_CLASSIC_NO_KEY;
|
||||
}
|
||||
if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) {
|
||||
sec_reader.sector_num = i;
|
||||
if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) {
|
||||
uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
|
||||
for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
|
||||
mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
|
||||
}
|
||||
sectors_read++;
|
||||
} else {
|
||||
// Invalid key, set it to not found
|
||||
if(key_a != MF_CLASSIC_NO_KEY) {
|
||||
mf_classic_set_key_not_found(data, i, MfClassicKeyA);
|
||||
} else {
|
||||
mf_classic_set_key_not_found(data, i, MfClassicKeyB);
|
||||
}
|
||||
}
|
||||
}
|
||||
mf_classic_read_sector(tx_rx, data, i);
|
||||
}
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found);
|
||||
FURI_LOG_D(TAG, "Read %d sectors and %d keys", sectors_read, keys_found);
|
||||
|
||||
return sectors_read;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t S
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type);
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector);
|
||||
|
||||
bool mf_classic_is_sector_trailer(uint8_t block);
|
||||
|
||||
@@ -51,7 +51,7 @@ void mf_ul_reset(MfUltralightData* data) {
|
||||
data->data_size = 0;
|
||||
data->data_read = 0;
|
||||
data->curr_authlim = 0;
|
||||
data->has_auth = false;
|
||||
data->auth_success = false;
|
||||
}
|
||||
|
||||
static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) {
|
||||
@@ -756,6 +756,34 @@ bool mf_ul_read_card(
|
||||
mf_ultralight_read_tearing_flags(tx_rx, data);
|
||||
}
|
||||
data->curr_authlim = 0;
|
||||
|
||||
if(reader->pages_read == reader->pages_to_read &&
|
||||
reader->supported_features & MfUltralightSupportAuth && !data->auth_success) {
|
||||
MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data);
|
||||
if(config->access.authlim == 0) {
|
||||
// Attempt to auth with default PWD
|
||||
uint16_t pack;
|
||||
data->auth_success = mf_ultralight_authenticate(tx_rx, MF_UL_DEFAULT_PWD, &pack);
|
||||
if(data->auth_success) {
|
||||
config->auth_data.pwd.value = MF_UL_DEFAULT_PWD;
|
||||
config->auth_data.pack.value = pack;
|
||||
} else {
|
||||
furi_hal_nfc_sleep();
|
||||
furi_hal_nfc_activate_nfca(300, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(reader->pages_read != reader->pages_to_read) {
|
||||
if(reader->supported_features & MfUltralightSupportAuth) {
|
||||
// Probably password protected, fix AUTH0 and PROT so before AUTH0
|
||||
// can be written and since AUTH0 won't be readable, like on the
|
||||
// original card
|
||||
MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data);
|
||||
config->auth0 = reader->pages_read;
|
||||
config->access.prot = true;
|
||||
}
|
||||
}
|
||||
|
||||
return card_read;
|
||||
@@ -1201,6 +1229,8 @@ static void mf_ul_emulate_write(
|
||||
}
|
||||
|
||||
void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) {
|
||||
emulator->comp_write_cmd_started = false;
|
||||
emulator->sector_select_cmd_started = false;
|
||||
emulator->curr_sector = 0;
|
||||
emulator->ntag_i2c_plus_sector3_lockout = false;
|
||||
emulator->auth_success = false;
|
||||
@@ -1244,8 +1274,7 @@ void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* d
|
||||
emulator->config = mf_ultralight_get_config_pages(&emulator->data);
|
||||
emulator->page_num = emulator->data.data_size / 4;
|
||||
emulator->data_changed = false;
|
||||
emulator->comp_write_cmd_started = false;
|
||||
emulator->sector_select_cmd_started = false;
|
||||
memset(&emulator->auth_attempt, 0, sizeof(MfUltralightAuth));
|
||||
mf_ul_reset_emulation(emulator, true);
|
||||
}
|
||||
|
||||
@@ -1706,6 +1735,17 @@ bool mf_ul_prepare_emulation_response(
|
||||
} else if(cmd == MF_UL_AUTH) {
|
||||
if(emulator->supported_features & MfUltralightSupportAuth) {
|
||||
if(buff_rx_len == (1 + 4) * 8) {
|
||||
// Record password sent by PCD
|
||||
memcpy(
|
||||
emulator->auth_attempt.pwd.raw,
|
||||
&buff_rx[1],
|
||||
sizeof(emulator->auth_attempt.pwd.raw));
|
||||
emulator->auth_attempted = true;
|
||||
if(emulator->auth_received_callback) {
|
||||
emulator->auth_received_callback(
|
||||
emulator->auth_attempt, emulator->context);
|
||||
}
|
||||
|
||||
uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data);
|
||||
if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) {
|
||||
if(emulator->data.curr_authlim != UINT16_MAX) {
|
||||
@@ -1863,3 +1903,14 @@ bool mf_ul_prepare_emulation_response(
|
||||
|
||||
return tx_bits > 0;
|
||||
}
|
||||
|
||||
bool mf_ul_is_full_capture(MfUltralightData* data) {
|
||||
if(data->data_read != data->data_size) return false;
|
||||
|
||||
// Having read all the pages doesn't mean that we've got everything.
|
||||
// By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000,
|
||||
// so a default read on an auth-supported NTAG is never complete.
|
||||
if(!(mf_ul_get_features(data->type) & MfUltralightSupportAuth)) return true;
|
||||
MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data);
|
||||
return config->auth_data.pwd.value != 0 || config->auth_data.pack.value != 0;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@
|
||||
|
||||
#define MF_UL_NTAG203_COUNTER_PAGE (41)
|
||||
|
||||
#define MF_UL_DEFAULT_PWD (0xFFFFFFFF)
|
||||
|
||||
typedef enum {
|
||||
MfUltralightAuthMethodManual,
|
||||
MfUltralightAuthMethodAmeebo,
|
||||
MfUltralightAuthMethodXiaomi,
|
||||
MfUltralightAuthMethodAuto,
|
||||
} MfUltralightAuthMethod;
|
||||
|
||||
// Important: order matters; some features are based on positioning in this enum
|
||||
@@ -110,7 +113,6 @@ typedef struct {
|
||||
uint8_t signature[32];
|
||||
uint32_t counter[3];
|
||||
uint8_t tearing[3];
|
||||
bool has_auth;
|
||||
MfUltralightAuthMethod auth_method;
|
||||
uint8_t auth_key[4];
|
||||
bool auth_success;
|
||||
@@ -169,6 +171,9 @@ typedef struct {
|
||||
MfUltralightFeatures supported_features;
|
||||
} MfUltralightReader;
|
||||
|
||||
// TODO rework with reader analyzer
|
||||
typedef void (*MfUltralightAuthReceivedCallback)(MfUltralightAuth auth, void* context);
|
||||
|
||||
typedef struct {
|
||||
MfUltralightData data;
|
||||
MfUltralightConfigPages* config;
|
||||
@@ -185,6 +190,12 @@ typedef struct {
|
||||
bool sector_select_cmd_started;
|
||||
bool ntag_i2c_plus_sector3_lockout;
|
||||
bool read_counter_incremented;
|
||||
bool auth_attempted;
|
||||
MfUltralightAuth auth_attempt;
|
||||
|
||||
// TODO rework with reader analyzer
|
||||
MfUltralightAuthReceivedCallback auth_received_callback;
|
||||
void* context;
|
||||
} MfUltralightEmulator;
|
||||
|
||||
void mf_ul_reset(MfUltralightData* data);
|
||||
@@ -241,3 +252,5 @@ bool mf_ul_prepare_emulation_response(
|
||||
uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data);
|
||||
|
||||
uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data);
|
||||
|
||||
bool mf_ul_is_full_capture(MfUltralightData* data);
|
||||
|
||||
@@ -37,11 +37,7 @@ iButtonWorker* ibutton_worker_alloc() {
|
||||
worker->emulate_cb = NULL;
|
||||
worker->cb_ctx = NULL;
|
||||
|
||||
worker->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(worker->thread, "ibutton_worker");
|
||||
furi_thread_set_callback(worker->thread, ibutton_worker_thread);
|
||||
furi_thread_set_context(worker->thread, worker);
|
||||
furi_thread_set_stack_size(worker->thread, 2048);
|
||||
worker->thread = furi_thread_alloc_ex("iButtonWorker", 2048, ibutton_worker_thread, worker);
|
||||
|
||||
worker->protocols = protocol_dict_alloc(ibutton_protocols, iButtonProtocolMax);
|
||||
|
||||
@@ -135,7 +131,9 @@ void ibutton_worker_switch_mode(iButtonWorker* worker, iButtonWorkerMode mode) {
|
||||
|
||||
void ibutton_worker_notify_emulate(iButtonWorker* worker) {
|
||||
iButtonMessage message = {.type = iButtonMessageNotifyEmulate};
|
||||
furi_check(furi_message_queue_put(worker->messages, &message, 0) == FuriStatusOk);
|
||||
// we're running in an interrupt context, so we can't wait
|
||||
// and we can drop message if queue is full, that's ok for that message
|
||||
furi_message_queue_put(worker->messages, &message, 0);
|
||||
}
|
||||
|
||||
void ibutton_worker_set_key_p(iButtonWorker* worker, iButtonKey* key) {
|
||||
|
||||
@@ -222,8 +222,8 @@ void ibutton_worker_emulate_dallas_start(iButtonWorker* worker) {
|
||||
memcpy(device_id, key_id, key_size);
|
||||
|
||||
onewire_slave_attach(worker->slave, worker->device);
|
||||
onewire_slave_start(worker->slave);
|
||||
onewire_slave_set_result_callback(worker->slave, onewire_slave_callback, worker);
|
||||
onewire_slave_start(worker->slave);
|
||||
}
|
||||
|
||||
void ibutton_worker_emulate_dallas_stop(iButtonWorker* worker) {
|
||||
|
||||
@@ -541,7 +541,7 @@ static size_t _etoa(
|
||||
exp2 = (int)(expval * 3.321928094887362 + 0.5);
|
||||
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
|
||||
const double z2 = z * z;
|
||||
conv.U = (uint64_t)(exp2 + 1023) << 52U;
|
||||
conv.U = ((uint64_t)exp2 + 1023) << 52U;
|
||||
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
|
||||
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
|
||||
// correct for rounding errors
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
#include "ansonic.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolAnsonic"
|
||||
|
||||
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c"
|
||||
#define CNT_TO_DIP(dip) \
|
||||
(dip & 0x0800 ? '1' : '0'), (dip & 0x0400 ? '1' : '0'), (dip & 0x0200 ? '1' : '0'), \
|
||||
(dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \
|
||||
(dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \
|
||||
(dip & 0x0008 ? '1' : '0')
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ansonic_const = {
|
||||
.te_short = 555,
|
||||
.te_long = 1111,
|
||||
.te_delta = 120,
|
||||
.min_count_bit_for_found = 12,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderAnsonic {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderAnsonic {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
AnsonicDecoderStepReset = 0,
|
||||
AnsonicDecoderStepFoundStartBit,
|
||||
AnsonicDecoderStepSaveDuration,
|
||||
AnsonicDecoderStepCheckDuration,
|
||||
} AnsonicDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ansonic_alloc,
|
||||
.free = subghz_protocol_decoder_ansonic_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_ansonic_feed,
|
||||
.reset = subghz_protocol_decoder_ansonic_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_ansonic_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ansonic_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ansonic_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ansonic_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ansonic_alloc,
|
||||
.free = subghz_protocol_encoder_ansonic_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_ansonic_deserialize,
|
||||
.stop = subghz_protocol_encoder_ansonic_stop,
|
||||
.yield = subghz_protocol_encoder_ansonic_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_ansonic = {
|
||||
.name = SUBGHZ_PROTOCOL_ANSONIC_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_ansonic_decoder,
|
||||
.encoder = &subghz_protocol_ansonic_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderAnsonic* instance = malloc(sizeof(SubGhzProtocolEncoderAnsonic));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_ansonic;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 52;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ansonic_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderAnsonic* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderAnsonic instance
|
||||
* @return true On success
|
||||
*/
|
||||
static bool subghz_protocol_encoder_ansonic_get_upload(SubGhzProtocolEncoderAnsonic* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
|
||||
if(size_upload > instance->encoder.size_upload) {
|
||||
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
|
||||
return false;
|
||||
} else {
|
||||
instance->encoder.size_upload = size_upload;
|
||||
}
|
||||
//Send header
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short * 35);
|
||||
//Send start bit
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short);
|
||||
//Send key data
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
//send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_long);
|
||||
} else {
|
||||
//send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderAnsonic* instance = context;
|
||||
bool res = false;
|
||||
do {
|
||||
if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Deserialize error");
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
subghz_protocol_ansonic_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_ansonic_get_upload(instance)) break;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = true;
|
||||
} while(false);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ansonic_stop(void* context) {
|
||||
SubGhzProtocolEncoderAnsonic* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ansonic_yield(void* context) {
|
||||
SubGhzProtocolEncoderAnsonic* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ansonic_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderAnsonic* instance = malloc(sizeof(SubGhzProtocolDecoderAnsonic));
|
||||
instance->base.protocol = &subghz_protocol_ansonic;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ansonic_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ansonic_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
instance->decoder.parser_step = AnsonicDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case AnsonicDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short * 35) <
|
||||
subghz_protocol_ansonic_const.te_delta * 35)) {
|
||||
//Found header Ansonic
|
||||
instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit;
|
||||
}
|
||||
break;
|
||||
case AnsonicDecoderStepFoundStartBit:
|
||||
if(!level) {
|
||||
break;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) <
|
||||
subghz_protocol_ansonic_const.te_delta) {
|
||||
//Found start bit Ansonic
|
||||
instance->decoder.parser_step = AnsonicDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = AnsonicDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case AnsonicDecoderStepSaveDuration:
|
||||
if(!level) { //save interval
|
||||
if(duration >= (subghz_protocol_ansonic_const.te_short * 4)) {
|
||||
instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit;
|
||||
if(instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_ansonic_const.min_count_bit_for_found) {
|
||||
instance->generic.serial = 0x0;
|
||||
instance->generic.btn = 0x0;
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = AnsonicDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = AnsonicDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case AnsonicDecoderStepCheckDuration:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_short) <
|
||||
subghz_protocol_ansonic_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_long) <
|
||||
subghz_protocol_ansonic_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = AnsonicDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_long) <
|
||||
subghz_protocol_ansonic_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) <
|
||||
subghz_protocol_ansonic_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = AnsonicDecoderStepSaveDuration;
|
||||
} else
|
||||
instance->decoder.parser_step = AnsonicDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = AnsonicDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_ansonic_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
/*
|
||||
* 12345678(10) k 9
|
||||
* AAA => 10101010 1 01 0
|
||||
*
|
||||
* 1...10 - DIP
|
||||
* k- KEY
|
||||
*/
|
||||
instance->cnt = instance->data & 0xFFF;
|
||||
instance->btn = ((instance->data >> 1) & 0x3);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool subghz_protocol_decoder_ansonic_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
subghz_protocol_ansonic_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAnsonic* instance = context;
|
||||
subghz_protocol_ansonic_check_remote_controller(&instance->generic);
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%03lX\r\n"
|
||||
"Btn:%X\r\n"
|
||||
"DIP:" DIP_PATTERN "\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.btn,
|
||||
CNT_TO_DIP(instance->generic.cnt));
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_ANSONIC_NAME "Ansonic"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderAnsonic SubGhzProtocolDecoderAnsonic;
|
||||
typedef struct SubGhzProtocolEncoderAnsonic SubGhzProtocolEncoderAnsonic;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_ansonic;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderAnsonic.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderAnsonic* pointer to a SubGhzProtocolEncoderAnsonic instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderAnsonic.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
|
||||
*/
|
||||
void subghz_protocol_encoder_ansonic_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
|
||||
*/
|
||||
void subghz_protocol_encoder_ansonic_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_ansonic_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderAnsonic.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderAnsonic* pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_ansonic_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderAnsonic.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
*/
|
||||
void subghz_protocol_decoder_ansonic_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderAnsonic.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
*/
|
||||
void subghz_protocol_decoder_ansonic_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderAnsonic.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return true On success
|
||||
*/
|
||||
bool subghz_protocol_decoder_ansonic_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderAnsonic.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output);
|
||||
@@ -138,9 +138,9 @@ bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat*
|
||||
FURI_LOG_E(TAG, "Deserialize error");
|
||||
break;
|
||||
}
|
||||
if((instance->generic.data_count_bit !=
|
||||
subghz_protocol_nice_flo_const.min_count_bit_for_found) &&
|
||||
(instance->generic.data_count_bit !=
|
||||
if((instance->generic.data_count_bit <
|
||||
subghz_protocol_nice_flo_const.min_count_bit_for_found) ||
|
||||
(instance->generic.data_count_bit >
|
||||
2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
@@ -297,9 +297,9 @@ bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat*
|
||||
if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if((instance->generic.data_count_bit !=
|
||||
subghz_protocol_nice_flo_const.min_count_bit_for_found) &&
|
||||
(instance->generic.data_count_bit !=
|
||||
if((instance->generic.data_count_bit <
|
||||
subghz_protocol_nice_flo_const.min_count_bit_for_found) ||
|
||||
(instance->generic.data_count_bit >
|
||||
2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
|
||||
@@ -312,7 +312,6 @@ void subghz_protocol_decoder_power_smart_feed(
|
||||
if((instance->decoder.decode_data & POWER_SMART_PACKET_HEADER_MASK) ==
|
||||
POWER_SMART_PACKET_HEADER) {
|
||||
if(subghz_protocol_power_smart_chek_valid(instance->decoder.decode_data)) {
|
||||
instance->decoder.decode_data = instance->decoder.decode_data;
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit =
|
||||
subghz_protocol_power_smart_const.min_count_bit_for_found;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#define TAG "SubGhzProtocolPrinceton"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_princeton_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 1200,
|
||||
.te_short = 390,
|
||||
.te_long = 1170,
|
||||
.te_delta = 300,
|
||||
.min_count_bit_for_found = 24,
|
||||
};
|
||||
@@ -245,8 +245,7 @@ void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t
|
||||
break;
|
||||
case PrincetonDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(duration >= ((uint32_t)subghz_protocol_princeton_const.te_short * 10 +
|
||||
subghz_protocol_princeton_const.te_delta)) {
|
||||
if(duration >= ((uint32_t)subghz_protocol_princeton_const.te_long * 2)) {
|
||||
instance->decoder.parser_step = PrincetonDecoderStepSaveDuration;
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_princeton_const.min_count_bit_for_found) {
|
||||
|
||||
@@ -12,7 +12,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec,
|
||||
&subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2,
|
||||
&subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3,
|
||||
&subghz_protocol_clemsa,
|
||||
&subghz_protocol_clemsa, &subghz_protocol_ansonic,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -35,5 +35,6 @@
|
||||
#include "magellan.h"
|
||||
#include "intertechno_v3.h"
|
||||
#include "clemsa.h"
|
||||
#include "ansonic.h"
|
||||
|
||||
extern const SubGhzProtocolRegistry subghz_protocol_registry;
|
||||
|
||||
@@ -18,6 +18,7 @@ struct SubGhzFileEncoderWorker {
|
||||
volatile bool worker_running;
|
||||
volatile bool worker_stoping;
|
||||
bool level;
|
||||
bool is_storage_slow;
|
||||
FuriString* str_data;
|
||||
FuriString* file_path;
|
||||
|
||||
@@ -86,7 +87,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) {
|
||||
if(ret == sizeof(int32_t)) {
|
||||
LevelDuration level_duration = {.level = LEVEL_DURATION_RESET};
|
||||
if(duration < 0) {
|
||||
level_duration = level_duration_make(false, duration * -1);
|
||||
level_duration = level_duration_make(false, -duration);
|
||||
} else if(duration > 0) {
|
||||
level_duration = level_duration_make(true, duration);
|
||||
} else if(duration == 0) {
|
||||
@@ -96,7 +97,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) {
|
||||
}
|
||||
return level_duration;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Slow flash read");
|
||||
instance->is_storage_slow = true;
|
||||
return level_duration_wait();
|
||||
}
|
||||
}
|
||||
@@ -110,6 +111,7 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
|
||||
SubGhzFileEncoderWorker* instance = context;
|
||||
FURI_LOG_I(TAG, "Worker start");
|
||||
bool res = false;
|
||||
instance->is_storage_slow = false;
|
||||
Stream* stream = flipper_format_get_raw_stream(instance->flipper_format);
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(
|
||||
@@ -139,21 +141,21 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
|
||||
furi_string_trim(instance->str_data);
|
||||
if(!subghz_file_encoder_worker_data_parse(
|
||||
instance, furi_string_get_cstr(instance->str_data))) {
|
||||
//to stop DMA correctly
|
||||
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
|
||||
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
|
||||
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
furi_delay_ms(1);
|
||||
}
|
||||
furi_delay_ms(5);
|
||||
}
|
||||
//waiting for the end of the transfer
|
||||
if(instance->is_storage_slow) {
|
||||
FURI_LOG_E(TAG, "Storage is slow");
|
||||
}
|
||||
FURI_LOG_I(TAG, "End read file");
|
||||
while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) {
|
||||
furi_delay_ms(5);
|
||||
@@ -174,11 +176,8 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
|
||||
SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() {
|
||||
SubGhzFileEncoderWorker* instance = malloc(sizeof(SubGhzFileEncoderWorker));
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "SubGhzFEWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 2048);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, subghz_file_encoder_worker_thread);
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("SubGhzFEWorker", 2048, subghz_file_encoder_worker_thread, instance);
|
||||
instance->stream = furi_stream_buffer_alloc(sizeof(int32_t) * 2048, sizeof(int32_t));
|
||||
|
||||
instance->storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
@@ -464,7 +464,7 @@ bool subghz_keystore_raw_encrypted_save(
|
||||
}
|
||||
stream_write_cstring(output_stream, encrypted_line);
|
||||
|
||||
} while(ret > 0 && result);
|
||||
} while(result);
|
||||
|
||||
flipper_format_free(output_flipper_format);
|
||||
|
||||
|
||||
@@ -201,11 +201,8 @@ static int32_t subghz_tx_rx_worker_thread(void* context) {
|
||||
SubGhzTxRxWorker* subghz_tx_rx_worker_alloc() {
|
||||
SubGhzTxRxWorker* instance = malloc(sizeof(SubGhzTxRxWorker));
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "SubGhzTxRxWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 2048);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, subghz_tx_rx_worker_thread);
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("SubGhzTxRxWorker", 2048, subghz_tx_rx_worker_thread, instance);
|
||||
instance->stream_tx =
|
||||
furi_stream_buffer_alloc(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t));
|
||||
instance->stream_rx =
|
||||
|
||||
@@ -88,11 +88,8 @@ static int32_t subghz_worker_thread_callback(void* context) {
|
||||
SubGhzWorker* subghz_worker_alloc() {
|
||||
SubGhzWorker* instance = malloc(sizeof(SubGhzWorker));
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "SubGhzWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 2048);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, subghz_worker_thread_callback);
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("SubGhzWorker", 2048, subghz_worker_thread_callback, instance);
|
||||
|
||||
instance->stream =
|
||||
furi_stream_buffer_alloc(sizeof(LevelDuration) * 4096, sizeof(LevelDuration));
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <furi/furi.h>
|
||||
|
||||
/*
|
||||
Testing 10000 api calls
|
||||
|
||||
No lock
|
||||
Time diff: 445269.218750 us
|
||||
Time per call: 44.526920 us
|
||||
|
||||
furi_thread_flags
|
||||
Time diff: 430279.875000 us // lol wtf? smaller than no lock?
|
||||
Time per call: 43.027988 us // I tested it many times, it's always smaller
|
||||
|
||||
FuriEventFlag
|
||||
Time diff: 831523.625000 us
|
||||
Time per call: 83.152359 us
|
||||
|
||||
FuriSemaphore
|
||||
Time diff: 999807.125000 us
|
||||
Time per call: 99.980713 us
|
||||
|
||||
FuriMutex
|
||||
Time diff: 1071417.500000 us
|
||||
Time per call: 107.141747 us
|
||||
*/
|
||||
|
||||
typedef FuriEventFlag* FuriApiLock;
|
||||
|
||||
#define API_LOCK_EVENT (1U << 0)
|
||||
|
||||
#define api_lock_alloc_locked() furi_event_flag_alloc()
|
||||
|
||||
#define api_lock_wait_unlock(_lock) \
|
||||
furi_event_flag_wait(_lock, API_LOCK_EVENT, FuriFlagWaitAny, FuriWaitForever)
|
||||
|
||||
#define api_lock_free(_lock) furi_event_flag_free(_lock)
|
||||
|
||||
#define api_lock_unlock(_lock) furi_event_flag_set(_lock, API_LOCK_EVENT)
|
||||
|
||||
#define api_lock_wait_unlock_and_free(_lock) \
|
||||
api_lock_wait_unlock(_lock); \
|
||||
api_lock_free(_lock);
|
||||
+11
-16
@@ -38,7 +38,7 @@ void path_extract_extension(FuriString* path, char* ext, size_t ext_len_max) {
|
||||
size_t dot = furi_string_search_rchar(path, '.');
|
||||
size_t filename_start = furi_string_search_rchar(path, '/');
|
||||
|
||||
if((dot > 0) && (filename_start < dot)) {
|
||||
if((dot != FURI_STRING_FAILURE) && (filename_start < dot)) {
|
||||
strlcpy(ext, &(furi_string_get_cstr(path))[dot], ext_len_max);
|
||||
}
|
||||
}
|
||||
@@ -95,22 +95,17 @@ bool path_contains_only_ascii(const char* path) {
|
||||
name_pos++;
|
||||
}
|
||||
|
||||
while(*name_pos != '\0') {
|
||||
if((*name_pos >= '0') && (*name_pos <= '9')) {
|
||||
name_pos++;
|
||||
continue;
|
||||
} else if((*name_pos >= 'A') && (*name_pos <= 'Z')) {
|
||||
name_pos++;
|
||||
continue;
|
||||
} else if((*name_pos >= 'a') && (*name_pos <= 'z')) {
|
||||
name_pos++;
|
||||
continue;
|
||||
} else if(strchr(" .!#\\$%&'()-@^_`{}~", *name_pos) != NULL) {
|
||||
name_pos++;
|
||||
continue;
|
||||
}
|
||||
for(; *name_pos; ++name_pos) {
|
||||
const char c = *name_pos;
|
||||
|
||||
return false;
|
||||
// Regular ASCII characters from 0x20 to 0x7e
|
||||
const bool is_out_of_range = (c < ' ') || (c > '~');
|
||||
// Cross-platform forbidden character set
|
||||
const bool is_forbidden = strchr("\\<>*|\":?", c);
|
||||
|
||||
if(is_out_of_range || is_forbidden) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "property.h"
|
||||
|
||||
#include <core/check.h>
|
||||
|
||||
void property_value_out(PropertyValueContext* ctx, const char* fmt, unsigned int nparts, ...) {
|
||||
furi_assert(ctx);
|
||||
furi_string_reset(ctx->key);
|
||||
|
||||
va_list args;
|
||||
va_start(args, nparts);
|
||||
|
||||
for(size_t i = 0; i < nparts; ++i) {
|
||||
const char* keypart = va_arg(args, const char*);
|
||||
furi_string_cat(ctx->key, keypart);
|
||||
if(i < nparts - 1) {
|
||||
furi_string_push_back(ctx->key, ctx->sep);
|
||||
}
|
||||
}
|
||||
|
||||
const char* value_str;
|
||||
|
||||
if(fmt) {
|
||||
furi_string_vprintf(ctx->value, fmt, args);
|
||||
value_str = furi_string_get_cstr(ctx->value);
|
||||
} else {
|
||||
// C string passthrough (no formatting)
|
||||
value_str = va_arg(args, const char*);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
ctx->out(furi_string_get_cstr(ctx->key), value_str, ctx->last, ctx->context);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <core/string.h>
|
||||
|
||||
/** Callback type called every time another key-value pair of device information is ready
|
||||
*
|
||||
* @param key[in] device information type identifier
|
||||
* @param value[in] device information value
|
||||
* @param last[in] whether the passed key-value pair is the last one
|
||||
* @param context[in] to pass to callback
|
||||
*/
|
||||
typedef void (*PropertyValueCallback)(const char* key, const char* value, bool last, void* context);
|
||||
|
||||
typedef struct {
|
||||
FuriString* key; /**< key string buffer, must be initialised before use */
|
||||
FuriString* value; /**< value string buffer, must be initialised before use */
|
||||
PropertyValueCallback out; /**< output callback function */
|
||||
char sep; /**< separator character between key parts */
|
||||
bool last; /**< flag to indicate last element */
|
||||
void* context; /**< user-defined context, passed through to out callback */
|
||||
} PropertyValueContext;
|
||||
|
||||
/** Builds key and value strings and outputs them via a callback function
|
||||
*
|
||||
* @param ctx[in] local property context
|
||||
* @param fmt[in] value format, set to NULL to bypass formatting
|
||||
* @param nparts[in] number of key parts (separated by character)
|
||||
* @param ...[in] list of key parts followed by value
|
||||
*/
|
||||
void property_value_out(PropertyValueContext* ctx, const char* fmt, unsigned int nparts, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -5,12 +5,6 @@
|
||||
#include <furi.h>
|
||||
|
||||
void set_random_name(char* name, uint8_t max_name_size) {
|
||||
static bool rand_generator_inited = false;
|
||||
|
||||
if(!rand_generator_inited) {
|
||||
srand(DWT->CYCCNT);
|
||||
rand_generator_inited = true;
|
||||
}
|
||||
const char* prefix[] = {
|
||||
"ancient", "hollow", "strange", "disappeared", "unknown",
|
||||
"unthinkable", "unnamable", "nameless", "my", "concealed",
|
||||
|
||||
Reference in New Issue
Block a user