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:
218
lib/infrared/signal/infrared_brute_force.c
Normal file
218
lib/infrared/signal/infrared_brute_force.c
Normal 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);
|
||||
}
|
||||
122
lib/infrared/signal/infrared_brute_force.h
Normal file
122
lib/infrared/signal/infrared_brute_force.h
Normal 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
|
||||
53
lib/infrared/signal/infrared_error_code.h
Normal file
53
lib/infrared/signal/infrared_error_code.h
Normal 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
|
||||
458
lib/infrared/signal/infrared_signal.c
Normal file
458
lib/infrared/signal/infrared_signal.c
Normal 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);
|
||||
}
|
||||
}
|
||||
228
lib/infrared/signal/infrared_signal.h
Normal file
228
lib/infrared/signal/infrared_signal.h
Normal 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
|
||||
Reference in New Issue
Block a user