unleashed-firmware/applications/debug/unit_tests/tests/infrared/infrared_test.c
hedger 9e42e00ead
Icons: compression fixes & larger dimension support (#3564)
* toolbox, gui: fixes for compressed icon handling
* ufbt: fixes for generated vscode project
* scripts: increased max dimensions for image converter
* icon type changes
* linter fixes; api sync
* gui: docs fix
* toolbox: fixed potential decoder buffer overflow
* minor cleanup
* fbt: sdk: suppressed deprecation warnings in API table
* toolbox: compress: added unit tests
   vscode: now installs resources for unit_tests
   unit_tests: now loads subghz region data
* toolbox: compress: review fixes, pt 1
* compress: now passes decoder buffer size as constructor argument; auto-resize decoder buffer; crash on failed icon decompression
* PVS fixes
* pvs fixes, pt2
* doxygen fixes
* investigating unit test failures
* investigating unit test failures
* investigating unit test failures
* investigating unit test failures
* investigating unit test failures
* UnitTests: move all tests into plugins, brakes testing
* UnitTests: add plugin API and update plugin entrypoints
* UniTests: Test runner that works with plugins
* fbt: extra filtering for extapps to include in build
* UnitTests: filter tests by name
* loader: restored API table for unit_test build config
* Add various missing symbols to API table
* UnitTest: fail on plugin load error
* UnitTests: cleanup plugin api and reporting
* unit_tests: composite resolver
* UnitTests: remove unused declaration
* unit_tests, nfc: moved mock nfc implementation to libnfc
* unit_tests: api: removed redundant #define
* toolbox: compress: removed size_hint for icons; triggering furi_check on oversized icons
* gui: icon, icon_animation: removed size hit APIs
* Format Sources. Cleanup code.
* loader: refuse to start .fal as app
* toolbox: compress: fixed memory corruption in operations with small destination buffer; added unit tests for that case
* unit_tests: proper test skipping; better selective test interface
* unit_tests: moved 'loading' logging to proper location

Co-authored-by: あく <alleteam@gmail.com>
2024-05-20 18:23:47 +01:00

554 lines
18 KiB
C

#include <furi.h>
#include <flipper_format.h>
#include <infrared.h>
#include <common/infrared_common_i.h>
#include "../test.h"
#define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/")
#define IR_TEST_FILE_PREFIX "test_"
#define IR_TEST_FILE_SUFFIX ".irtest"
typedef struct {
InfraredDecoderHandler* decoder_handler;
InfraredEncoderHandler* encoder_handler;
FuriString* file_path;
FlipperFormat* ff;
} InfraredTest;
static InfraredTest* test;
static void infrared_test_alloc(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
test = malloc(sizeof(InfraredTest));
test->decoder_handler = infrared_alloc_decoder();
test->encoder_handler = infrared_alloc_encoder();
test->ff = flipper_format_buffered_file_alloc(storage);
test->file_path = furi_string_alloc();
}
static void infrared_test_free(void) {
furi_check(test);
infrared_free_decoder(test->decoder_handler);
infrared_free_encoder(test->encoder_handler);
flipper_format_free(test->ff);
furi_string_free(test->file_path);
furi_record_close(RECORD_STORAGE);
free(test);
test = NULL;
}
static bool infrared_test_prepare_file(const char* protocol_name) {
FuriString* file_type;
file_type = furi_string_alloc();
bool success = false;
furi_string_printf(
test->file_path,
"%s%s%s%s",
IR_TEST_FILES_DIR,
IR_TEST_FILE_PREFIX,
protocol_name,
IR_TEST_FILE_SUFFIX);
do {
uint32_t format_version;
if(!flipper_format_buffered_file_open_existing(
test->ff, furi_string_get_cstr(test->file_path)))
break;
if(!flipper_format_read_header(test->ff, file_type, &format_version)) break;
if(furi_string_cmp_str(file_type, "IR tests file") || format_version != 1) break;
success = true;
} while(false);
furi_string_free(file_type);
return success;
}
static bool infrared_test_load_raw_signal(
FlipperFormat* ff,
const char* signal_name,
uint32_t** timings,
uint32_t* timings_count) {
FuriString* buf;
buf = furi_string_alloc();
bool success = false;
do {
bool is_name_found = false;
for(; !is_name_found && flipper_format_read_string(ff, "name", buf);
is_name_found = !furi_string_cmp(buf, signal_name))
;
if(!is_name_found) break;
if(!flipper_format_read_string(ff, "type", buf) || furi_string_cmp_str(buf, "raw")) break;
if(!flipper_format_get_value_count(ff, "data", timings_count)) break;
if(!*timings_count) break;
*timings = malloc(*timings_count * sizeof(uint32_t*));
if(!flipper_format_read_uint32(ff, "data", *timings, *timings_count)) {
free(*timings);
break;
}
success = true;
} while(false);
furi_string_free(buf);
return success;
}
static bool infrared_test_read_message(FlipperFormat* ff, InfraredMessage* message) {
FuriString* buf;
buf = furi_string_alloc();
bool success = false;
do {
if(!flipper_format_read_string(ff, "protocol", buf)) break;
message->protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf));
if(!infrared_is_protocol_valid(message->protocol)) break;
if(!flipper_format_read_hex(ff, "address", (uint8_t*)&message->address, sizeof(uint32_t)))
break;
if(!flipper_format_read_hex(ff, "command", (uint8_t*)&message->command, sizeof(uint32_t)))
break;
if(!flipper_format_read_bool(ff, "repeat", &message->repeat, 1)) break;
success = true;
} while(false);
furi_string_free(buf);
return success;
}
static bool infrared_test_load_messages(
FlipperFormat* ff,
const char* signal_name,
InfraredMessage** messages,
uint32_t* messages_count) {
FuriString* buf;
buf = furi_string_alloc();
bool success = false;
do {
bool is_name_found = false;
for(; !is_name_found && flipper_format_read_string(ff, "name", buf);
is_name_found = !furi_string_cmp(buf, signal_name))
;
if(!is_name_found) break;
if(!flipper_format_read_string(ff, "type", buf) ||
furi_string_cmp_str(buf, "parsed_array"))
break;
if(!flipper_format_read_uint32(ff, "count", messages_count, 1)) break;
if(!*messages_count) break;
*messages = malloc(*messages_count * sizeof(InfraredMessage));
uint32_t i;
for(i = 0; i < *messages_count; ++i) {
if(!infrared_test_read_message(ff, (*messages) + i)) {
break;
}
}
if(*messages_count != i) {
free(*messages);
break;
}
success = true;
} while(false);
furi_string_free(buf);
return success;
}
static void infrared_test_compare_message_results(
const InfraredMessage* message_decoded,
const InfraredMessage* message_expected) {
mu_check(message_decoded->protocol == message_expected->protocol);
mu_check(message_decoded->command == message_expected->command);
mu_check(message_decoded->address == message_expected->address);
if((message_expected->protocol == InfraredProtocolSIRC) ||
(message_expected->protocol == InfraredProtocolSIRC15) ||
(message_expected->protocol == InfraredProtocolSIRC20)) {
mu_check(message_decoded->repeat == false);
} else {
mu_check(message_decoded->repeat == message_expected->repeat);
}
}
/* Encodes signal and merges same levels (high+high, low+low) */
static void infrared_test_run_encoder_fill_array(
InfraredEncoderHandler* handler,
uint32_t* timings,
uint32_t* timings_len,
bool* start_level) {
uint32_t duration = 0;
bool level = false;
bool level_read;
InfraredStatus status = InfraredStatusError;
size_t i = 0;
bool first = true;
while(1) {
status = infrared_encode(handler, &duration, &level_read);
if(first) {
if(start_level) *start_level = level_read;
first = false;
timings[0] = 0;
} else if(level_read != level) {
++i;
furi_check(i < *timings_len);
timings[i] = 0;
}
level = level_read;
timings[i] += duration;
furi_check((status == InfraredStatusOk) || (status == InfraredStatusDone));
if(status == InfraredStatusDone) break;
}
*timings_len = i + 1;
}
// messages in input array for encoder should have one protocol
static void infrared_test_run_encoder(InfraredProtocol protocol, uint32_t test_index) {
uint32_t* timings;
uint32_t timings_count = 200;
uint32_t* expected_timings;
uint32_t expected_timings_count;
InfraredMessage* input_messages;
uint32_t input_messages_count;
FuriString* buf;
buf = furi_string_alloc();
const char* protocol_name = infrared_get_protocol_name(protocol);
mu_assert(infrared_test_prepare_file(protocol_name), "Failed to prepare test file");
furi_string_printf(buf, "encoder_input%ld", test_index);
mu_assert(
infrared_test_load_messages(
test->ff, furi_string_get_cstr(buf), &input_messages, &input_messages_count),
"Failed to load messages from file");
furi_string_printf(buf, "encoder_expected%ld", test_index);
mu_assert(
infrared_test_load_raw_signal(
test->ff, furi_string_get_cstr(buf), &expected_timings, &expected_timings_count),
"Failed to load raw signal from file");
flipper_format_buffered_file_close(test->ff);
furi_string_free(buf);
uint32_t j = 0;
timings = malloc(sizeof(uint32_t) * timings_count);
for(uint32_t message_counter = 0; message_counter < input_messages_count; ++message_counter) {
const InfraredMessage* message = &input_messages[message_counter];
if(!message->repeat) {
infrared_reset_encoder(test->encoder_handler, message);
}
timings_count = 200;
infrared_test_run_encoder_fill_array(test->encoder_handler, timings, &timings_count, NULL);
furi_check(timings_count <= 200);
for(size_t i = 0; i < timings_count; ++i, ++j) {
mu_check(MATCH_TIMING(timings[i], expected_timings[j], 120));
mu_assert(j < expected_timings_count, "encoded more timings than expected");
}
}
free(input_messages);
free(expected_timings);
free(timings);
mu_assert(j == expected_timings_count, "encoded less timings than expected");
}
static void infrared_test_run_encoder_decoder(InfraredProtocol protocol, uint32_t test_index) {
uint32_t* timings = 0;
uint32_t timings_count = 200;
InfraredMessage* input_messages;
uint32_t input_messages_count;
bool level = false;
FuriString* buf;
buf = furi_string_alloc();
timings = malloc(sizeof(uint32_t) * timings_count);
const char* protocol_name = infrared_get_protocol_name(protocol);
mu_assert(infrared_test_prepare_file(protocol_name), "Failed to prepare test file");
furi_string_printf(buf, "encoder_decoder_input%ld", test_index);
mu_assert(
infrared_test_load_messages(
test->ff, furi_string_get_cstr(buf), &input_messages, &input_messages_count),
"Failed to load messages from file");
flipper_format_buffered_file_close(test->ff);
furi_string_free(buf);
for(uint32_t message_counter = 0; message_counter < input_messages_count; ++message_counter) {
const InfraredMessage* message_encoded = &input_messages[message_counter];
if(!message_encoded->repeat) {
infrared_reset_encoder(test->encoder_handler, message_encoded);
}
timings_count = 200;
infrared_test_run_encoder_fill_array(
test->encoder_handler, timings, &timings_count, &level);
furi_check(timings_count <= 200);
const InfraredMessage* message_decoded = 0;
for(size_t i = 0; i < timings_count; ++i) {
message_decoded = infrared_decode(test->decoder_handler, level, timings[i]);
if((i == timings_count - 2) && level && message_decoded) {
/* In case we end with space timing - message can be decoded at last mark */
break;
} else if(i < timings_count - 1) {
mu_check(!message_decoded);
} else {
if(!message_decoded) {
message_decoded = infrared_check_decoder_ready(test->decoder_handler);
}
mu_check(message_decoded);
}
level = !level;
}
if(message_decoded) {
infrared_test_compare_message_results(message_decoded, message_encoded);
} else {
mu_check(0);
}
}
free(input_messages);
free(timings);
}
static void infrared_test_run_decoder(InfraredProtocol protocol, uint32_t test_index) {
uint32_t* timings;
uint32_t timings_count;
InfraredMessage* messages;
uint32_t messages_count;
FuriString* buf;
buf = furi_string_alloc();
mu_assert(
infrared_test_prepare_file(infrared_get_protocol_name(protocol)),
"Failed to prepare test file");
furi_string_printf(buf, "decoder_input%ld", test_index);
mu_assert(
infrared_test_load_raw_signal(
test->ff, furi_string_get_cstr(buf), &timings, &timings_count),
"Failed to load raw signal from file");
furi_string_printf(buf, "decoder_expected%ld", test_index);
mu_assert(
infrared_test_load_messages(
test->ff, furi_string_get_cstr(buf), &messages, &messages_count),
"Failed to load messages from file");
flipper_format_buffered_file_close(test->ff);
furi_string_free(buf);
InfraredMessage message_decoded_check_local;
bool level = 0;
uint32_t message_counter = 0;
const InfraredMessage* message_decoded = 0;
for(uint32_t i = 0; i < timings_count; ++i) {
const InfraredMessage* message_decoded_check = 0;
if(timings[i] > INFRARED_RAW_RX_TIMING_DELAY_US) {
message_decoded_check = infrared_check_decoder_ready(test->decoder_handler);
if(message_decoded_check) {
/* infrared_decode() can reset message, but we have to call infrared_decode() to perform real
* simulation: infrared_check() by timeout, then infrared_decode() when meet edge */
message_decoded_check_local = *message_decoded_check;
message_decoded_check = &message_decoded_check_local;
}
}
message_decoded = infrared_decode(test->decoder_handler, level, timings[i]);
if(message_decoded_check || message_decoded) {
mu_assert(
!(message_decoded_check && message_decoded),
"both messages decoded: check_ready() and infrared_decode()");
if(message_decoded_check) {
message_decoded = message_decoded_check;
}
mu_assert(message_counter < messages_count, "decoded more than expected");
infrared_test_compare_message_results(message_decoded, &messages[message_counter]);
++message_counter;
}
level = !level;
}
message_decoded = infrared_check_decoder_ready(test->decoder_handler);
if(message_decoded) {
infrared_test_compare_message_results(message_decoded, &messages[message_counter]);
++message_counter;
}
free(timings);
free(messages);
mu_assert(message_counter == messages_count, "decoded less than expected");
}
MU_TEST(infrared_test_decoder_samsung32) {
infrared_test_run_decoder(InfraredProtocolSamsung32, 1);
}
MU_TEST(infrared_test_decoder_mixed) {
infrared_test_run_decoder(InfraredProtocolRC5, 2);
infrared_test_run_decoder(InfraredProtocolSIRC, 1);
infrared_test_run_decoder(InfraredProtocolNECext, 1);
infrared_test_run_decoder(InfraredProtocolRC6, 2);
infrared_test_run_decoder(InfraredProtocolSamsung32, 1);
infrared_test_run_decoder(InfraredProtocolRC6, 1);
infrared_test_run_decoder(InfraredProtocolSamsung32, 1);
infrared_test_run_decoder(InfraredProtocolRC5, 1);
infrared_test_run_decoder(InfraredProtocolSIRC, 2);
infrared_test_run_decoder(InfraredProtocolNECext, 1);
infrared_test_run_decoder(InfraredProtocolSIRC, 4);
infrared_test_run_decoder(InfraredProtocolNEC, 2);
infrared_test_run_decoder(InfraredProtocolRC6, 1);
infrared_test_run_decoder(InfraredProtocolNECext, 1);
infrared_test_run_decoder(InfraredProtocolSIRC, 5);
infrared_test_run_decoder(InfraredProtocolNEC, 3);
infrared_test_run_decoder(InfraredProtocolRC5, 5);
infrared_test_run_decoder(InfraredProtocolSamsung32, 1);
infrared_test_run_decoder(InfraredProtocolSIRC, 3);
infrared_test_run_decoder(InfraredProtocolKaseikyo, 1);
infrared_test_run_decoder(InfraredProtocolRCA, 1);
infrared_test_run_decoder(InfraredProtocolPioneer, 6);
}
MU_TEST(infrared_test_decoder_nec) {
for(uint32_t i = 1; i <= 3; ++i) {
infrared_test_run_decoder(InfraredProtocolNEC, i);
}
}
MU_TEST(infrared_test_decoder_unexpected_end_in_sequence) {
for(uint32_t i = 1; i <= 2; ++i) {
infrared_test_run_decoder(InfraredProtocolNEC, i);
infrared_test_run_decoder(InfraredProtocolNEC, i);
}
}
MU_TEST(infrared_test_decoder_necext1) {
for(uint32_t i = 0; i < 2; ++i) {
UNUSED(i);
infrared_test_run_decoder(InfraredProtocolNECext, 1);
}
}
MU_TEST(infrared_test_decoder_long_packets_with_nec_start) {
for(uint32_t i = 1; i <= 2; ++i) {
infrared_test_run_decoder(InfraredProtocolNEC42ext, i);
}
}
MU_TEST(infrared_test_encoder_sirc) {
for(uint32_t i = 1; i <= 2; ++i) {
infrared_test_run_encoder(InfraredProtocolSIRC, i);
}
}
MU_TEST(infrared_test_decoder_sirc) {
for(uint32_t i = 1; i <= 5; ++i) {
infrared_test_run_decoder(InfraredProtocolSIRC, 5);
}
}
MU_TEST(infrared_test_decoder_rc5) {
infrared_test_run_decoder(InfraredProtocolRC5X, 1);
for(uint32_t i = 1; i <= 7; ++i) {
infrared_test_run_decoder(InfraredProtocolRC5, i);
}
}
MU_TEST(infrared_test_encoder_rc5x) {
infrared_test_run_encoder(InfraredProtocolRC5X, 1);
}
MU_TEST(infrared_test_encoder_rc5) {
infrared_test_run_encoder(InfraredProtocolRC5, 1);
}
MU_TEST(infrared_test_decoder_rc6) {
infrared_test_run_decoder(InfraredProtocolRC6, 1);
}
MU_TEST(infrared_test_encoder_rc6) {
infrared_test_run_encoder(InfraredProtocolRC6, 1);
}
MU_TEST(infrared_test_decoder_kaseikyo) {
for(uint32_t i = 1; i <= 6; ++i) {
infrared_test_run_decoder(InfraredProtocolKaseikyo, i);
}
}
MU_TEST(infrared_test_decoder_rca) {
for(uint32_t i = 1; i <= 6; ++i) {
infrared_test_run_decoder(InfraredProtocolRCA, i);
}
}
MU_TEST(infrared_test_decoder_pioneer) {
for(uint32_t i = 1; i <= 11; ++i) {
infrared_test_run_decoder(InfraredProtocolPioneer, i);
}
}
MU_TEST(infrared_test_encoder_decoder_all) {
infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1);
infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1);
infrared_test_run_encoder_decoder(InfraredProtocolNEC42, 1);
infrared_test_run_encoder_decoder(InfraredProtocolNEC42ext, 1);
infrared_test_run_encoder_decoder(InfraredProtocolSamsung32, 1);
infrared_test_run_encoder_decoder(InfraredProtocolRC6, 1);
infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1);
infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1);
infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1);
infrared_test_run_encoder_decoder(InfraredProtocolRCA, 1);
infrared_test_run_encoder_decoder(InfraredProtocolPioneer, 1);
}
MU_TEST_SUITE(infrared_test) {
MU_SUITE_CONFIGURE(&infrared_test_alloc, &infrared_test_free);
MU_RUN_TEST(infrared_test_encoder_sirc);
MU_RUN_TEST(infrared_test_decoder_sirc);
MU_RUN_TEST(infrared_test_encoder_rc5x);
MU_RUN_TEST(infrared_test_encoder_rc5);
MU_RUN_TEST(infrared_test_decoder_rc5);
MU_RUN_TEST(infrared_test_decoder_rc6);
MU_RUN_TEST(infrared_test_encoder_rc6);
MU_RUN_TEST(infrared_test_decoder_unexpected_end_in_sequence);
MU_RUN_TEST(infrared_test_decoder_long_packets_with_nec_start);
MU_RUN_TEST(infrared_test_decoder_nec);
MU_RUN_TEST(infrared_test_decoder_samsung32);
MU_RUN_TEST(infrared_test_decoder_necext1);
MU_RUN_TEST(infrared_test_decoder_kaseikyo);
MU_RUN_TEST(infrared_test_decoder_rca);
MU_RUN_TEST(infrared_test_decoder_pioneer);
MU_RUN_TEST(infrared_test_decoder_mixed);
MU_RUN_TEST(infrared_test_encoder_decoder_all);
}
int run_minunit_test_infrared(void) {
MU_RUN_SUITE(infrared_test);
return MU_EXIT_CODE;
}
TEST_API_DEFINE(run_minunit_test_infrared)