1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX
2025-11-06 20:29:16 +03:00
18 changed files with 183 additions and 36 deletions

View File

@@ -0,0 +1,218 @@
#include "infrared_brute_force.h"
#include <stdlib.h>
#include <m-dict.h>
#include <m-array.h>
#include <flipper_format/flipper_format.h>
#include "infrared_signal.h"
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST); //-V658
typedef struct {
size_t index;
SignalPositionArray_t signals;
} InfraredBruteForceRecord;
static inline void ir_bf_record_init(InfraredBruteForceRecord* record) {
record->index = 0;
SignalPositionArray_init(record->signals);
}
#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r)))
static inline void
ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
dest->index = src->index;
SignalPositionArray_init_set(dest->signals, src->signals);
}
#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s)))
static inline void
ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
dest->index = src->index;
SignalPositionArray_set(dest->signals, src->signals);
}
#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s)))
static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) {
SignalPositionArray_clear(record->signals);
}
#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r)))
#define IR_BF_RECORD_OPLIST \
(INIT(IR_BF_RECORD_INIT), \
INIT_SET(IR_BF_RECORD_INIT_SET), \
SET(IR_BF_RECORD_SET), \
CLEAR(IR_BF_RECORD_CLEAR))
DICT_DEF2(
InfraredBruteForceRecordDict,
FuriString*,
FURI_STRING_OPLIST,
InfraredBruteForceRecord,
IR_BF_RECORD_OPLIST);
struct InfraredBruteForce {
FlipperFormat* ff;
const char* db_filename;
FuriString* current_record_name;
InfraredBruteForceRecord current_record;
InfraredSignal* current_signal;
InfraredBruteForceRecordDict_t records;
bool is_started;
};
InfraredBruteForce* infrared_brute_force_alloc(void) {
InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce));
brute_force->ff = NULL;
brute_force->db_filename = NULL;
brute_force->current_signal = NULL;
brute_force->is_started = false;
brute_force->current_record_name = furi_string_alloc();
InfraredBruteForceRecordDict_init(brute_force->records);
return brute_force;
}
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_clear(brute_force->records);
furi_string_free(brute_force->current_record_name);
free(brute_force);
}
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
brute_force->db_filename = db_filename;
}
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
furi_assert(brute_force->db_filename);
InfraredErrorCode error = InfraredErrorCodeNone;
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
FuriString* signal_name = furi_string_alloc();
InfraredSignal* signal = infrared_signal_alloc();
do {
if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) {
error = InfraredErrorCodeFileOperationFailed;
break;
}
bool signal_valid = false;
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
size_t signal_start = flipper_format_tell(ff);
error = infrared_signal_read_body(signal, ff);
signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
if(!signal_valid) break;
InfraredBruteForceRecord* record =
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
furi_assert(record);
SignalPositionArray_push_back(record->signals, signal_start);
}
if(!signal_valid) break;
} while(false);
infrared_signal_free(signal);
furi_string_free(signal_name);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
return error;
}
bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
bool success = false;
*record_count = 0;
InfraredBruteForceRecordDict_it_t it;
for(InfraredBruteForceRecordDict_it(it, brute_force->records);
!InfraredBruteForceRecordDict_end_p(it);
InfraredBruteForceRecordDict_next(it)) {
const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
if(record->value.index == index) {
*record_count = SignalPositionArray_size(record->value.signals);
if(*record_count) {
furi_string_set(brute_force->current_record_name, record->key);
brute_force->current_record = record->value;
}
break;
}
}
if(*record_count) {
Storage* storage = furi_record_open(RECORD_STORAGE);
brute_force->ff = flipper_format_buffered_file_alloc(storage);
brute_force->current_signal = infrared_signal_alloc();
brute_force->is_started = true;
success =
flipper_format_buffered_file_open_existing(brute_force->ff, brute_force->db_filename);
if(!success) infrared_brute_force_stop(brute_force);
}
return success;
}
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
furi_check(brute_force);
return brute_force->is_started;
}
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(brute_force->is_started);
furi_string_reset(brute_force->current_record_name);
infrared_signal_free(brute_force->current_signal);
flipper_format_free(brute_force->ff);
brute_force->current_signal = NULL;
brute_force->ff = NULL;
brute_force->is_started = false;
furi_record_close(RECORD_STORAGE);
}
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) {
furi_check(brute_force);
furi_assert(brute_force->is_started);
if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false;
size_t signal_start =
*SignalPositionArray_cget(brute_force->current_record.signals, signal_index);
if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart))
return false;
if(INFRARED_ERROR_PRESENT(
infrared_signal_read_body(brute_force->current_signal, brute_force->ff)))
return false;
infrared_signal_transmit(brute_force->current_signal);
return true;
}
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name) {
InfraredBruteForceRecord value;
ir_bf_record_init(&value);
value.index = index;
FuriString* key;
key = furi_string_alloc_set(name);
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
furi_string_free(key);
}
void infrared_brute_force_reset(InfraredBruteForce* brute_force) {
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_reset(brute_force->records);
}

View File

@@ -0,0 +1,122 @@
/**
* @file infrared_brute_force.h
* @brief Infrared signal brute-forcing library.
*
* The BruteForce library is used to send large quantities of signals,
* sorted by a category. It is used to implement the Universal Remote
* feature.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "infrared_error_code.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief InfraredBruteForce opaque type declaration.
*/
typedef struct InfraredBruteForce InfraredBruteForce;
/**
* @brief Create a new InfraredBruteForce instance.
*
* @returns pointer to the created instance.
*/
InfraredBruteForce* infrared_brute_force_alloc(void);
/**
* @brief Delete an InfraredBruteForce instance.
*
* @param[in,out] brute_force pointer to the instance to be deleted.
*/
void infrared_brute_force_free(InfraredBruteForce* brute_force);
/**
* @brief Set an InfraredBruteForce instance to use a signal database contained in a file.
*
* @param[in,out] brute_force pointer to the instance to be configured.
* @param[in] db_filename pointer to a zero-terminated string containing a full path to the database file.
*/
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename);
/**
* @brief Build a signal dictionary from a previously set database file.
*
* This function must be called each time after setting the database via
* a infrared_brute_force_set_db_filename() call.
*
* @param[in,out] brute_force pointer to the instance to be updated.
* @returns InfraredErrorCodeNone on success, otherwise error code.
*/
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force);
/**
* @brief Start transmitting signals from a category stored in the dictionary.
*
* The function locates the category identified by @p index, reports the number of
* records it contains via @p record_count, and prepares the brute-force instance
* to transmit those signals. On failure @p record_count is set to zero.
*
* @param[in,out] brute_force pointer to the instance to be started.
* @param[in] index index of the signal category in the dictionary.
* @param[out] record_count pointer that receives the number of records in the category.
* @returns true if the category is found and the backing database file is opened, false otherwise.
*/
bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count);
/**
* @brief Determine whether the transmission was started.
*
* @param[in] brute_force pointer to the instance to be tested.
* @returns true if transmission was started, false otherwise.
*/
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
/**
* @brief Stop transmitting the signals.
*
* @param[in] brute_force pointer to the instance to be stopped.
*/
void infrared_brute_force_stop(InfraredBruteForce* brute_force);
/**
* @brief Send an arbitrary signal from the chosen category.
*
* @param[in] brute_force pointer to the instance
* @param signal_index the index of the signal within the category, must be
* between 0 and `record_count` as told by
* `infrared_brute_force_start`
*
* @returns true on success, false otherwise
*/
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index);
/**
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.
*
* @param[in,out] brute_force pointer to the instance to be updated.
* @param[in] index index of the category to be added.
* @param[in] name name of the category to be added.
*/
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name);
/**
* @brief Reset an InfraredBruteForce instance.
*
* @param[in,out] brute_force pointer to the instance to be reset.
*/
void infrared_brute_force_reset(InfraredBruteForce* brute_force);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,53 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
InfraredErrorCodeNone = 0,
InfraredErrorCodeFileOperationFailed = 0x800000,
InfraredErrorCodeWrongFileType = 0x80000100,
InfraredErrorCodeWrongFileVersion = 0x80000200,
//Common signal errors
InfraredErrorCodeSignalTypeUnknown = 0x80000300,
InfraredErrorCodeSignalNameNotFound = 0x80000400,
InfraredErrorCodeSignalUnableToReadType = 0x80000500,
InfraredErrorCodeSignalUnableToWriteType = 0x80000600,
//Raw signal errors
InfraredErrorCodeSignalRawUnableToReadFrequency = 0x80000700,
InfraredErrorCodeSignalRawUnableToReadDutyCycle = 0x80000800,
InfraredErrorCodeSignalRawUnableToReadTimingsSize = 0x80000900,
InfraredErrorCodeSignalRawUnableToReadTooLongData = 0x80000A00,
InfraredErrorCodeSignalRawUnableToReadData = 0x80000B00,
InfraredErrorCodeSignalRawUnableToWriteFrequency = 0x80000C00,
InfraredErrorCodeSignalRawUnableToWriteDutyCycle = 0x80000D00,
InfraredErrorCodeSignalRawUnableToWriteData = 0x80000E00,
//Message signal errors
InfraredErrorCodeSignalMessageUnableToReadProtocol = 0x80000F00,
InfraredErrorCodeSignalMessageUnableToReadAddress = 0x80001000,
InfraredErrorCodeSignalMessageUnableToReadCommand = 0x80001100,
InfraredErrorCodeSignalMessageIsInvalid = 0x80001200,
InfraredErrorCodeSignalMessageUnableToWriteProtocol = 0x80001300,
InfraredErrorCodeSignalMessageUnableToWriteAddress = 0x80001400,
InfraredErrorCodeSignalMessageUnableToWriteCommand = 0x80001500,
} InfraredErrorCode;
#define INFRARED_ERROR_CODE_MASK (0xFFFFFF00)
#define INFRARED_ERROR_INDEX_MASK (0x000000FF)
#define INFRARED_ERROR_GET_CODE(error) ((error) & INFRARED_ERROR_CODE_MASK)
#define INFRARED_ERROR_GET_INDEX(error) ((error) & INFRARED_ERROR_INDEX_MASK)
#define INFRARED_ERROR_SET_INDEX(code, index) ((code) |= ((index) & INFRARED_ERROR_INDEX_MASK))
#define INFRARED_ERROR_PRESENT(error) (INFRARED_ERROR_GET_CODE(error) != InfraredErrorCodeNone)
#define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == (test_code))
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,458 @@
#include "infrared_signal.h"
#include <stdlib.h>
#include <string.h>
#include <core/check.h>
#include <infrared_worker.h>
#include <infrared_transmit.h>
#define TAG "InfraredSignal"
// Common keys
#define INFRARED_SIGNAL_NAME_KEY "name"
#define INFRARED_SIGNAL_TYPE_KEY "type"
// Type key values
#define INFRARED_SIGNAL_TYPE_RAW "raw"
#define INFRARED_SIGNAL_TYPE_PARSED "parsed"
// Raw signal keys
#define INFRARED_SIGNAL_DATA_KEY "data"
#define INFRARED_SIGNAL_FREQUENCY_KEY "frequency"
#define INFRARED_SIGNAL_DUTY_CYCLE_KEY "duty_cycle"
// Parsed signal keys
#define INFRARED_SIGNAL_PROTOCOL_KEY "protocol"
#define INFRARED_SIGNAL_ADDRESS_KEY "address"
#define INFRARED_SIGNAL_COMMAND_KEY "command"
struct InfraredSignal {
bool is_raw;
union {
InfraredMessage message;
InfraredRawSignal raw;
} payload;
};
static void infrared_signal_clear_timings(InfraredSignal* signal) {
if(signal->is_raw) {
free(signal->payload.raw.timings);
signal->payload.raw.timings_size = 0;
signal->payload.raw.timings = NULL;
}
}
static bool infrared_signal_is_message_valid(const InfraredMessage* message) {
if(!infrared_is_protocol_valid(message->protocol)) {
FURI_LOG_E(TAG, "Unknown protocol");
return false;
}
uint32_t address_length = infrared_get_protocol_address_length(message->protocol);
uint32_t address_mask = (1UL << address_length) - 1;
if(message->address != (message->address & address_mask)) {
FURI_LOG_E(
TAG,
"Address is out of range (mask 0x%08lX): 0x%lX\r\n",
address_mask,
message->address);
return false;
}
uint32_t command_length = infrared_get_protocol_command_length(message->protocol);
uint32_t command_mask = (1UL << command_length) - 1;
if(message->command != (message->command & command_mask)) {
FURI_LOG_E(
TAG,
"Command is out of range (mask 0x%08lX): 0x%lX\r\n",
command_mask,
message->command);
return false;
}
return true;
}
static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) {
if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) {
FURI_LOG_E(
TAG,
"Frequency is out of range (%X - %X): %lX",
INFRARED_MIN_FREQUENCY,
INFRARED_MAX_FREQUENCY,
raw->frequency);
return false;
} else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1.f)) {
FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle);
return false;
} else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) {
FURI_LOG_E(
TAG,
"Timings amount is out of range (0 - %X): %zX",
MAX_TIMINGS_AMOUNT,
raw->timings_size);
return false;
}
return true;
}
static inline InfraredErrorCode
infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) {
const char* protocol_name = infrared_get_protocol_name(message->protocol);
InfraredErrorCode error = InfraredErrorCodeNone;
do {
if(!flipper_format_write_string_cstr(
ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED)) {
error = InfraredErrorCodeSignalUnableToWriteType;
break;
}
if(!flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name)) {
error = InfraredErrorCodeSignalMessageUnableToWriteProtocol;
break;
}
if(!flipper_format_write_hex(
ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4)) {
error = InfraredErrorCodeSignalMessageUnableToWriteAddress;
break;
}
if(!flipper_format_write_hex(
ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4)) {
error = InfraredErrorCodeSignalMessageUnableToWriteCommand;
break;
}
} while(false);
return error;
}
static inline InfraredErrorCode
infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) {
furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
InfraredErrorCode error = InfraredErrorCodeNone;
do {
if(!flipper_format_write_string_cstr(
ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW)) {
error = InfraredErrorCodeSignalUnableToWriteType;
break;
}
if(!flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1)) {
error = InfraredErrorCodeSignalRawUnableToWriteFrequency;
break;
}
if(!flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1)) {
error = InfraredErrorCodeSignalRawUnableToWriteDutyCycle;
break;
}
if(!flipper_format_write_uint32(
ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size)) {
error = InfraredErrorCodeSignalRawUnableToWriteData;
break;
}
} while(false);
return error;
}
static inline InfraredErrorCode
infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) {
FuriString* buf;
buf = furi_string_alloc();
InfraredErrorCode error = InfraredErrorCodeNone;
do {
if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) {
error = InfraredErrorCodeSignalMessageUnableToReadProtocol;
break;
}
InfraredMessage message;
message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf));
if(!flipper_format_read_hex(
ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) {
error = InfraredErrorCodeSignalMessageUnableToReadAddress;
break;
}
if(!flipper_format_read_hex(
ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) {
error = InfraredErrorCodeSignalMessageUnableToReadCommand;
break;
}
if(!infrared_signal_is_message_valid(&message)) {
error = InfraredErrorCodeSignalMessageIsInvalid;
break;
}
infrared_signal_set_message(signal, &message);
} while(false);
furi_string_free(buf);
return error;
}
static inline InfraredErrorCode
infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) {
InfraredErrorCode error = InfraredErrorCodeNone;
do {
uint32_t frequency;
if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) {
error = InfraredErrorCodeSignalRawUnableToReadFrequency;
break;
}
float duty_cycle;
if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) {
error = InfraredErrorCodeSignalRawUnableToReadDutyCycle;
break;
}
uint32_t timings_size;
if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) {
error = InfraredErrorCodeSignalRawUnableToReadTimingsSize;
break;
}
if(timings_size > MAX_TIMINGS_AMOUNT) {
error = InfraredErrorCodeSignalRawUnableToReadTooLongData;
break;
}
uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_DATA_KEY, timings, timings_size)) {
error = InfraredErrorCodeSignalRawUnableToReadData;
free(timings);
break;
}
infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
free(timings);
error = InfraredErrorCodeNone;
} while(false);
return error;
}
InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) {
FuriString* tmp = furi_string_alloc();
InfraredErrorCode error = InfraredErrorCodeNone;
do {
if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) {
error = InfraredErrorCodeSignalUnableToReadType;
break;
}
if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) {
error = infrared_signal_read_raw(signal, ff);
} else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) {
error = infrared_signal_read_message(signal, ff);
} else {
FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp));
error = InfraredErrorCodeSignalTypeUnknown;
break;
}
} while(false);
furi_string_free(tmp);
return error;
}
InfraredSignal* infrared_signal_alloc(void) {
InfraredSignal* signal = malloc(sizeof(InfraredSignal));
signal->is_raw = false;
signal->payload.message.protocol = InfraredProtocolUnknown;
return signal;
}
void infrared_signal_free(InfraredSignal* signal) {
infrared_signal_clear_timings(signal);
free(signal);
}
bool infrared_signal_is_raw(const InfraredSignal* signal) {
return signal->is_raw;
}
bool infrared_signal_is_valid(const InfraredSignal* signal) {
return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) :
infrared_signal_is_message_valid(&signal->payload.message);
}
void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) {
if(other->is_raw) {
const InfraredRawSignal* raw = &other->payload.raw;
infrared_signal_set_raw_signal(
signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
} else {
const InfraredMessage* message = &other->payload.message;
infrared_signal_set_message(signal, message);
}
}
void infrared_signal_set_raw_signal(
InfraredSignal* signal,
const uint32_t* timings,
size_t timings_size,
uint32_t frequency,
float duty_cycle) {
infrared_signal_clear_timings(signal);
// If the frequency is out of bounds, set it to the closest bound same for duty cycle
// TODO: Should we return error instead? Also infrared_signal_is_valid is used only in CLI for some reason?!
if(frequency > INFRARED_MAX_FREQUENCY) {
frequency = INFRARED_MAX_FREQUENCY;
} else if(frequency < INFRARED_MIN_FREQUENCY) {
frequency = INFRARED_MIN_FREQUENCY;
}
if((duty_cycle <= (float)0) || (duty_cycle > (float)1)) {
duty_cycle = (float)0.33;
}
// In case of timings out of bounds we just call return
if((timings_size <= 0) || (timings_size > MAX_TIMINGS_AMOUNT)) {
return;
}
signal->is_raw = true;
signal->payload.raw.timings_size = timings_size;
signal->payload.raw.frequency = frequency;
signal->payload.raw.duty_cycle = duty_cycle;
signal->payload.raw.timings = malloc(timings_size * sizeof(uint32_t));
memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t));
}
const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal) {
furi_assert(signal->is_raw);
return &signal->payload.raw;
}
void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) {
infrared_signal_clear_timings(signal);
signal->is_raw = false;
signal->payload.message = *message;
}
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) {
furi_assert(!signal->is_raw);
return &signal->payload.message;
}
InfraredErrorCode
infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) {
InfraredErrorCode error = InfraredErrorCodeNone;
if(!flipper_format_write_comment_cstr(ff, "") ||
!flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) {
error = InfraredErrorCodeFileOperationFailed;
} else if(signal->is_raw) {
error = infrared_signal_save_raw(&signal->payload.raw, ff);
} else {
error = infrared_signal_save_message(&signal->payload.message, ff);
}
return error;
}
InfraredErrorCode
infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) {
InfraredErrorCode error = InfraredErrorCodeNone;
do {
error = infrared_signal_read_name(ff, name);
if(INFRARED_ERROR_PRESENT(error)) break;
error = infrared_signal_read_body(signal, ff);
} while(false);
return error;
}
InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name) {
return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name) ?
InfraredErrorCodeNone :
InfraredErrorCodeSignalNameNotFound;
}
InfraredErrorCode infrared_signal_search_by_name_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const char* name) {
InfraredErrorCode error = InfraredErrorCodeNone;
FuriString* tmp = furi_string_alloc();
do {
error = infrared_signal_read_name(ff, tmp);
if(INFRARED_ERROR_PRESENT(error)) break;
if(furi_string_equal(tmp, name)) {
error = infrared_signal_read_body(signal, ff);
break;
}
} while(true);
furi_string_free(tmp);
return error;
}
InfraredErrorCode infrared_signal_search_by_index_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
size_t index) {
InfraredErrorCode error = InfraredErrorCodeNone;
FuriString* tmp = furi_string_alloc();
for(uint32_t i = 0;; ++i) {
error = infrared_signal_read_name(ff, tmp);
if(INFRARED_ERROR_PRESENT(error)) {
INFRARED_ERROR_SET_INDEX(error, i);
break;
}
if(i == index) {
error = infrared_signal_read_body(signal, ff);
if(INFRARED_ERROR_PRESENT(error)) {
INFRARED_ERROR_SET_INDEX(error, i);
}
break;
}
}
furi_string_free(tmp);
return error;
}
void infrared_signal_transmit(const InfraredSignal* signal) {
if(signal->is_raw) {
const InfraredRawSignal* raw_signal = &signal->payload.raw;
infrared_send_raw_ext(
raw_signal->timings,
raw_signal->timings_size,
true,
raw_signal->frequency,
raw_signal->duty_cycle);
} else {
const InfraredMessage* message = &signal->payload.message;
infrared_send(message, 1);
}
}

View File

@@ -0,0 +1,228 @@
/**
* @file infrared_signal.h
* @brief Infrared signal library.
*
* Infrared signals may be of two types:
* - known to the infrared signal decoder, or *parsed* signals
* - the rest, or *raw* signals, which are treated merely as a set of timings.
*/
#pragma once
#include "infrared_error_code.h"
#include <flipper_format/flipper_format.h>
#include <infrared.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief InfraredSignal opaque type declaration.
*/
typedef struct InfraredSignal InfraredSignal;
/**
* @brief Raw signal type definition.
*
* Measurement units used:
* - time: microseconds (uS)
* - frequency: Hertz (Hz)
* - duty_cycle: no units, fraction between 0 and 1.
*/
typedef struct {
size_t timings_size; /**< Number of elements in the timings array. */
uint32_t* timings; /**< Pointer to an array of timings describing the signal. */
uint32_t frequency; /**< Carrier frequency of the signal. */
float duty_cycle; /**< Duty cycle of the signal. */
} InfraredRawSignal;
/**
* @brief Create a new InfraredSignal instance.
*
* @returns pointer to the instance created.
*/
InfraredSignal* infrared_signal_alloc(void);
/**
* @brief Delete an InfraredSignal instance.
*
* @param[in,out] signal pointer to the instance to be deleted.
*/
void infrared_signal_free(InfraredSignal* signal);
/**
* @brief Test whether an InfraredSignal instance holds a raw signal.
*
* @param[in] signal pointer to the instance to be tested.
* @returns true if the instance holds a raw signal, false otherwise.
*/
bool infrared_signal_is_raw(const InfraredSignal* signal);
/**
* @brief Test whether an InfraredSignal instance holds any signal.
*
* @param[in] signal pointer to the instance to be tested.
* @returns true if the instance holds raw signal, false otherwise.
*/
bool infrared_signal_is_valid(const InfraredSignal* signal);
/**
* @brief Set an InfraredInstance to hold the signal from another one.
*
* Any instance's previous contents will be automatically deleted before
* copying the source instance's contents.
*
* @param[in,out] signal pointer to the destination instance.
* @param[in] other pointer to the source instance.
*/
void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other);
/**
* @brief Set an InfraredInstance to hold a raw signal.
*
* Any instance's previous contents will be automatically deleted before
* copying the raw signal.
*
* After this call, infrared_signal_is_raw() will return true.
*
* @param[in,out] signal pointer to the destination instance.
* @param[in] timings pointer to an array containing the raw signal timings.
* @param[in] timings_size number of elements in the timings array.
* @param[in] frequency signal carrier frequency, in Hertz.
* @param[in] duty_cycle signal duty cycle, fraction between 0 and 1.
*/
void infrared_signal_set_raw_signal(
InfraredSignal* signal,
const uint32_t* timings,
size_t timings_size,
uint32_t frequency,
float duty_cycle);
/**
* @brief Get the raw signal held by an InfraredSignal instance.
*
* @warning the instance MUST hold a *raw* signal, otherwise undefined behaviour will occur.
*
* @param[in] signal pointer to the instance to be queried.
* @returns pointer to the raw signal structure held by the instance.
*/
const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal);
/**
* @brief Set an InfraredInstance to hold a parsed signal.
*
* Any instance's previous contents will be automatically deleted before
* copying the raw signal.
*
* After this call, infrared_signal_is_raw() will return false.
*
* @param[in,out] signal pointer to the destination instance.
* @param[in] message pointer to the message containing the parsed signal.
*/
void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message);
/**
* @brief Get the parsed signal held by an InfraredSignal instance.
*
* @warning the instance MUST hold a *parsed* signal, otherwise undefined behaviour will occur.
*
* @param[in] signal pointer to the instance to be queried.
* @returns pointer to the parsed signal structure held by the instance.
*/
const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal);
/**
* @brief Read a signal and its name from a FlipperFormat file into an InfraredSignal instance.
*
* The file must be allocated and open prior to this call. The seek position determines
* which signal will be read (if there is more than one in the file). Calling this function
* repeatedly will result in all signals in the file to be read until no more are left.
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[out] name pointer to the string to hold the signal name. Must be properly allocated.
* @returns InfraredErrorCodeNone if a signal was successfully read, otherwise error code
*/
InfraredErrorCode
infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name);
/**
* @brief Read a signal name from a FlipperFormat file.
*
* Same behaviour as infrared_signal_read(), but only the name is read.
*
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[out] name pointer to the string to hold the signal name. Must be properly allocated.
* @returns InfraredErrorCodeNone if a signal name was successfully read, otherwise error code
*/
InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name);
/**
* @brief Read a signal from a FlipperFormat file.
*
* Same behaviour as infrared_signal_read(), but only the body is read.
*
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[out] signal pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated.
* @returns InfraredErrorCodeNone if a signal body was successfully read, otherwise error code.
*/
InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff);
/**
* @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance.
*
* This function will look for a signal with the given name and if found, attempt to read it.
* Same considerations apply as to infrared_signal_read().
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[in] name pointer to a zero-terminated string containing the requested signal name.
* @returns InfraredErrorCodeNone if a signal was found and successfully read, otherwise error code.
*/
InfraredErrorCode infrared_signal_search_by_name_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const char* name);
/**
* @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance.
*
* This function will look for a signal with the given index and if found, attempt to read it.
* Same considerations apply as to infrared_signal_read().
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[in] index the requested signal index.
* @returns InfraredErrorCodeNone if a signal was found and successfully read, otherwise error code.
*/
InfraredErrorCode infrared_signal_search_by_index_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
size_t index);
/**
* @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file.
*
* The file must be allocated and open prior to this call. Additionally, an appropriate header
* must be already written into the file.
*
* @param[in] signal pointer to the instance holding the signal to be saved.
* @param[in,out] ff pointer to the FlipperFormat file instance to write to.
* @param[in] name pointer to a zero-terminated string contating the name of the signal.
* @returns InfraredErrorCodeNone if a signal was successfully saved, otherwise error code
*/
InfraredErrorCode
infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name);
/**
* @brief Transmit a signal contained in an InfraredSignal instance.
*
* The transmission happens once per call using the built-in hardware (via HAL calls).
*
* @param[in] signal pointer to the instance holding the signal to be transmitted.
*/
void infrared_signal_transmit(const InfraredSignal* signal);
#ifdef __cplusplus
}
#endif