From c42cce3c6cea61d41babcfa1e732cbcf5738b3a0 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 25 Feb 2022 23:36:29 +1000 Subject: [PATCH 01/13] [FL-2312] Flipper format: insert OR update (#1009) * Flipper format: seek_to_end, key_exist * Flipper Format: insert_or_update --- .../flipper_format_string_test.c | 110 +++++++++++++++++ lib/flipper_format/flipper_format.c | 116 +++++++++++++++++- lib/flipper_format/flipper_format.h | 99 +++++++++++++++ lib/flipper_format/flipper_format_stream.c | 2 +- lib/flipper_format/flipper_format_stream_i.h | 11 ++ 5 files changed, 336 insertions(+), 2 deletions(-) diff --git a/applications/tests/flipper_format/flipper_format_string_test.c b/applications/tests/flipper_format/flipper_format_string_test.c index 9986c523..ebc3008f 100644 --- a/applications/tests/flipper_format/flipper_format_string_test.c +++ b/applications/tests/flipper_format/flipper_format_string_test.c @@ -11,22 +11,30 @@ static const uint32_t test_version = 666; static const char* test_string_key = "String data"; static const char* test_string_data = "String"; static const char* test_string_updated_data = "New string"; +static const char* test_string_updated_2_data = "And some more"; static const char* test_int_key = "Int32 data"; static const int32_t test_int_data[] = {1234, -6345, 7813, 0}; static const int32_t test_int_updated_data[] = {-1337, 69}; +static const int32_t test_int_updated_2_data[] = {-3, -2, -1, 0, 1, 2, 3}; static const char* test_uint_key = "Uint32 data"; static const uint32_t test_uint_data[] = {1234, 0, 5678, 9098, 7654321}; static const uint32_t test_uint_updated_data[] = {8, 800, 555, 35, 35}; +static const uint32_t test_uint_updated_2_data[] = {20, 21}; static const char* test_float_key = "Float data"; static const float test_float_data[] = {1.5f, 1000.0f}; static const float test_float_updated_data[] = {1.2f}; +static const float test_float_updated_2_data[] = {0.01f, 0.0f, -51.6f}; static const char* test_hex_key = "Hex data"; static const uint8_t test_hex_data[] = {0xDE, 0xAD, 0xBE}; static const uint8_t test_hex_updated_data[] = {0xFE, 0xCA}; +static const uint8_t test_hex_updated_2_data[] = {0xCA, 0xCA, 0x05}; + +static const char* test_hex_new_key = "New Hex data"; +static const uint8_t test_hex_new_data[] = {0xFF, 0x6A, 0x91}; static const char* test_data_nix = "Filetype: Flipper Format test\n" "Version: 666\n" @@ -59,7 +67,40 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { uint32_t count; + // key exist test + size_t position_before = stream_tell(flipper_format_get_raw_stream(flipper_format)); + mu_check(flipper_format_key_exist(flipper_format, test_hex_key)); + mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); + + mu_check(!flipper_format_key_exist(flipper_format, "invalid key")); + mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); + + // stream seek to end test + mu_check(flipper_format_seek_to_end(flipper_format)); + mu_assert_int_eq( + stream_size(flipper_format_get_raw_stream(flipper_format)), + stream_tell(flipper_format_get_raw_stream(flipper_format))); + + // key exist test + position_before = stream_tell(flipper_format_get_raw_stream(flipper_format)); + mu_check(flipper_format_key_exist(flipper_format, test_hex_key)); + mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); + + mu_check(!flipper_format_key_exist(flipper_format, "invalid key")); + mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); + + // rewind mu_check(flipper_format_rewind(flipper_format)); + + // key exist test + position_before = stream_tell(flipper_format_get_raw_stream(flipper_format)); + mu_check(flipper_format_key_exist(flipper_format, test_hex_key)); + mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); + + mu_check(!flipper_format_key_exist(flipper_format, "invalid key")); + mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); + + // read test string_init(tmpstr); mu_check(flipper_format_read_header(flipper_format, tmpstr, &version)); @@ -94,6 +135,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { string_clear(tmpstr); + // update data mu_check(flipper_format_rewind(flipper_format)); mu_check(flipper_format_update_string_cstr( flipper_format, test_string_key, test_string_updated_data)); @@ -106,6 +148,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { mu_check(flipper_format_update_hex( flipper_format, test_hex_key, ARRAY_W_COUNT(test_hex_updated_data))); + // read updated data test uint32_t uint32_updated_data[COUNT_OF(test_uint_updated_data)]; int32_t int32_updated_data[COUNT_OF(test_int_updated_data)]; float float_updated_data[COUNT_OF(test_float_updated_data)]; @@ -149,9 +192,76 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { string_clear(tmpstr); + // update data + mu_check(flipper_format_rewind(flipper_format)); + mu_check(flipper_format_insert_or_update_string_cstr( + flipper_format, test_string_key, test_string_updated_2_data)); + mu_check(flipper_format_insert_or_update_int32( + flipper_format, test_int_key, ARRAY_W_COUNT(test_int_updated_2_data))); + mu_check(flipper_format_insert_or_update_uint32( + flipper_format, test_uint_key, ARRAY_W_COUNT(test_uint_updated_2_data))); + mu_check(flipper_format_insert_or_update_float( + flipper_format, test_float_key, ARRAY_W_COUNT(test_float_updated_2_data))); + mu_check(flipper_format_insert_or_update_hex( + flipper_format, test_hex_key, ARRAY_W_COUNT(test_hex_updated_2_data))); + mu_check(flipper_format_insert_or_update_hex( + flipper_format, test_hex_new_key, ARRAY_W_COUNT(test_hex_new_data))); + + uint32_t uint32_updated_2_data[COUNT_OF(test_uint_updated_2_data)]; + int32_t int32_updated_2_data[COUNT_OF(test_int_updated_2_data)]; + float float_updated_2_data[COUNT_OF(test_float_updated_2_data)]; + uint8_t hex_updated_2_data[COUNT_OF(test_hex_updated_2_data)]; + uint8_t hex_new_data[COUNT_OF(test_hex_new_data)]; + + mu_check(flipper_format_rewind(flipper_format)); + string_init(tmpstr); + + mu_check(flipper_format_read_header(flipper_format, tmpstr, &version)); + mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr)); + mu_assert_int_eq(test_version, version); + + mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr)); + mu_assert_string_eq(test_string_updated_2_data, string_get_cstr(tmpstr)); + + mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count)); + mu_assert_int_eq(COUNT_OF(test_int_updated_2_data), count); + mu_check(flipper_format_read_int32( + flipper_format, test_int_key, ARRAY_W_COUNT(int32_updated_2_data))); + mu_check(memcmp(test_int_updated_2_data, ARRAY_W_BSIZE(int32_updated_2_data)) == 0); + + mu_check(flipper_format_get_value_count(flipper_format, test_uint_key, &count)); + mu_assert_int_eq(COUNT_OF(test_uint_updated_2_data), count); + mu_check(flipper_format_read_uint32( + flipper_format, test_uint_key, ARRAY_W_COUNT(uint32_updated_2_data))); + mu_check(memcmp(test_uint_updated_2_data, ARRAY_W_BSIZE(uint32_updated_2_data)) == 0); + + mu_check(flipper_format_get_value_count(flipper_format, test_float_key, &count)); + mu_assert_int_eq(COUNT_OF(test_float_updated_2_data), count); + mu_check(flipper_format_read_float( + flipper_format, test_float_key, ARRAY_W_COUNT(float_updated_2_data))); + mu_check(memcmp(test_float_updated_2_data, ARRAY_W_BSIZE(float_updated_2_data)) == 0); + + mu_check(flipper_format_get_value_count(flipper_format, test_hex_key, &count)); + mu_assert_int_eq(COUNT_OF(test_hex_updated_2_data), count); + mu_check( + flipper_format_read_hex(flipper_format, test_hex_key, ARRAY_W_COUNT(hex_updated_2_data))); + mu_check(memcmp(test_hex_updated_2_data, ARRAY_W_BSIZE(hex_updated_2_data)) == 0); + + mu_check(flipper_format_get_value_count(flipper_format, test_hex_new_key, &count)); + mu_assert_int_eq(COUNT_OF(test_hex_new_data), count); + mu_check( + flipper_format_read_hex(flipper_format, test_hex_new_key, ARRAY_W_COUNT(hex_new_data))); + mu_check(memcmp(test_hex_new_data, ARRAY_W_BSIZE(hex_new_data)) == 0); + + mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr)); + + string_clear(tmpstr); + + // delete key test mu_check(flipper_format_rewind(flipper_format)); mu_check(flipper_format_delete_key(flipper_format, test_uint_key)); + // deleted key read test mu_check(flipper_format_rewind(flipper_format)); mu_check(!flipper_format_read_uint32( flipper_format, test_uint_key, ARRAY_W_COUNT(uint32_updated_data))); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 074d6da2..dd3676c5 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -102,6 +102,20 @@ bool flipper_format_rewind(FlipperFormat* flipper_format) { return stream_rewind(flipper_format->stream); } +bool flipper_format_seek_to_end(FlipperFormat* flipper_format) { + furi_assert(flipper_format); + return stream_seek(flipper_format->stream, 0, StreamOffsetFromEnd); +} + +bool flipper_format_key_exist(FlipperFormat* flipper_format, const char* key) { + size_t pos = stream_tell(flipper_format->stream); + stream_seek(flipper_format->stream, 0, StreamOffsetFromStart); + bool result = flipper_format_stream_seek_to_key(flipper_format->stream, key, false); + stream_seek(flipper_format->stream, pos, StreamOffsetFromStart); + + return result; +} + bool flipper_format_read_header( FlipperFormat* flipper_format, string_t filetype, @@ -320,7 +334,7 @@ bool flipper_format_update_string(FlipperFormat* flipper_format, const char* key FlipperStreamWriteData write_data = { .key = key, .type = FlipperStreamValueStr, - .data = data, + .data = string_get_cstr(data), .data_size = 1, }; bool result = flipper_format_stream_delete_key_and_write( @@ -408,3 +422,103 @@ bool flipper_format_update_hex( flipper_format->stream, &write_data, flipper_format->strict_mode); return result; } + +bool flipper_format_insert_or_update_string( + FlipperFormat* flipper_format, + const char* key, + string_t data) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_string(flipper_format, key, data); + } else { + result = flipper_format_update_string(flipper_format, key, data); + } + + return result; +} + +bool flipper_format_insert_or_update_string_cstr( + FlipperFormat* flipper_format, + const char* key, + const char* data) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_string_cstr(flipper_format, key, data); + } else { + result = flipper_format_update_string_cstr(flipper_format, key, data); + } + + return result; +} + +bool flipper_format_insert_or_update_uint32( + FlipperFormat* flipper_format, + const char* key, + const uint32_t* data, + const uint16_t data_size) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_uint32(flipper_format, key, data, data_size); + } else { + result = flipper_format_update_uint32(flipper_format, key, data, data_size); + } + + return result; +} + +bool flipper_format_insert_or_update_int32( + FlipperFormat* flipper_format, + const char* key, + const int32_t* data, + const uint16_t data_size) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_int32(flipper_format, key, data, data_size); + } else { + result = flipper_format_update_int32(flipper_format, key, data, data_size); + } + + return result; +} + +bool flipper_format_insert_or_update_float( + FlipperFormat* flipper_format, + const char* key, + const float* data, + const uint16_t data_size) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_float(flipper_format, key, data, data_size); + } else { + result = flipper_format_update_float(flipper_format, key, data, data_size); + } + + return result; +} + +bool flipper_format_insert_or_update_hex( + FlipperFormat* flipper_format, + const char* key, + const uint8_t* data, + const uint16_t data_size) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_hex(flipper_format, key, data, data_size); + } else { + result = flipper_format_update_hex(flipper_format, key, data, data_size); + } + + return result; +} \ No newline at end of file diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index c23e02fc..a67c7a47 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -179,6 +179,22 @@ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_m */ bool flipper_format_rewind(FlipperFormat* flipper_format); +/** + * Move the RW pointer at the end. Can be useful if you want to add some data after reading. + * @param flipper_format Pointer to a FlipperFormat instance + * @return True on success + */ +bool flipper_format_seek_to_end(FlipperFormat* flipper_format); + +/** + * Check if the key exists. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @return true key exists + * @return false key is not exists + */ +bool flipper_format_key_exist(FlipperFormat* flipper_format, const char* key); + /** * Read the header (file type and version). * @param flipper_format Pointer to a FlipperFormat instance @@ -466,6 +482,89 @@ bool flipper_format_update_hex( const uint8_t* data, const uint16_t data_size); +/** + * Updates the value of the first matching key to a string value, or adds the key and value if the key did not exist. + * Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_string( + FlipperFormat* flipper_format, + const char* key, + string_t data); + +/** + * Updates the value of the first matching key to a string value, or adds the key and value if the key did not exist. + * Plain C version. + * Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_string_cstr( + FlipperFormat* flipper_format, + const char* key, + const char* data); + +/** + * Updates the value of the first matching key to a uint32 array value, or adds the key and value if the key did not exist. + * Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_uint32( + FlipperFormat* flipper_format, + const char* key, + const uint32_t* data, + const uint16_t data_size); + +/** + * Updates the value of the first matching key to a int32 array value, or adds the key and value if the key did not exist. + * Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_int32( + FlipperFormat* flipper_format, + const char* key, + const int32_t* data, + const uint16_t data_size); + +/** + * Updates the value of the first matching key to a float array value, or adds the key and value if the key did not exist. + * Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_float( + FlipperFormat* flipper_format, + const char* key, + const float* data, + const uint16_t data_size); + +/** + * Updates the value of the first matching key to an array of hex-formatted bytes, or adds the key and value if the key did not exist. + *Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_hex( + FlipperFormat* flipper_format, + const char* key, + const uint8_t* data, + const uint16_t data_size); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index 02425d63..44084b05 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -93,7 +93,7 @@ static bool flipper_format_stream_read_valid_key(Stream* stream, string_t key) { return found; } -static bool flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) { +bool flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) { bool found = false; string_t read_key; diff --git a/lib/flipper_format/flipper_format_stream_i.h b/lib/flipper_format/flipper_format_stream_i.h index ee825d0e..74cf8f1a 100644 --- a/lib/flipper_format/flipper_format_stream_i.h +++ b/lib/flipper_format/flipper_format_stream_i.h @@ -18,6 +18,17 @@ extern "C" { */ bool flipper_format_stream_write_eol(Stream* stream); +/** + * Seek to the key from the current position of the stream. + * Position will be at the beginning of the value corresponding to the key, if the key is found,, or at the end of the stream. + * @param stream + * @param key + * @param strict_mode + * @return true key is found + * @return false key is not found + */ +bool flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode); + #ifdef __cplusplus } #endif \ No newline at end of file From 052237f8c9bb34bc244abcbf108cdf1ec6ec58ec Mon Sep 17 00:00:00 2001 From: Albert Kharisov Date: Fri, 25 Feb 2022 19:22:58 +0400 Subject: [PATCH 02/13] [FL-2279] IR doxygen, rename irda -> infrared (#1010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IR: Doxygen docs, some rename * Rename irda -> infrared * Rollback collateral renames Co-authored-by: あく --- .github/CODEOWNERS | 2 +- Makefile | 2 +- applications/ReadMe.md | 4 +- applications/applications.c | 18 +- applications/applications.mk | 16 +- .../archive/helpers/archive_browser.h | 6 +- applications/archive/helpers/archive_files.h | 2 +- .../archive/scenes/archive_scene_browser.c | 2 +- .../archive/views/archive_browser_view.c | 4 +- .../archive/views/archive_browser_view.h | 2 +- applications/gui/modules/button_menu.c | 4 +- .../cli/infrared_cli.cpp} | 98 +-- .../helpers/infrared_parser.cpp} | 45 +- .../infrared/helpers/infrared_parser.h | 48 ++ .../infrared_app.cpp} | 121 ++-- applications/infrared/infrared_app.h | 322 ++++++++++ .../infrared_app_brute_force.cpp} | 22 +- .../infrared/infrared_app_brute_force.h | 67 ++ applications/infrared/infrared_app_event.h | 47 ++ .../infrared_app_remote_manager.cpp} | 71 +- .../infrared/infrared_app_remote_manager.h | 188 ++++++ .../infrared_app_signal.cpp} | 32 +- applications/infrared/infrared_app_signal.h | 134 ++++ .../infrared_app_view_manager.cpp} | 90 +-- .../infrared/infrared_app_view_manager.h | 164 +++++ applications/infrared/infrared_runner.cpp | 9 + .../infrared/scene/infrared_app_scene.h | 305 +++++++++ .../scene/infrared_app_scene_ask_back.cpp} | 30 +- .../scene/infrared_app_scene_edit.cpp} | 48 +- .../scene/infrared_app_scene_edit_delete.cpp} | 40 +- .../infrared_app_scene_edit_delete_done.cpp | 38 ++ .../infrared_app_scene_edit_key_select.cpp | 57 ++ .../scene/infrared_app_scene_edit_rename.cpp} | 34 +- .../infrared_app_scene_edit_rename_done.cpp | 31 + .../scene/infrared_app_scene_learn.cpp | 75 +++ .../scene/infrared_app_scene_learn_done.cpp} | 20 +- .../infrared_app_scene_learn_enter_name.cpp} | 26 +- .../infrared_app_scene_learn_success.cpp} | 65 +- .../scene/infrared_app_scene_remote.cpp} | 74 +-- .../scene/infrared_app_scene_remote_list.cpp} | 23 +- .../scene/infrared_app_scene_start.cpp} | 28 +- .../scene/infrared_app_scene_universal.cpp} | 28 +- .../infrared_app_scene_universal_common.cpp | 101 +++ .../infrared_app_scene_universal_tv.cpp} | 31 +- .../view/infrared_progress_view.c} | 48 +- .../infrared/view/infrared_progress_view.h | 68 ++ .../infrared_monitor/infrared_monitor.c | 140 ++++ applications/irda/helpers/irda_parser.h | 13 - applications/irda/irda_app.h | 138 ---- applications/irda/irda_app_brute_force.h | 36 -- applications/irda/irda_app_event.h | 28 - applications/irda/irda_app_remote_manager.h | 76 --- applications/irda/irda_app_signal.h | 68 -- applications/irda/irda_app_view_manager.h | 66 -- applications/irda/irda_runner.cpp | 9 - applications/irda/scene/irda_app_scene.h | 184 ------ .../scene/irda_app_scene_edit_delete_done.cpp | 38 -- .../scene/irda_app_scene_edit_key_select.cpp | 57 -- .../scene/irda_app_scene_edit_rename_done.cpp | 31 - .../irda/scene/irda_app_scene_learn.cpp | 75 --- .../scene/irda_app_scene_universal_common.cpp | 101 --- applications/irda/view/irda_progress_view.h | 25 - applications/irda_monitor/irda_monitor.c | 139 ---- .../infrared_decoder_encoder_test.c} | 95 +-- .../test_data/infrared_nec_test_data.srcdata} | 316 ++++----- .../infrared_necext_test_data.srcdata} | 272 ++++---- .../test_data/infrared_rc5_test_data.srcdata} | 106 +-- .../test_data/infrared_rc6_test_data.srcdata} | 122 ++-- .../infrared_samsung_test_data.srcdata} | 134 ++-- .../infrared_sirc_test_data.srcdata} | 414 ++++++------ applications/tests/rpc/rpc_test.c | 2 +- applications/tests/test_index.c | 4 +- assets/compiled/assets_icons.c | 92 +-- assets/compiled/assets_icons.h | 26 +- .../icons/{Irda => Infrared}/Back_15x10.png | Bin .../DolphinReadingSuccess_59x63.png | Bin .../icons/{Irda => Infrared}/Down_25x27.png | Bin .../{Irda => Infrared}/Down_hvr_25x27.png | Bin .../{Irda => Infrared}/Fill-marker_7x7.png | Bin .../InfraredArrowDown_4x8.png} | Bin .../InfraredArrowUp_4x8.png} | Bin .../InfraredLearnShort_128x31.png} | Bin .../InfraredLearn_128x64.png} | Bin .../InfraredSendShort_128x34.png} | Bin .../InfraredSend_128x64.png} | Bin .../icons/{Irda => Infrared}/Mute_25x27.png | Bin .../{Irda => Infrared}/Mute_hvr_25x27.png | Bin .../icons/{Irda => Infrared}/Power_25x27.png | Bin .../{Irda => Infrared}/Power_hvr_25x27.png | Bin assets/icons/{Irda => Infrared}/Up_25x27.png | Bin .../icons/{Irda => Infrared}/Up_hvr_25x27.png | Bin .../{Irda => Infrared}/Vol_down_25x27.png | Bin .../{Irda => Infrared}/Vol_down_hvr_25x27.png | Bin .../icons/{Irda => Infrared}/Vol_up_25x27.png | Bin .../{Irda => Infrared}/Vol_up_hvr_25x27.png | Bin .../resources/{irda => infrared}/assets/tv.ir | 0 .../targets/f6/furi_hal/furi_hal_resources.c | 4 +- .../targets/f6/furi_hal/furi_hal_resources.h | 4 +- .../targets/f7/furi_hal/furi_hal_resources.c | 4 +- .../targets/f7/furi_hal/furi_hal_resources.h | 4 +- documentation/Doxyfile | 2 +- firmware/targets/f6/Inc/main.h | 12 +- .../{furi_hal_irda.c => furi_hal_infrared.c} | 441 ++++++------- .../targets/f6/furi_hal/furi_hal_resources.c | 4 +- .../targets/f6/furi_hal/furi_hal_resources.h | 4 +- firmware/targets/f7/Inc/main.h | 12 +- .../{furi_hal_irda.c => furi_hal_infrared.c} | 441 ++++++------- .../targets/f7/furi_hal/furi_hal_resources.c | 4 +- .../targets/f7/furi_hal/furi_hal_resources.h | 4 +- .../furi_hal_include/furi_hal_infrared.h | 148 +++++ .../targets/furi_hal_include/furi_hal_irda.h | 148 ----- lib/ReadMe.md | 2 +- .../common/infrared_common_decoder.c} | 113 ++-- .../common/infrared_common_encoder.c} | 78 +-- .../common/infrared_common_i.h | 89 +++ .../common/infrared_common_protocol_defs.c | 117 ++++ lib/infrared/encoder_decoder/infrared.c | 301 +++++++++ lib/infrared/encoder_decoder/infrared.h | 205 ++++++ .../encoder_decoder/infrared_i.h} | 22 +- .../infrared_protocol_defs_i.h | 269 ++++++++ .../nec/infrared_decoder_nec.c} | 56 +- .../nec/infrared_encoder_nec.c} | 53 +- .../encoder_decoder/nec/infrared_nec_spec.c | 47 ++ .../rc5/infrared_decoder_rc5.c | 82 +++ .../rc5/infrared_encoder_rc5.c | 55 ++ .../encoder_decoder/rc5/infrared_rc5_spec.c | 27 + .../rc6/infrared_decoder_rc6.c} | 66 +- .../rc6/infrared_encoder_rc6.c | 61 ++ .../encoder_decoder/rc6/infrared_rc6_spec.c | 17 + .../samsung/infrared_decoder_samsung.c | 72 +++ .../samsung/infrared_encoder_samsung.c} | 45 +- .../samsung/infrared_samsung_spec.c | 17 + .../sirc/infrared_decoder_sirc.c | 60 ++ .../sirc/infrared_encoder_sirc.c | 73 +++ .../encoder_decoder/sirc/infrared_sirc_spec.c | 37 ++ lib/infrared/worker/infrared_transmit.c | 116 ++++ .../worker/infrared_transmit.h} | 12 +- lib/infrared/worker/infrared_worker.c | 608 ++++++++++++++++++ lib/infrared/worker/infrared_worker.h | 175 +++++ .../encoder_decoder/common/irda_common_i.h | 81 --- .../common/irda_common_protocol_defs.c | 117 ---- lib/irda/encoder_decoder/irda.c | 298 --------- lib/irda/encoder_decoder/irda.h | 204 ------ .../encoder_decoder/irda_protocol_defs_i.h | 262 -------- lib/irda/encoder_decoder/nec/irda_nec_spec.c | 47 -- .../encoder_decoder/rc5/irda_decoder_rc5.c | 82 --- .../encoder_decoder/rc5/irda_encoder_rc5.c | 55 -- lib/irda/encoder_decoder/rc5/irda_rc5_spec.c | 27 - .../encoder_decoder/rc6/irda_encoder_rc6.c | 61 -- lib/irda/encoder_decoder/rc6/irda_rc6_spec.c | 17 - .../samsung/irda_decoder_samsung.c | 72 --- .../samsung/irda_samsung_spec.c | 17 - .../encoder_decoder/sirc/irda_decoder_sirc.c | 60 -- .../encoder_decoder/sirc/irda_encoder_sirc.c | 71 -- .../encoder_decoder/sirc/irda_sirc_spec.c | 37 -- lib/irda/worker/irda_transmit.c | 114 ---- lib/irda/worker/irda_worker.c | 598 ----------------- lib/irda/worker/irda_worker.h | 171 ----- lib/lib.mk | 10 +- 159 files changed, 6387 insertions(+), 5622 deletions(-) rename applications/{irda/cli/irda_cli.cpp => infrared/cli/infrared_cli.cpp} (56%) rename applications/{irda/helpers/irda_parser.cpp => infrared/helpers/infrared_parser.cpp} (78%) create mode 100644 applications/infrared/helpers/infrared_parser.h rename applications/{irda/irda_app.cpp => infrared/infrared_app.cpp} (61%) create mode 100644 applications/infrared/infrared_app.h rename applications/{irda/irda_app_brute_force.cpp => infrared/infrared_app_brute_force.cpp} (78%) create mode 100644 applications/infrared/infrared_app_brute_force.h create mode 100644 applications/infrared/infrared_app_event.h rename applications/{irda/irda_app_remote_manager.cpp => infrared/infrared_app_remote_manager.cpp} (69%) create mode 100644 applications/infrared/infrared_app_remote_manager.h rename applications/{irda/irda_app_signal.cpp => infrared/infrared_app_signal.cpp} (75%) create mode 100644 applications/infrared/infrared_app_signal.h rename applications/{irda/irda_app_view_manager.cpp => infrared/infrared_app_view_manager.cpp} (51%) create mode 100644 applications/infrared/infrared_app_view_manager.h create mode 100644 applications/infrared/infrared_runner.cpp create mode 100644 applications/infrared/scene/infrared_app_scene.h rename applications/{irda/scene/irda_app_scene_ask_back.cpp => infrared/scene/infrared_app_scene_ask_back.cpp} (65%) rename applications/{irda/scene/irda_app_scene_edit.cpp => infrared/scene/infrared_app_scene_edit.cpp} (50%) rename applications/{irda/scene/irda_app_scene_edit_delete.cpp => infrared/scene/infrared_app_scene_edit_delete.cpp} (66%) create mode 100644 applications/infrared/scene/infrared_app_scene_edit_delete_done.cpp create mode 100644 applications/infrared/scene/infrared_app_scene_edit_key_select.cpp rename applications/{irda/scene/irda_app_scene_edit_rename.cpp => infrared/scene/infrared_app_scene_edit_rename.cpp} (60%) create mode 100644 applications/infrared/scene/infrared_app_scene_edit_rename_done.cpp create mode 100644 applications/infrared/scene/infrared_app_scene_learn.cpp rename applications/{irda/scene/irda_app_scene_learn_done.cpp => infrared/scene/infrared_app_scene_learn_done.cpp} (53%) rename applications/{irda/scene/irda_app_scene_learn_enter_name.cpp => infrared/scene/infrared_app_scene_learn_enter_name.cpp} (58%) rename applications/{irda/scene/irda_app_scene_learn_success.cpp => infrared/scene/infrared_app_scene_learn_success.cpp} (61%) rename applications/{irda/scene/irda_app_scene_remote.cpp => infrared/scene/infrared_app_scene_remote.cpp} (54%) rename applications/{irda/scene/irda_app_scene_remote_list.cpp => infrared/scene/infrared_app_scene_remote_list.cpp} (57%) rename applications/{irda/scene/irda_app_scene_start.cpp => infrared/scene/infrared_app_scene_start.cpp} (62%) rename applications/{irda/scene/irda_app_scene_universal.cpp => infrared/scene/infrared_app_scene_universal.cpp} (52%) create mode 100644 applications/infrared/scene/infrared_app_scene_universal_common.cpp rename applications/{irda/scene/irda_app_scene_universal_tv.cpp => infrared/scene/infrared_app_scene_universal_tv.cpp} (80%) rename applications/{irda/view/irda_progress_view.c => infrared/view/infrared_progress_view.c} (62%) create mode 100644 applications/infrared/view/infrared_progress_view.h create mode 100644 applications/infrared_monitor/infrared_monitor.c delete mode 100644 applications/irda/helpers/irda_parser.h delete mode 100644 applications/irda/irda_app.h delete mode 100644 applications/irda/irda_app_brute_force.h delete mode 100644 applications/irda/irda_app_event.h delete mode 100644 applications/irda/irda_app_remote_manager.h delete mode 100644 applications/irda/irda_app_signal.h delete mode 100644 applications/irda/irda_app_view_manager.h delete mode 100644 applications/irda/irda_runner.cpp delete mode 100644 applications/irda/scene/irda_app_scene.h delete mode 100644 applications/irda/scene/irda_app_scene_edit_delete_done.cpp delete mode 100644 applications/irda/scene/irda_app_scene_edit_key_select.cpp delete mode 100644 applications/irda/scene/irda_app_scene_edit_rename_done.cpp delete mode 100644 applications/irda/scene/irda_app_scene_learn.cpp delete mode 100644 applications/irda/scene/irda_app_scene_universal_common.cpp delete mode 100644 applications/irda/view/irda_progress_view.h delete mode 100644 applications/irda_monitor/irda_monitor.c rename applications/tests/{irda_decoder_encoder/irda_decoder_encoder_test.c => infrared_decoder_encoder/infrared_decoder_encoder_test.c} (77%) rename applications/tests/{irda_decoder_encoder/test_data/irda_nec_test_data.srcdata => infrared_decoder_encoder/test_data/infrared_nec_test_data.srcdata} (66%) rename applications/tests/{irda_decoder_encoder/test_data/irda_necext_test_data.srcdata => infrared_decoder_encoder/test_data/infrared_necext_test_data.srcdata} (56%) rename applications/tests/{irda_decoder_encoder/test_data/irda_rc5_test_data.srcdata => infrared_decoder_encoder/test_data/infrared_rc5_test_data.srcdata} (56%) rename applications/tests/{irda_decoder_encoder/test_data/irda_rc6_test_data.srcdata => infrared_decoder_encoder/test_data/infrared_rc6_test_data.srcdata} (65%) rename applications/tests/{irda_decoder_encoder/test_data/irda_samsung_test_data.srcdata => infrared_decoder_encoder/test_data/infrared_samsung_test_data.srcdata} (71%) rename applications/tests/{irda_decoder_encoder/test_data/irda_sirc_test_data.srcdata => infrared_decoder_encoder/test_data/infrared_sirc_test_data.srcdata} (75%) rename assets/icons/{Irda => Infrared}/Back_15x10.png (100%) rename assets/icons/{Irda => Infrared}/DolphinReadingSuccess_59x63.png (100%) rename assets/icons/{Irda => Infrared}/Down_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Down_hvr_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Fill-marker_7x7.png (100%) rename assets/icons/{Irda/IrdaArrowDown_4x8.png => Infrared/InfraredArrowDown_4x8.png} (100%) rename assets/icons/{Irda/IrdaArrowUp_4x8.png => Infrared/InfraredArrowUp_4x8.png} (100%) rename assets/icons/{Irda/IrdaLearnShort_128x31.png => Infrared/InfraredLearnShort_128x31.png} (100%) rename assets/icons/{Irda/IrdaLearn_128x64.png => Infrared/InfraredLearn_128x64.png} (100%) rename assets/icons/{Irda/IrdaSendShort_128x34.png => Infrared/InfraredSendShort_128x34.png} (100%) rename assets/icons/{Irda/IrdaSend_128x64.png => Infrared/InfraredSend_128x64.png} (100%) rename assets/icons/{Irda => Infrared}/Mute_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Mute_hvr_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Power_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Power_hvr_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Up_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Up_hvr_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Vol_down_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Vol_down_hvr_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Vol_up_25x27.png (100%) rename assets/icons/{Irda => Infrared}/Vol_up_hvr_25x27.png (100%) rename assets/resources/{irda => infrared}/assets/tv.ir (100%) rename firmware/targets/f6/furi_hal/{furi_hal_irda.c => furi_hal_infrared.c} (50%) rename firmware/targets/f7/furi_hal/{furi_hal_irda.c => furi_hal_infrared.c} (50%) create mode 100644 firmware/targets/furi_hal_include/furi_hal_infrared.h delete mode 100644 firmware/targets/furi_hal_include/furi_hal_irda.h rename lib/{irda/encoder_decoder/common/irda_common_decoder.c => infrared/encoder_decoder/common/infrared_common_decoder.c} (70%) rename lib/{irda/encoder_decoder/common/irda_common_encoder.c => infrared/encoder_decoder/common/infrared_common_encoder.c} (64%) create mode 100644 lib/infrared/encoder_decoder/common/infrared_common_i.h create mode 100644 lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c create mode 100644 lib/infrared/encoder_decoder/infrared.c create mode 100644 lib/infrared/encoder_decoder/infrared.h rename lib/{irda/encoder_decoder/irda_i.h => infrared/encoder_decoder/infrared_i.h} (53%) create mode 100644 lib/infrared/encoder_decoder/infrared_protocol_defs_i.h rename lib/{irda/encoder_decoder/nec/irda_decoder_nec.c => infrared/encoder_decoder/nec/infrared_decoder_nec.c} (58%) rename lib/{irda/encoder_decoder/nec/irda_encoder_nec.c => infrared/encoder_decoder/nec/infrared_encoder_nec.c} (58%) create mode 100644 lib/infrared/encoder_decoder/nec/infrared_nec_spec.c create mode 100644 lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c create mode 100644 lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c create mode 100644 lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c rename lib/{irda/encoder_decoder/rc6/irda_decoder_rc6.c => infrared/encoder_decoder/rc6/infrared_decoder_rc6.c} (56%) create mode 100644 lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c create mode 100644 lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c create mode 100644 lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c rename lib/{irda/encoder_decoder/samsung/irda_encoder_samsung.c => infrared/encoder_decoder/samsung/infrared_encoder_samsung.c} (50%) create mode 100644 lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c create mode 100644 lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c create mode 100644 lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c create mode 100644 lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c create mode 100644 lib/infrared/worker/infrared_transmit.c rename lib/{irda/worker/irda_transmit.h => infrared/worker/infrared_transmit.h} (78%) create mode 100644 lib/infrared/worker/infrared_worker.c create mode 100644 lib/infrared/worker/infrared_worker.h delete mode 100644 lib/irda/encoder_decoder/common/irda_common_i.h delete mode 100644 lib/irda/encoder_decoder/common/irda_common_protocol_defs.c delete mode 100644 lib/irda/encoder_decoder/irda.c delete mode 100644 lib/irda/encoder_decoder/irda.h delete mode 100644 lib/irda/encoder_decoder/irda_protocol_defs_i.h delete mode 100644 lib/irda/encoder_decoder/nec/irda_nec_spec.c delete mode 100644 lib/irda/encoder_decoder/rc5/irda_decoder_rc5.c delete mode 100644 lib/irda/encoder_decoder/rc5/irda_encoder_rc5.c delete mode 100644 lib/irda/encoder_decoder/rc5/irda_rc5_spec.c delete mode 100644 lib/irda/encoder_decoder/rc6/irda_encoder_rc6.c delete mode 100644 lib/irda/encoder_decoder/rc6/irda_rc6_spec.c delete mode 100644 lib/irda/encoder_decoder/samsung/irda_decoder_samsung.c delete mode 100644 lib/irda/encoder_decoder/samsung/irda_samsung_spec.c delete mode 100644 lib/irda/encoder_decoder/sirc/irda_decoder_sirc.c delete mode 100644 lib/irda/encoder_decoder/sirc/irda_encoder_sirc.c delete mode 100644 lib/irda/encoder_decoder/sirc/irda_sirc_spec.c delete mode 100644 lib/irda/worker/irda_transmit.c delete mode 100644 lib/irda/worker/irda_worker.c delete mode 100644 lib/irda/worker/irda_worker.h diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c8490d23..2c82c7d4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,7 +14,7 @@ applications/gui/** @skotopes @DrZlo13 applications/gui-test/** @skotopes @DrZlo13 applications/ibutton/** @skotopes @DrZlo13 applications/input/** @skotopes @DrZlo13 -applications/irda/** @skotopes @DrZlo13 @albkharisov +applications/infrared/** @skotopes @DrZlo13 applications/lf-rfid/** @skotopes @DrZlo13 applications/menu/** @skotopes @DrZlo13 applications/music-player/** @skotopes @DrZlo13 diff --git a/Makefile b/Makefile index 7e0104a6..6c01e76e 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ PROJECT_SOURCE_DIRECTORIES := \ $(PROJECT_ROOT)/lib/cyfral \ $(PROJECT_ROOT)/lib/drivers \ $(PROJECT_ROOT)/lib/flipper_file \ - $(PROJECT_ROOT)/lib/irda \ + $(PROJECT_ROOT)/lib/infrared \ $(PROJECT_ROOT)/lib/nfc_protocols \ $(PROJECT_ROOT)/lib/ST25RFAL002 \ $(PROJECT_ROOT)/lib/onewire \ diff --git a/applications/ReadMe.md b/applications/ReadMe.md index 022092c6..a1fbb5d6 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -15,8 +15,8 @@ - `gui` - GUI service and API - `ibutton` - iButton application, onewire keys and more - `input` - Input service -- `irda` - Irda application, controls your IR devices -- `irda_monitor` - Irda debug tool +- `infrared` - Infrared application, controls your IR devices +- `infrared_monitor` - Infrared debug tool - `lfrfid` - LF RFID application - `lfrfid_debug` - LF RFID debug tool - `loader` - Application loader service diff --git a/applications/applications.c b/applications/applications.c index 8153dcfa..b4df86a9 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -27,8 +27,8 @@ extern int32_t delay_test_app(void* p); extern int32_t display_test_app(void* p); extern int32_t gpio_app(void* p); extern int32_t ibutton_app(void* p); -extern int32_t irda_app(void* p); -extern int32_t irda_monitor_app(void* p); +extern int32_t infrared_app(void* p); +extern int32_t infrared_monitor_app(void* p); extern int32_t keypad_test_app(void* p); extern int32_t lfrfid_app(void* p); extern int32_t lfrfid_debug_app(void* p); @@ -51,7 +51,7 @@ extern int32_t snake_game_app(void* p); extern void bt_on_system_start(); extern void crypto_on_system_start(); extern void ibutton_on_system_start(); -extern void irda_on_system_start(); +extern void infrared_on_system_start(); extern void lfrfid_on_system_start(); extern void nfc_on_system_start(); extern void storage_on_system_start(); @@ -136,8 +136,8 @@ const FlipperApplication FLIPPER_APPS[] = { {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, #endif -#ifdef APP_IRDA - {.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, +#ifdef APP_INFRARED + {.app = infrared_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, #endif #ifdef APP_GPIO @@ -164,8 +164,8 @@ const size_t FLIPPER_APPS_COUNT = sizeof(FLIPPER_APPS) / sizeof(FlipperApplicati const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { crypto_on_system_start, -#ifdef APP_IRDA - irda_on_system_start, +#ifdef APP_INFRARED + infrared_on_system_start, #endif #ifdef APP_NFC @@ -251,8 +251,8 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { {.app = uart_echo_app, .name = "Uart Echo", .stack_size = 2048, .icon = NULL}, #endif -#ifdef APP_IRDA_MONITOR - {.app = irda_monitor_app, .name = "Irda Monitor", .stack_size = 1024, .icon = NULL}, +#ifdef APP_INFRARED_MONITOR + {.app = infrared_monitor_app, .name = "Infrared Monitor", .stack_size = 1024, .icon = NULL}, #endif #ifdef APP_SCENED diff --git a/applications/applications.mk b/applications/applications.mk index 5733e761..954ec647 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -26,7 +26,7 @@ SRV_DESKTOP = 1 APP_ARCHIVE = 1 APP_GPIO = 1 APP_IBUTTON = 1 -APP_IRDA = 1 +APP_INFRARED = 1 APP_LF_RFID = 1 APP_NFC = 1 APP_SUBGHZ = 1 @@ -40,7 +40,7 @@ APP_SNAKE_GAME = 1 # Debug APP_ACCESSOR = 1 APP_BLINK = 1 -APP_IRDA_MONITOR = 1 +APP_INFRARED_MONITOR = 1 APP_KEYPAD_TEST = 1 APP_SD_TEST = 1 APP_VIBRO_TEST = 1 @@ -59,9 +59,9 @@ endif # Prefix with APP_* -APP_IRDA_MONITOR ?= 0 -ifeq ($(APP_IRDA_MONITOR), 1) -CFLAGS += -DAPP_IRDA_MONITOR +APP_INFRARED_MONITOR ?= 0 +ifeq ($(APP_INFRARED_MONITOR), 1) +CFLAGS += -DAPP_INFRARED_MONITOR SRV_GUI = 1 endif @@ -122,9 +122,9 @@ SRV_GUI = 1 endif -APP_IRDA ?= 0 -ifeq ($(APP_IRDA), 1) -CFLAGS += -DAPP_IRDA +APP_INFRARED ?= 0 +ifeq ($(APP_INFRARED), 1) +CFLAGS += -DAPP_INFRARED SRV_GUI = 1 endif diff --git a/applications/archive/helpers/archive_browser.h b/applications/archive/helpers/archive_browser.h index 6486967f..aa42a34a 100644 --- a/applications/archive/helpers/archive_browser.h +++ b/applications/archive/helpers/archive_browser.h @@ -10,7 +10,7 @@ static const char* tab_default_paths[] = { [ArchiveTabNFC] = "/any/nfc", [ArchiveTabSubGhz] = "/any/subghz", [ArchiveTabLFRFID] = "/any/lfrfid", - [ArchiveTabIrda] = "/any/irda", + [ArchiveTabInfrared] = "/any/infrared", [ArchiveTabBadUsb] = "/any/badusb", [ArchiveTabU2f] = "/app:u2f", [ArchiveTabBrowser] = "/any", @@ -21,7 +21,7 @@ static const char* known_ext[] = { [ArchiveFileTypeNFC] = ".nfc", [ArchiveFileTypeSubGhz] = ".sub", [ArchiveFileTypeLFRFID] = ".rfid", - [ArchiveFileTypeIrda] = ".ir", + [ArchiveFileTypeInfrared] = ".ir", [ArchiveFileTypeBadUsb] = ".txt", [ArchiveFileTypeU2f] = "?", [ArchiveFileTypeFolder] = "?", @@ -34,7 +34,7 @@ static const ArchiveFileTypeEnum known_type[] = { [ArchiveTabNFC] = ArchiveFileTypeNFC, [ArchiveTabSubGhz] = ArchiveFileTypeSubGhz, [ArchiveTabLFRFID] = ArchiveFileTypeLFRFID, - [ArchiveTabIrda] = ArchiveFileTypeIrda, + [ArchiveTabInfrared] = ArchiveFileTypeInfrared, [ArchiveTabBadUsb] = ArchiveFileTypeBadUsb, [ArchiveTabU2f] = ArchiveFileTypeU2f, [ArchiveTabBrowser] = ArchiveFileTypeUnknown, diff --git a/applications/archive/helpers/archive_files.h b/applications/archive/helpers/archive_files.h index ff751d9b..c8b16c53 100644 --- a/applications/archive/helpers/archive_files.h +++ b/applications/archive/helpers/archive_files.h @@ -8,7 +8,7 @@ typedef enum { ArchiveFileTypeNFC, ArchiveFileTypeSubGhz, ArchiveFileTypeLFRFID, - ArchiveFileTypeIrda, + ArchiveFileTypeInfrared, ArchiveFileTypeBadUsb, ArchiveFileTypeU2f, ArchiveFileTypeFolder, diff --git a/applications/archive/scenes/archive_scene_browser.c b/applications/archive/scenes/archive_scene_browser.c index 228bb305..cee19168 100644 --- a/applications/archive/scenes/archive_scene_browser.c +++ b/applications/archive/scenes/archive_scene_browser.c @@ -12,7 +12,7 @@ static const char* flipper_app_name[] = { [ArchiveFileTypeNFC] = "NFC", [ArchiveFileTypeSubGhz] = "Sub-GHz", [ArchiveFileTypeLFRFID] = "125 kHz RFID", - [ArchiveFileTypeIrda] = "Infrared", + [ArchiveFileTypeInfrared] = "Infrared", [ArchiveFileTypeBadUsb] = "Bad USB", [ArchiveFileTypeU2f] = "U2F", }; diff --git a/applications/archive/views/archive_browser_view.c b/applications/archive/views/archive_browser_view.c index 67a4ce02..fd81124e 100644 --- a/applications/archive/views/archive_browser_view.c +++ b/applications/archive/views/archive_browser_view.c @@ -9,7 +9,7 @@ static const char* ArchiveTabNames[] = { [ArchiveTabNFC] = "NFC", [ArchiveTabSubGhz] = "Sub-GHz", [ArchiveTabLFRFID] = "RFID LF", - [ArchiveTabIrda] = "Infrared", + [ArchiveTabInfrared] = "Infrared", [ArchiveTabBadUsb] = "Bad USB", [ArchiveTabU2f] = "U2F", [ArchiveTabBrowser] = "Browser"}; @@ -19,7 +19,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeNFC] = &I_Nfc_10px, [ArchiveFileTypeSubGhz] = &I_sub1_10px, [ArchiveFileTypeLFRFID] = &I_125_10px, - [ArchiveFileTypeIrda] = &I_ir_10px, + [ArchiveFileTypeInfrared] = &I_ir_10px, [ArchiveFileTypeBadUsb] = &I_badusb_10px, [ArchiveFileTypeU2f] = &I_u2f_10px, [ArchiveFileTypeFolder] = &I_dir_10px, diff --git a/applications/archive/views/archive_browser_view.h b/applications/archive/views/archive_browser_view.h index af4ed85e..9fc2cdf0 100644 --- a/applications/archive/views/archive_browser_view.h +++ b/applications/archive/views/archive_browser_view.h @@ -22,7 +22,7 @@ typedef enum { ArchiveTabSubGhz, ArchiveTabLFRFID, ArchiveTabNFC, - ArchiveTabIrda, + ArchiveTabInfrared, ArchiveTabIButton, ArchiveTabBadUsb, ArchiveTabU2f, diff --git a/applications/gui/modules/button_menu.c b/applications/gui/modules/button_menu.c index 0f379b74..8f38efc4 100644 --- a/applications/gui/modules/button_menu.c +++ b/applications/gui/modules/button_menu.c @@ -108,11 +108,11 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { ButtonMenuItemArray_it_t it; if(active_screen > 0) { - canvas_draw_icon(canvas, 28, 1, &I_IrdaArrowUp_4x8); + canvas_draw_icon(canvas, 28, 1, &I_InfraredArrowUp_4x8); } if(max_screen > active_screen) { - canvas_draw_icon(canvas, 28, 123, &I_IrdaArrowDown_4x8); + canvas_draw_icon(canvas, 28, 123, &I_InfraredArrowDown_4x8); } if(model->header) { diff --git a/applications/irda/cli/irda_cli.cpp b/applications/infrared/cli/infrared_cli.cpp similarity index 56% rename from applications/irda/cli/irda_cli.cpp rename to applications/infrared/cli/infrared_cli.cpp index e8a61dc3..0613ca51 100644 --- a/applications/irda/cli/irda_cli.cpp +++ b/applications/infrared/cli/infrared_cli.cpp @@ -1,52 +1,52 @@ #include -#include +#include #include #include #include -#include +#include #include -#include +#include #include #include #include -#include +#include #include -#include "../helpers/irda_parser.h" +#include "../helpers/infrared_parser.h" -static void irda_cli_start_ir_rx(Cli* cli, string_t args); -static void irda_cli_start_ir_tx(Cli* cli, string_t args); +static void infrared_cli_start_ir_rx(Cli* cli, string_t args); +static void infrared_cli_start_ir_tx(Cli* cli, string_t args); static const struct { const char* cmd; void (*process_function)(Cli* cli, string_t args); -} irda_cli_commands[] = { - {.cmd = "rx", .process_function = irda_cli_start_ir_rx}, - {.cmd = "tx", .process_function = irda_cli_start_ir_tx}, +} infrared_cli_commands[] = { + {.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, + {.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, }; -static void signal_received_callback(void* context, IrdaWorkerSignal* received_signal) { +static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { furi_assert(received_signal); char buf[100]; size_t buf_cnt; Cli* cli = (Cli*)context; - if(irda_worker_signal_is_decoded(received_signal)) { - const IrdaMessage* message = irda_worker_get_decoded_signal(received_signal); + if(infrared_worker_signal_is_decoded(received_signal)) { + const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); buf_cnt = sniprintf( buf, sizeof(buf), "%s, A:0x%0*lX, C:0x%0*lX%s\r\n", - irda_get_protocol_name(message->protocol), - ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), + infrared_get_protocol_name(message->protocol), + ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), message->address, - ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), message->command, message->repeat ? " R" : ""); cli_write(cli, (uint8_t*)buf, buf_cnt); } else { const uint32_t* timings; size_t timings_cnt; - irda_worker_get_raw_signal(received_signal, &timings, &timings_cnt); + infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); buf_cnt = sniprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt); cli_write(cli, (uint8_t*)buf, buf_cnt); @@ -59,39 +59,39 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s } } -static void irda_cli_start_ir_rx(Cli* cli, string_t args) { - IrdaWorker* worker = irda_worker_alloc(); - irda_worker_rx_start(worker); - irda_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli); +static void infrared_cli_start_ir_rx(Cli* cli, string_t args) { + InfraredWorker* worker = infrared_worker_alloc(); + infrared_worker_rx_start(worker); + infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli); - printf("Receiving IRDA...\r\nPress Ctrl+C to abort\r\n"); + printf("Receiving INFRARED...\r\nPress Ctrl+C to abort\r\n"); while(!cli_cmd_interrupt_received(cli)) { delay(50); } - irda_worker_rx_stop(worker); - irda_worker_free(worker); + infrared_worker_rx_stop(worker); + infrared_worker_free(worker); } -static void irda_cli_print_usage(void) { +static void infrared_cli_print_usage(void) { printf("Usage:\r\n"); printf("\tir rx\r\n"); printf("\tir tx
\r\n"); printf("\t and
are hex-formatted\r\n"); printf("\tAvailable protocols:"); - for(int i = 0; irda_is_protocol_valid((IrdaProtocol)i); ++i) { - printf(" %s", irda_get_protocol_name((IrdaProtocol)i)); + for(int i = 0; infrared_is_protocol_valid((InfraredProtocol)i); ++i) { + printf(" %s", infrared_get_protocol_name((InfraredProtocol)i)); } printf("\r\n"); printf("\tRaw format:\r\n"); printf("\tir_tx RAW F: DC: ...\r\n"); printf( "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n", - IRDA_MIN_FREQUENCY, - IRDA_MAX_FREQUENCY); + INFRARED_MIN_FREQUENCY, + INFRARED_MAX_FREQUENCY); } -static bool parse_message(const char* str, IrdaMessage* message) { +static bool parse_message(const char* str, InfraredMessage* message) { char protocol_name[32]; int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message->address, &message->command); @@ -99,10 +99,10 @@ static bool parse_message(const char* str, IrdaMessage* message) { return false; } - message->protocol = irda_get_protocol_by_name(protocol_name); + message->protocol = infrared_get_protocol_by_name(protocol_name); message->repeat = false; - return irda_parser_is_parsed_signal_valid(message); + return infrared_parser_is_parsed_signal_valid(message); } static bool parse_signal_raw( @@ -136,11 +136,11 @@ static bool parse_signal_raw( ++*timings_cnt; } - return irda_parser_is_raw_signal_valid(*frequency, *duty_cycle, *timings_cnt); + return infrared_parser_is_raw_signal_valid(*frequency, *duty_cycle, *timings_cnt); } -static void irda_cli_start_ir_tx(Cli* cli, string_t args) { - IrdaMessage message; +static void infrared_cli_start_ir_tx(Cli* cli, string_t args) { + InfraredMessage message; const char* str = string_get_cstr(args); uint32_t frequency; float duty_cycle; @@ -148,27 +148,27 @@ static void irda_cli_start_ir_tx(Cli* cli, string_t args) { uint32_t* timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt); if(parse_message(str, &message)) { - irda_send(&message, 1); + infrared_send(&message, 1); } else if(parse_signal_raw(str, timings, &timings_cnt, &duty_cycle, &frequency)) { - irda_send_raw_ext(timings, timings_cnt, true, frequency, duty_cycle); + infrared_send_raw_ext(timings, timings_cnt, true, frequency, duty_cycle); } else { printf("Wrong arguments.\r\n"); - irda_cli_print_usage(); + infrared_cli_print_usage(); } free(timings); } -static void irda_cli_start_ir(Cli* cli, string_t args, void* context) { - if(furi_hal_irda_is_busy()) { - printf("IRDA is busy. Exit."); +static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { + if(furi_hal_infrared_is_busy()) { + printf("INFRARED is busy. Exit."); return; } size_t i = 0; - for(; i < COUNT_OF(irda_cli_commands); ++i) { - size_t size = strlen(irda_cli_commands[i].cmd); - bool cmd_found = !strncmp(string_get_cstr(args), irda_cli_commands[i].cmd, size); + for(; i < COUNT_OF(infrared_cli_commands); ++i) { + size_t size = strlen(infrared_cli_commands[i].cmd); + bool cmd_found = !strncmp(string_get_cstr(args), infrared_cli_commands[i].cmd, size); if(cmd_found) { if(string_size(args) == size) { break; @@ -180,17 +180,17 @@ static void irda_cli_start_ir(Cli* cli, string_t args, void* context) { } } - if(i < COUNT_OF(irda_cli_commands)) { - irda_cli_commands[i].process_function(cli, args); + if(i < COUNT_OF(infrared_cli_commands)) { + infrared_cli_commands[i].process_function(cli, args); } else { - irda_cli_print_usage(); + infrared_cli_print_usage(); } } -extern "C" void irda_on_system_start() { +extern "C" void infrared_on_system_start() { #ifdef SRV_CLI Cli* cli = (Cli*)furi_record_open("cli"); - cli_add_command(cli, "ir", CliCommandFlagDefault, irda_cli_start_ir, NULL); + cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL); furi_record_close("cli"); #endif } diff --git a/applications/irda/helpers/irda_parser.cpp b/applications/infrared/helpers/infrared_parser.cpp similarity index 78% rename from applications/irda/helpers/irda_parser.cpp rename to applications/infrared/helpers/infrared_parser.cpp index a23a9b6d..12a93924 100644 --- a/applications/irda/helpers/irda_parser.cpp +++ b/applications/infrared/helpers/infrared_parser.cpp @@ -1,19 +1,19 @@ -#include "../irda_app_signal.h" -#include "irda.h" -#include "irda/helpers/irda_parser.h" -#include "irda_worker.h" +#include "../infrared_app_signal.h" +#include "infrared.h" +#include "infrared/helpers/infrared_parser.h" +#include "infrared_worker.h" #include "m-string.h" #include #include #include -#include +#include -#define TAG "IrdaParser" +#define TAG "InfraredParser" -bool irda_parser_save_signal( +bool infrared_parser_save_signal( FlipperFormat* ff, - const IrdaAppSignal& signal, + const InfraredAppSignal& signal, const std::string& name) { furi_assert(ff); furi_assert(!name.empty()); @@ -33,7 +33,7 @@ bool irda_parser_save_signal( break; } else { auto parsed_signal = signal.get_message(); - const char* protocol_name = irda_get_protocol_name(parsed_signal.protocol); + const char* protocol_name = infrared_get_protocol_name(parsed_signal.protocol); if(!flipper_format_write_string_cstr(ff, "type", "parsed")) break; if(!flipper_format_write_string_cstr(ff, "protocol", protocol_name)) break; if(!flipper_format_write_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4)) @@ -47,7 +47,7 @@ bool irda_parser_save_signal( return result; } -bool irda_parser_read_signal(FlipperFormat* ff, IrdaAppSignal& signal, std::string& name) { +bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name) { furi_assert(ff); bool result = false; @@ -75,12 +75,12 @@ bool irda_parser_read_signal(FlipperFormat* ff, IrdaAppSignal& signal, std::stri } free(timings); } else if(!string_cmp_str(read_string, "parsed")) { - IrdaMessage parsed_signal; + InfraredMessage parsed_signal; if(!flipper_format_read_string(ff, "protocol", read_string)) break; - parsed_signal.protocol = irda_get_protocol_by_name(string_get_cstr(read_string)); + parsed_signal.protocol = infrared_get_protocol_by_name(string_get_cstr(read_string)); if(!flipper_format_read_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4)) break; if(!flipper_format_read_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4)) break; - if(!irda_parser_is_parsed_signal_valid(&parsed_signal)) break; + if(!infrared_parser_is_parsed_signal_valid(&parsed_signal)) break; signal.set_message(&parsed_signal); result = true; } else { @@ -92,17 +92,17 @@ bool irda_parser_read_signal(FlipperFormat* ff, IrdaAppSignal& signal, std::stri return result; } -bool irda_parser_is_parsed_signal_valid(const IrdaMessage* signal) { +bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal) { furi_assert(signal); bool result = true; - if(!irda_is_protocol_valid(signal->protocol)) { + if(!infrared_is_protocol_valid(signal->protocol)) { FURI_LOG_E(TAG, "Unknown protocol"); result = false; } if(result) { - uint32_t address_length = irda_get_protocol_address_length(signal->protocol); + uint32_t address_length = infrared_get_protocol_address_length(signal->protocol); uint32_t address_mask = (1LU << address_length) - 1; if(signal->address != (signal->address & address_mask)) { FURI_LOG_E( @@ -115,7 +115,7 @@ bool irda_parser_is_parsed_signal_valid(const IrdaMessage* signal) { } if(result) { - uint32_t command_length = irda_get_protocol_command_length(signal->protocol); + uint32_t command_length = infrared_get_protocol_command_length(signal->protocol); uint32_t command_mask = (1LU << command_length) - 1; if(signal->command != (signal->command & command_mask)) { FURI_LOG_E( @@ -130,15 +130,18 @@ bool irda_parser_is_parsed_signal_valid(const IrdaMessage* signal) { return result; } -bool irda_parser_is_raw_signal_valid(uint32_t frequency, float duty_cycle, uint32_t timings_cnt) { +bool infrared_parser_is_raw_signal_valid( + uint32_t frequency, + float duty_cycle, + uint32_t timings_cnt) { bool result = true; - if((frequency > IRDA_MAX_FREQUENCY) || (frequency < IRDA_MIN_FREQUENCY)) { + if((frequency > INFRARED_MAX_FREQUENCY) || (frequency < INFRARED_MIN_FREQUENCY)) { FURI_LOG_E( TAG, "Frequency is out of range (%lX - %lX): %lX", - IRDA_MIN_FREQUENCY, - IRDA_MAX_FREQUENCY, + INFRARED_MIN_FREQUENCY, + INFRARED_MAX_FREQUENCY, frequency); result = false; } else if((duty_cycle <= 0) || (duty_cycle > 1)) { diff --git a/applications/infrared/helpers/infrared_parser.h b/applications/infrared/helpers/infrared_parser.h new file mode 100644 index 00000000..2e790c38 --- /dev/null +++ b/applications/infrared/helpers/infrared_parser.h @@ -0,0 +1,48 @@ +/** + * @file infrared_parser.h + * Infrared: Helper file for conversion Flipper File Format + * to Infrared signal class, and backwards + */ +#pragma once + +#include "../infrared_app_signal.h" +#include +#include + +/** Save Infrared signal into file + * + * @param ff - Flipper File Format instance + * @param signal - Infrared signal to save + * @param name - name for saved signal. Every + * signal on disk has name. + */ +bool infrared_parser_save_signal( + FlipperFormat* ff, + const InfraredAppSignal& signal, + const std::string& name); + +/** Read Infrared signal from file + * + * @param ff - Flipper File Format instance + * @param signal - Infrared signal to read to + * @param name - name for saved signal. Every + * signal in file has name. + */ +bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name); + +/** Validate parsed signal + * + * @signal - signal to validate + * @retval true if valid, false otherwise + */ +bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal); + +/** Validate raw signal + * + * @signal - signal to validate + * @retval true if valid, false otherwise + */ +bool infrared_parser_is_raw_signal_valid( + uint32_t frequency, + float duty_cycle, + uint32_t timings_cnt); diff --git a/applications/irda/irda_app.cpp b/applications/infrared/infrared_app.cpp similarity index 61% rename from applications/irda/irda_app.cpp rename to applications/infrared/infrared_app.cpp index 184ed34e..5859682d 100644 --- a/applications/irda/irda_app.cpp +++ b/applications/infrared/infrared_app.cpp @@ -1,13 +1,13 @@ -#include "irda_app.h" -#include +#include "infrared_app.h" +#include #include #include #include #include #include -int32_t IrdaApp::run(void* args) { - IrdaAppEvent event; +int32_t InfraredApp::run(void* args) { + InfraredAppEvent event; bool consumed; bool exit = false; @@ -17,7 +17,7 @@ int32_t IrdaApp::run(void* args) { remote_name.erase(remote_name.find_last_of('.')); bool result = remote_manager.load(remote_name); if(result) { - current_scene = IrdaApp::Scene::Remote; + current_scene = InfraredApp::Scene::Remote; } else { printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); return -1; @@ -29,12 +29,12 @@ int32_t IrdaApp::run(void* args) { while(!exit) { view_manager.receive_event(&event); - if(event.type == IrdaAppEvent::Type::Exit) break; + if(event.type == InfraredAppEvent::Type::Exit) break; consumed = scenes[current_scene]->on_event(this, &event); if(!consumed) { - if(event.type == IrdaAppEvent::Type::Back) { + if(event.type == InfraredAppEvent::Type::Back) { exit = switch_to_previous_scene(); } } @@ -45,36 +45,36 @@ int32_t IrdaApp::run(void* args) { return 0; }; -IrdaApp::IrdaApp() { - furi_check(IrdaAppRemoteManager::max_button_name_length < get_text_store_size()); +InfraredApp::InfraredApp() { + furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); notification = static_cast(furi_record_open("notification")); - irda_worker = irda_worker_alloc(); + infrared_worker = infrared_worker_alloc(); } -IrdaApp::~IrdaApp() { - irda_worker_free(irda_worker); +InfraredApp::~InfraredApp() { + infrared_worker_free(infrared_worker); furi_record_close("notification"); for(auto& [key, scene] : scenes) delete scene; } -IrdaAppViewManager* IrdaApp::get_view_manager() { +InfraredAppViewManager* InfraredApp::get_view_manager() { return &view_manager; } -void IrdaApp::set_learn_new_remote(bool value) { +void InfraredApp::set_learn_new_remote(bool value) { learn_new_remote = value; } -bool IrdaApp::get_learn_new_remote() { +bool InfraredApp::get_learn_new_remote() { return learn_new_remote; } -void IrdaApp::switch_to_next_scene(Scene next_scene) { +void InfraredApp::switch_to_next_scene(Scene next_scene) { previous_scenes_list.push_front(current_scene); switch_to_next_scene_without_saving(next_scene); } -void IrdaApp::switch_to_next_scene_without_saving(Scene next_scene) { +void InfraredApp::switch_to_next_scene_without_saving(Scene next_scene) { if(next_scene != Scene::Exit) { scenes[current_scene]->on_exit(this); current_scene = next_scene; @@ -83,7 +83,8 @@ void IrdaApp::switch_to_next_scene_without_saving(Scene next_scene) { } } -void IrdaApp::search_and_switch_to_previous_scene(const std::initializer_list& scenes_list) { +void InfraredApp::search_and_switch_to_previous_scene( + const std::initializer_list& scenes_list) { Scene previous_scene = Scene::Start; bool scene_found = false; @@ -101,8 +102,8 @@ void IrdaApp::search_and_switch_to_previous_scene(const std::initializer_liston_exit(this); @@ -112,7 +113,7 @@ void IrdaApp::search_and_switch_to_previous_scene(const std::initializer_list(context); - IrdaAppEvent event; - event.type = IrdaAppEvent::Type::TextEditDone; +void InfraredApp::text_input_callback(void* context) { + InfraredApp* app = static_cast(context); + InfraredAppEvent event; + event.type = InfraredAppEvent::Type::TextEditDone; app->get_view_manager()->send_event(&event); } -void IrdaApp::popup_callback(void* context) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; - event.type = IrdaAppEvent::Type::PopupTimer; +void InfraredApp::popup_callback(void* context) { + InfraredApp* app = static_cast(context); + InfraredAppEvent event; + event.type = InfraredAppEvent::Type::PopupTimer; app->get_view_manager()->send_event(&event); } -void IrdaApp::set_edit_element(IrdaApp::EditElement value) { +void InfraredApp::set_edit_element(InfraredApp::EditElement value) { element = value; } -IrdaApp::EditElement IrdaApp::get_edit_element(void) { +InfraredApp::EditElement InfraredApp::get_edit_element(void) { return element; } -void IrdaApp::set_edit_action(IrdaApp::EditAction value) { +void InfraredApp::set_edit_action(InfraredApp::EditAction value) { action = value; } -IrdaApp::EditAction IrdaApp::get_edit_action(void) { +InfraredApp::EditAction InfraredApp::get_edit_action(void) { return action; } -void IrdaApp::set_current_button(int value) { +void InfraredApp::set_current_button(int value) { current_button = value; } -int IrdaApp::get_current_button() { +int InfraredApp::get_current_button() { return current_button; } -void IrdaApp::notify_success() { +void InfraredApp::notify_success() { notification_message(notification, &sequence_success); } -void IrdaApp::notify_red_blink() { +void InfraredApp::notify_red_blink() { notification_message(notification, &sequence_blink_red_10); } -void IrdaApp::notify_sent_just_learnt() { - static const NotificationSequence sequence = { - &message_green_0, - &message_vibro_on, - &message_delay_50, - &message_vibro_off, - &message_green_255, - &message_do_not_reset, - NULL, - }; - - notification_message_block(notification, &sequence); -} - -void IrdaApp::notify_click() { +void InfraredApp::notify_click() { static const NotificationSequence sequence = { &message_click, &message_delay_1, @@ -233,7 +220,7 @@ void IrdaApp::notify_click() { notification_message_block(notification, &sequence); } -void IrdaApp::notify_click_and_green_blink() { +void InfraredApp::notify_click_and_green_blink() { static const NotificationSequence sequence = { &message_click, &message_delay_1, @@ -248,7 +235,7 @@ void IrdaApp::notify_click_and_green_blink() { notification_message_block(notification, &sequence); } -void IrdaApp::notify_blink_green() { +void InfraredApp::notify_blink_green() { static const NotificationSequence sequence = { &message_green_255, &message_delay_10, @@ -260,27 +247,27 @@ void IrdaApp::notify_blink_green() { notification_message(notification, &sequence); } -void IrdaApp::notify_green_on() { +void InfraredApp::notify_green_on() { notification_message(notification, &sequence_set_only_green_255); } -void IrdaApp::notify_green_off() { +void InfraredApp::notify_green_off() { notification_message(notification, &sequence_reset_green); } -IrdaWorker* IrdaApp::get_irda_worker() { - return irda_worker; +InfraredWorker* InfraredApp::get_infrared_worker() { + return infrared_worker; } -const IrdaAppSignal& IrdaApp::get_received_signal() const { +const InfraredAppSignal& InfraredApp::get_received_signal() const { return received_signal; } -void IrdaApp::set_received_signal(const IrdaAppSignal& signal) { +void InfraredApp::set_received_signal(const InfraredAppSignal& signal) { received_signal = signal; } -void IrdaApp::signal_sent_callback(void* context) { - IrdaApp* app = static_cast(context); +void InfraredApp::signal_sent_callback(void* context) { + InfraredApp* app = static_cast(context); app->notify_blink_green(); } diff --git a/applications/infrared/infrared_app.h b/applications/infrared/infrared_app.h new file mode 100644 index 00000000..39897272 --- /dev/null +++ b/applications/infrared/infrared_app.h @@ -0,0 +1,322 @@ +/** + * @file infrared_app.h + * Infrared: Main infrared application class + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "scene/infrared_app_scene.h" +#include "scene/infrared_app_scene.h" +#include "infrared_app_view_manager.h" +#include "infrared_app_remote_manager.h" +#include "infrared_app_view_manager.h" + +/** Main Infrared application class */ +class InfraredApp { +public: + /** Enum to save scene state: edit element */ + enum class EditElement : uint8_t { + Button, + Remote, + }; + /** Enum to save scene state: edit action */ + enum class EditAction : uint8_t { + Rename, + Delete, + }; + /** List of scenes for Infrared application */ + enum class Scene : uint8_t { + Exit, + Start, + Universal, + UniversalTV, + UniversalAudio, + UniversalAirConditioner, + Learn, + LearnSuccess, + LearnEnterName, + LearnDone, + AskBack, + Remote, + RemoteList, + Edit, + EditKeySelect, + EditRename, + EditDelete, + EditRenameDone, + EditDeleteDone, + }; + + /** Start application + * + * @param args - application arguments. + * Allowed argument is path to remote file. + * @retval 0 on success, error code otherwise + */ + int32_t run(void* args); + + /** Switch to next scene. Put current scene number on stack. + * Doesn't save scene state. + * + * @param index - next scene index + */ + void switch_to_next_scene(Scene index); + + /** Switch to next scene, but don't put current scene on + * stack. Thus calling switch_to_previous_scene() doesn't return + * to current scene. + * + * @param index - next scene index + */ + void switch_to_next_scene_without_saving(Scene index); + + /** Switch to previous scene. Pop scenes from stack and switch to last one. + * + * @param count - how many scenes should be popped + * @retval false on failed, true on success + */ + bool switch_to_previous_scene(uint8_t count = 1); + + /** Get previous scene in scene stack + * + * @retval previous scene + */ + Scene get_previous_scene(); + + /** Get view manager instance + * + * @retval view manager instance + */ + InfraredAppViewManager* get_view_manager(); + + /** Set one of text stores + * + * @param index - index of text store + * @param text - text to set + */ + void set_text_store(uint8_t index, const char* text...); + + /** Get value in text store + * + * @param index - index of text store + * @retval value in text_store + */ + char* get_text_store(uint8_t index); + + /** Get text store size + * + * @retval size of text store + */ + uint8_t get_text_store_size(); + + /** Get remote manager instance + * + * @retval remote manager instance + */ + InfraredAppRemoteManager* get_remote_manager(); + + /** Get infrared worker instance + * + * @retval infrared worker instance + */ + InfraredWorker* get_infrared_worker(); + + /** Get signal, previously got on Learn scene + * + * @retval received signal + */ + const InfraredAppSignal& get_received_signal() const; + + /** Set received signal + * + * @param signal - signal + */ + void set_received_signal(const InfraredAppSignal& signal); + + /** Switch to previous scene in one of provided in list. + * Pop scene stack, and find first scene from list. + * + * @param scenes_list - list of scenes + */ + void search_and_switch_to_previous_scene(const std::initializer_list& scenes_list); + + /** Set edit element value. It is used on edit scene to determine + * what should be deleted - remote or button. + * + * @param value - value to set + */ + void set_edit_element(EditElement value); + + /** Get edit element + * + * @retval edit element value + */ + EditElement get_edit_element(void); + + /** Set edit action value. It is used on edit scene to determine + * what action to perform - deletion or renaming. + * + * @param value - value to set + */ + void set_edit_action(EditAction value); + + /** Get edit action + * + * @retval edit action value + */ + EditAction get_edit_action(void); + + /** Get state of learning new signal. + * Adding new remote with 1 button from start scene and + * learning 1 additional button to remote have very similar + * flow, so they are joined. Difference in flow is handled + * by this boolean flag. + * + * @retval false if flow is in learning new remote, true if + * adding signal to created remote + * + */ + bool get_learn_new_remote(); + + /** Set state of learning new signal. + * Adding new remote with 1 button from start scene and + * learning 1 additional button to remote have very similar + * flow, so they are joined. Difference in flow is handled + * by this boolean flag. + * + * @param value - false if flow is in learning new remote, true if + * adding signal to created remote + */ + void set_learn_new_remote(bool value); + + /** Button is not assigned value + */ + enum : int { + ButtonNA = -1, + }; + + /** Get current button index + * + * @retval current button index + */ + int get_current_button(); + + /** Set current button index + * + * @param current button index + */ + void set_current_button(int value); + + /** Play success notification */ + void notify_success(); + /** Play red blink notification */ + void notify_red_blink(); + /** Light green */ + void notify_green_on(); + /** Disable green light */ + void notify_green_off(); + /** Play click sound */ + void notify_click(); + /** Play click and green notification */ + void notify_click_and_green_blink(); + /** Blink green light */ + void notify_blink_green(); + + /** Text input callback + * + * @param context - context to pass to callback + */ + static void text_input_callback(void* context); + + /** Popup callback + * + * @param context - context to pass to callback + */ + static void popup_callback(void* context); + + /** Signal sent callback + * + * @param context - context to pass to callback + */ + static void signal_sent_callback(void* context); + + /** Main class constructor, initializes all critical objects */ + InfraredApp(); + /** Main class destructor, deinitializes all critical objects */ + ~InfraredApp(); + + /** Path to Infrared directory */ + static constexpr const char* infrared_directory = "/any/infrared"; + /** Infrared files extension (remote files and universal databases) */ + static constexpr const char* infrared_extension = ".ir"; + /** Max Raw timings in signal */ + static constexpr const uint32_t max_raw_timings_in_signal = 512; + /** Max line length in Infrared file */ + static constexpr const uint32_t max_line_length = + (9 + 1) * InfraredApp::max_raw_timings_in_signal + 100; + +private: + /** Text store size */ + static constexpr const uint8_t text_store_size = 128; + /** Amount of text stores */ + static constexpr const uint8_t text_store_max = 2; + /** Store text here, for some views, because they doesn't + * hold ownership of text */ + char text_store[text_store_max][text_store_size + 1]; + /** + * Flag to control adding new signal flow. + * Adding new remote with 1 button from start scene and + * learning 1 additional button to remote have very similar + * flow, so they are joined. Difference in flow is handled + * by this boolean flag. + */ + bool learn_new_remote; + /** Value to control edit scene */ + EditElement element; + /** Value to control edit scene */ + EditAction action; + /** Selected button index */ + uint32_t current_button; + + /** Notification instance */ + NotificationApp* notification; + /** View manager instance */ + InfraredAppViewManager view_manager; + /** Remote manager instance */ + InfraredAppRemoteManager remote_manager; + /** Infrared worker instance */ + InfraredWorker* infrared_worker; + /** Signal received on Learn scene */ + InfraredAppSignal received_signal; + + /** Stack of previous scenes */ + std::forward_list previous_scenes_list; + /** Now acting scene */ + Scene current_scene = Scene::Start; + + /** Map of index/scene objects */ + std::map scenes = { + {Scene::Start, new InfraredAppSceneStart()}, + {Scene::Universal, new InfraredAppSceneUniversal()}, + {Scene::UniversalTV, new InfraredAppSceneUniversalTV()}, + {Scene::Learn, new InfraredAppSceneLearn()}, + {Scene::LearnSuccess, new InfraredAppSceneLearnSuccess()}, + {Scene::LearnEnterName, new InfraredAppSceneLearnEnterName()}, + {Scene::LearnDone, new InfraredAppSceneLearnDone()}, + {Scene::AskBack, new InfraredAppSceneAskBack()}, + {Scene::Remote, new InfraredAppSceneRemote()}, + {Scene::RemoteList, new InfraredAppSceneRemoteList()}, + {Scene::Edit, new InfraredAppSceneEdit()}, + {Scene::EditKeySelect, new InfraredAppSceneEditKeySelect()}, + {Scene::EditRename, new InfraredAppSceneEditRename()}, + {Scene::EditDelete, new InfraredAppSceneEditDelete()}, + {Scene::EditRenameDone, new InfraredAppSceneEditRenameDone()}, + {Scene::EditDeleteDone, new InfraredAppSceneEditDeleteDone()}, + }; +}; diff --git a/applications/irda/irda_app_brute_force.cpp b/applications/infrared/infrared_app_brute_force.cpp similarity index 78% rename from applications/irda/irda_app_brute_force.cpp rename to applications/infrared/infrared_app_brute_force.cpp index 8d7d684d..24be4d3e 100644 --- a/applications/irda/irda_app_brute_force.cpp +++ b/applications/infrared/infrared_app_brute_force.cpp @@ -1,18 +1,18 @@ -#include "helpers/irda_parser.h" -#include "irda_app_brute_force.h" -#include "irda_app_signal.h" +#include "helpers/infrared_parser.h" +#include "infrared_app_brute_force.h" +#include "infrared_app_signal.h" #include #include #include #include -void IrdaAppBruteForce::add_record(int index, const char* name) { +void InfraredAppBruteForce::add_record(int index, const char* name) { records[name].index = index; records[name].amount = 0; } -bool IrdaAppBruteForce::calculate_messages() { +bool InfraredAppBruteForce::calculate_messages() { bool result = false; Storage* storage = static_cast(furi_record_open("storage")); @@ -20,7 +20,7 @@ bool IrdaAppBruteForce::calculate_messages() { result = flipper_format_file_open_existing(ff, universal_db_filename); if(result) { - IrdaAppSignal signal; + InfraredAppSignal signal; string_t signal_name; string_init(signal_name); @@ -38,7 +38,7 @@ bool IrdaAppBruteForce::calculate_messages() { return result; } -void IrdaAppBruteForce::stop_bruteforce() { +void InfraredAppBruteForce::stop_bruteforce() { furi_assert((current_record.size())); if(current_record.size()) { @@ -49,15 +49,15 @@ void IrdaAppBruteForce::stop_bruteforce() { } } -bool IrdaAppBruteForce::send_next_bruteforce(void) { +bool InfraredAppBruteForce::send_next_bruteforce(void) { furi_assert(current_record.size()); furi_assert(ff); - IrdaAppSignal signal; + InfraredAppSignal signal; std::string signal_name; bool result = false; do { - result = irda_parser_read_signal(ff, signal, signal_name); + result = infrared_parser_read_signal(ff, signal, signal_name); } while(result && current_record.compare(signal_name)); if(result) { @@ -66,7 +66,7 @@ bool IrdaAppBruteForce::send_next_bruteforce(void) { return result; } -bool IrdaAppBruteForce::start_bruteforce(int index, int& record_amount) { +bool InfraredAppBruteForce::start_bruteforce(int index, int& record_amount) { bool result = false; record_amount = 0; diff --git a/applications/infrared/infrared_app_brute_force.h b/applications/infrared/infrared_app_brute_force.h new file mode 100644 index 00000000..2dd3ade9 --- /dev/null +++ b/applications/infrared/infrared_app_brute_force.h @@ -0,0 +1,67 @@ +/** + * @file infrared_app_brute_force.h + * Infrared: Brute Force class description + */ +#pragma once + +#include +#include +#include + +/** Class handles brute force mechanic */ +class InfraredAppBruteForce { + /** Universal database filename */ + const char* universal_db_filename; + + /** Current record name (POWER, MUTE, VOL+, etc). + * This is the name of signal to brute force. */ + std::string current_record; + + /** Flipper File Format instance */ + FlipperFormat* ff; + + /** Data about every record - index in button panel view + * and amount of signals, which is need for correct + * progress bar displaying. */ + typedef struct { + /** Index of record in button panel view model */ + int index; + /** Amount of signals of that type (POWER, MUTE, etc) */ + int amount; + } Record; + + /** Container to hold Record info. + * 'key' is record name, because we have to search by both, index and name, + * but index search has place once per button press, and should not be + * noticed, but name search should occur during entering universal menu, + * and will go through container for every record in file, that's why + * more critical to have faster search by record name. + */ + std::unordered_map records; + +public: + /** Calculate messages. Walk through the file ('universal_db_name') + * and calculate amount of records of certain type. */ + bool calculate_messages(); + + /** Start brute force */ + bool start_bruteforce(int index, int& record_amount); + + /** Stop brute force */ + void stop_bruteforce(); + + /** Send next signal during brute force */ + bool send_next_bruteforce(); + + /** Add record to container of records */ + void add_record(int index, const char* name); + + /** Initialize class, set db file */ + InfraredAppBruteForce(const char* filename) + : universal_db_filename(filename) { + } + + /** Deinitialize class */ + ~InfraredAppBruteForce() { + } +}; diff --git a/applications/infrared/infrared_app_event.h b/applications/infrared/infrared_app_event.h new file mode 100644 index 00000000..fdea826a --- /dev/null +++ b/applications/infrared/infrared_app_event.h @@ -0,0 +1,47 @@ +/** + * @file infrared_app_event.h + * Infrared: Scene events description + */ +#pragma once +#include +#include + +/** Infrared events class */ +class InfraredAppEvent { +public: + /** Type of event enum */ + enum class Type : uint8_t { + /** Tick event come after no other events came in 100 ms */ + Tick, + /** Exit application event */ + Exit, + /** Back event */ + Back, + /** Menu selected event type. Provided with payload value. */ + MenuSelected, + /** Button press event. Need for continuous signal sending. */ + MenuSelectedPress, + /** Button release event. Need for continuous signal sending. */ + MenuSelectedRelease, + /** Events from DialogEx view module */ + DialogExSelected, + /** Infrared signal received event */ + InfraredMessageReceived, + /** Text edit done event */ + TextEditDone, + /** Popup timer finished event */ + PopupTimer, + /** Button panel pressed event */ + ButtonPanelPressed, + }; + + union { + /** Menu selected event type payload. Selected index. */ + int32_t menu_index; + /** DialogEx view module event type payload */ + DialogExResult dialog_ex_result; + } payload; + + /** Type of event */ + Type type; +}; diff --git a/applications/irda/irda_app_remote_manager.cpp b/applications/infrared/infrared_app_remote_manager.cpp similarity index 69% rename from applications/irda/irda_app_remote_manager.cpp rename to applications/infrared/infrared_app_remote_manager.cpp index d2081b42..bf2483ad 100644 --- a/applications/irda/irda_app_remote_manager.cpp +++ b/applications/infrared/infrared_app_remote_manager.cpp @@ -1,25 +1,26 @@ #include #include -#include "irda_app_remote_manager.h" -#include "irda/helpers/irda_parser.h" -#include "irda/irda_app_signal.h" +#include "infrared_app_remote_manager.h" +#include "infrared/helpers/infrared_parser.h" +#include "infrared/infrared_app_signal.h" #include -#include +#include #include #include #include #include -#include "irda_app.h" +#include "infrared_app.h" static const std::string default_remote_name = "remote"; -std::string IrdaAppRemoteManager::make_full_name(const std::string& remote_name) const { - return std::string("") + IrdaApp::irda_directory + "/" + remote_name + IrdaApp::irda_extension; +std::string InfraredAppRemoteManager::make_full_name(const std::string& remote_name) const { + return std::string("") + InfraredApp::infrared_directory + "/" + remote_name + + InfraredApp::infrared_extension; } -std::string IrdaAppRemoteManager::find_vacant_remote_name(const std::string& name) { +std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) { bool exist = true; FileWorkerCpp file_worker; @@ -41,14 +42,14 @@ std::string IrdaAppRemoteManager::find_vacant_remote_name(const std::string& nam return !exist ? name + std::to_string(i) : std::string(); } -bool IrdaAppRemoteManager::add_button(const char* button_name, const IrdaAppSignal& signal) { +bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { remote->buttons.emplace_back(button_name, signal); return store(); } -bool IrdaAppRemoteManager::add_remote_with_button( +bool InfraredAppRemoteManager::add_remote_with_button( const char* button_name, - const IrdaAppSignal& signal) { + const InfraredAppSignal& signal) { furi_check(button_name != nullptr); auto new_name = find_vacant_remote_name(default_remote_name); @@ -56,11 +57,11 @@ bool IrdaAppRemoteManager::add_remote_with_button( return false; } - remote = std::make_unique(new_name); + remote = std::make_unique(new_name); return add_button(button_name, signal); } -std::vector IrdaAppRemoteManager::get_button_list(void) const { +std::vector InfraredAppRemoteManager::get_button_list(void) const { std::vector name_vector; name_vector.reserve(remote->buttons.size()); @@ -72,7 +73,7 @@ std::vector IrdaAppRemoteManager::get_button_list(void) const { return name_vector; } -const IrdaAppSignal& IrdaAppRemoteManager::get_button_data(size_t index) const { +const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) const { furi_check(remote.get() != nullptr); auto& buttons = remote->buttons; furi_check(index < buttons.size()); @@ -80,7 +81,7 @@ const IrdaAppSignal& IrdaAppRemoteManager::get_button_data(size_t index) const { return buttons.at(index).signal; } -bool IrdaAppRemoteManager::delete_remote() { +bool InfraredAppRemoteManager::delete_remote() { bool result; FileWorkerCpp file_worker; result = file_worker.remove(make_full_name(remote->name).c_str()); @@ -89,11 +90,11 @@ bool IrdaAppRemoteManager::delete_remote() { return result; } -void IrdaAppRemoteManager::reset_remote() { +void InfraredAppRemoteManager::reset_remote() { remote.reset(); } -bool IrdaAppRemoteManager::delete_button(uint32_t index) { +bool InfraredAppRemoteManager::delete_button(uint32_t index) { furi_check(remote.get() != nullptr); auto& buttons = remote->buttons; furi_check(index < buttons.size()); @@ -102,30 +103,18 @@ bool IrdaAppRemoteManager::delete_button(uint32_t index) { return store(); } -std::string IrdaAppRemoteManager::get_button_name(uint32_t index) { +std::string InfraredAppRemoteManager::get_button_name(uint32_t index) { furi_check(remote.get() != nullptr); auto& buttons = remote->buttons; furi_check(index < buttons.size()); return buttons[index].name.c_str(); } -std::string IrdaAppRemoteManager::get_remote_name() { +std::string InfraredAppRemoteManager::get_remote_name() { return remote.get() ? remote->name : std::string(); } -int IrdaAppRemoteManager::find_remote_name(const std::vector& strings) { - furi_assert(remote.get() != nullptr); - int i = 0; - for(const auto& str : strings) { - if(!str.compare(remote->name)) { - return i; - } - ++i; - } - return -1; -} - -bool IrdaAppRemoteManager::rename_remote(const char* str) { +bool InfraredAppRemoteManager::rename_remote(const char* str) { furi_check(str != nullptr); furi_check(remote.get() != nullptr); @@ -148,7 +137,7 @@ bool IrdaAppRemoteManager::rename_remote(const char* str) { return result; } -bool IrdaAppRemoteManager::rename_button(uint32_t index, const char* str) { +bool InfraredAppRemoteManager::rename_button(uint32_t index, const char* str) { furi_check(remote.get() != nullptr); auto& buttons = remote->buttons; furi_check(index < buttons.size()); @@ -157,16 +146,16 @@ bool IrdaAppRemoteManager::rename_button(uint32_t index, const char* str) { return store(); } -size_t IrdaAppRemoteManager::get_number_of_buttons() { +size_t InfraredAppRemoteManager::get_number_of_buttons() { furi_check(remote.get() != nullptr); return remote->buttons.size(); } -bool IrdaAppRemoteManager::store(void) { +bool InfraredAppRemoteManager::store(void) { bool result = false; FileWorkerCpp file_worker; - if(!file_worker.mkdir(IrdaApp::irda_directory)) return false; + if(!file_worker.mkdir(InfraredApp::infrared_directory)) return false; Storage* storage = static_cast(furi_record_open("storage")); FlipperFormat* ff = flipper_format_file_alloc(storage); @@ -178,7 +167,7 @@ bool IrdaAppRemoteManager::store(void) { } if(result) { for(const auto& button : remote->buttons) { - result = irda_parser_save_signal(ff, button.signal, button.name.c_str()); + result = infrared_parser_save_signal(ff, button.signal, button.name.c_str()); if(!result) { break; } @@ -190,7 +179,7 @@ bool IrdaAppRemoteManager::store(void) { return result; } -bool IrdaAppRemoteManager::load(const std::string& remote_name) { +bool InfraredAppRemoteManager::load(const std::string& remote_name) { bool result = false; Storage* storage = static_cast(furi_record_open("storage")); FlipperFormat* ff = flipper_format_file_alloc(storage); @@ -208,10 +197,10 @@ bool IrdaAppRemoteManager::load(const std::string& remote_name) { string_clear(header); } if(result) { - remote = std::make_unique(remote_name); - IrdaAppSignal signal; + remote = std::make_unique(remote_name); + InfraredAppSignal signal; std::string signal_name; - while(irda_parser_read_signal(ff, signal, signal_name)) { + while(infrared_parser_read_signal(ff, signal, signal_name)) { remote->buttons.emplace_back(signal_name.c_str(), std::move(signal)); } } diff --git a/applications/infrared/infrared_app_remote_manager.h b/applications/infrared/infrared_app_remote_manager.h new file mode 100644 index 00000000..bb706f19 --- /dev/null +++ b/applications/infrared/infrared_app_remote_manager.h @@ -0,0 +1,188 @@ +/** + * @file infrared_app_remote_manager.h + * Infrared: Remote manager class. + * It holds remote, can load/save/rename remote, + * add/remove/rename buttons. + */ +#pragma once + +#include "infrared_app_signal.h" + +#include +#include + +#include +#include +#include +#include + +/** Class to handle remote button */ +class InfraredAppRemoteButton { + /** Allow field access */ + friend class InfraredAppRemoteManager; + /** Name of signal */ + std::string name; + /** Signal data */ + InfraredAppSignal signal; + +public: + /** Initialize remote button + * + * @param name - button name + * @param signal - signal to copy for remote button + */ + InfraredAppRemoteButton(const char* name, const InfraredAppSignal& signal) + : name(name) + , signal(signal) { + } + + /** Initialize remote button + * + * @param name - button name + * @param signal - signal to move for remote button + */ + InfraredAppRemoteButton(const char* name, InfraredAppSignal&& signal) + : name(name) + , signal(std::move(signal)) { + } + + /** Deinitialize remote button */ + ~InfraredAppRemoteButton() { + } +}; + +/** Class to handle remote */ +class InfraredAppRemote { + /** Allow field access */ + friend class InfraredAppRemoteManager; + /** Button container */ + std::vector buttons; + /** Name of remote */ + std::string name; + +public: + /** Initialize new remote + * + * @param name - new remote name + */ + InfraredAppRemote(const std::string& name) + : name(name) { + } +}; + +/** Class to handle remote manager */ +class InfraredAppRemoteManager { + /** Remote instance. There can be 1 remote loaded at a time. */ + std::unique_ptr remote; + /** Make full name from remote name + * + * @param remote_name name of remote + * @retval full name of remote on disk + */ + std::string make_full_name(const std::string& remote_name) const; + +public: + /** Restriction to button name length. Buttons larger are ignored. */ + static constexpr const uint32_t max_button_name_length = 22; + + /** Restriction to remote name length. Remotes larger are ignored. */ + static constexpr const uint32_t max_remote_name_length = 22; + + /** Construct button from signal, and create remote + * + * @param button_name - name of button to create + * @param signal - signal to create button from + * @retval true for success, false otherwise + * */ + bool add_remote_with_button(const char* button_name, const InfraredAppSignal& signal); + + /** Add button to current remote + * + * @param button_name - name of button to create + * @param signal - signal to create button from + * @retval true for success, false otherwise + * */ + bool add_button(const char* button_name, const InfraredAppSignal& signal); + + /** Rename button in current remote + * + * @param index - index of button to rename + * @param str - new button name + */ + bool rename_button(uint32_t index, const char* str); + + /** Rename current remote + * + * @param str - new remote name + */ + bool rename_remote(const char* str); + + /** Find vacant remote name. If suggested name is occupied, + * incremented digit(2,3,4,etc) added to name and check repeated. + * + * @param name - suggested remote name + * @retval garanteed free remote name, prefixed with suggested + */ + std::string find_vacant_remote_name(const std::string& name); + + /** Get button list + * + * @retval container of button names + */ + std::vector get_button_list() const; + + /** Get button name by index + * + * @param index - index of button to get name from + * @retval button name + */ + std::string get_button_name(uint32_t index); + + /** Get remote name + * + * @retval remote name + */ + std::string get_remote_name(); + + /** Get number of buttons + * + * @retval number of buttons + */ + size_t get_number_of_buttons(); + + /** Get button's signal + * + * @param index - index of interested button + * @retval signal + */ + const InfraredAppSignal& get_button_data(size_t index) const; + + /** Delete button + * + * @param index - index of interested button + * @retval true if success, false otherwise + */ + bool delete_button(uint32_t index); + + /** Delete remote + * + * @retval true if success, false otherwise + */ + bool delete_remote(); + + /** Clean all loaded info in current remote */ + void reset_remote(); + + /** Store current remote data on disk + * + * @retval true if success, false otherwise + */ + bool store(); + + /** Load data from disk into current remote + * + * @param name - name of remote to load + * @retval true if success, false otherwise + */ + bool load(const std::string& name); +}; diff --git a/applications/irda/irda_app_signal.cpp b/applications/infrared/infrared_app_signal.cpp similarity index 75% rename from applications/irda/irda_app_signal.cpp rename to applications/infrared/infrared_app_signal.cpp index 7e40a041..3344b3ca 100644 --- a/applications/irda/irda_app_signal.cpp +++ b/applications/infrared/infrared_app_signal.cpp @@ -1,7 +1,7 @@ -#include "irda_app_signal.h" -#include +#include "infrared_app_signal.h" +#include -void IrdaAppSignal::copy_raw_signal( +void InfraredAppSignal::copy_raw_signal( const uint32_t* timings, size_t size, uint32_t frequency, @@ -18,7 +18,7 @@ void IrdaAppSignal::copy_raw_signal( } } -void IrdaAppSignal::clear_timings() { +void InfraredAppSignal::clear_timings() { if(raw_signal) { delete[] payload.raw.timings; payload.raw.timings_cnt = 0; @@ -26,7 +26,7 @@ void IrdaAppSignal::clear_timings() { } } -IrdaAppSignal::IrdaAppSignal( +InfraredAppSignal::InfraredAppSignal( const uint32_t* timings, size_t timings_cnt, uint32_t frequency, @@ -35,12 +35,12 @@ IrdaAppSignal::IrdaAppSignal( copy_raw_signal(timings, timings_cnt, frequency, duty_cycle); } -IrdaAppSignal::IrdaAppSignal(const IrdaMessage* irda_message) { +InfraredAppSignal::InfraredAppSignal(const InfraredMessage* infrared_message) { raw_signal = false; - payload.message = *irda_message; + payload.message = *infrared_message; } -IrdaAppSignal& IrdaAppSignal::operator=(const IrdaAppSignal& other) { +InfraredAppSignal& InfraredAppSignal::operator=(const InfraredAppSignal& other) { clear_timings(); raw_signal = other.raw_signal; if(!raw_signal) { @@ -56,7 +56,7 @@ IrdaAppSignal& IrdaAppSignal::operator=(const IrdaAppSignal& other) { return *this; } -IrdaAppSignal::IrdaAppSignal(const IrdaAppSignal& other) { +InfraredAppSignal::InfraredAppSignal(const InfraredAppSignal& other) { raw_signal = other.raw_signal; if(!raw_signal) { payload.message = other.payload.message; @@ -69,7 +69,7 @@ IrdaAppSignal::IrdaAppSignal(const IrdaAppSignal& other) { } } -IrdaAppSignal::IrdaAppSignal(IrdaAppSignal&& other) { +InfraredAppSignal::InfraredAppSignal(InfraredAppSignal&& other) { raw_signal = other.raw_signal; if(!raw_signal) { payload.message = other.payload.message; @@ -86,13 +86,13 @@ IrdaAppSignal::IrdaAppSignal(IrdaAppSignal&& other) { } } -void IrdaAppSignal::set_message(const IrdaMessage* irda_message) { +void InfraredAppSignal::set_message(const InfraredMessage* infrared_message) { clear_timings(); raw_signal = false; - payload.message = *irda_message; + payload.message = *infrared_message; } -void IrdaAppSignal::set_raw_signal( +void InfraredAppSignal::set_raw_signal( uint32_t* timings, size_t timings_cnt, uint32_t frequency, @@ -102,11 +102,11 @@ void IrdaAppSignal::set_raw_signal( copy_raw_signal(timings, timings_cnt, frequency, duty_cycle); } -void IrdaAppSignal::transmit() const { +void InfraredAppSignal::transmit() const { if(!raw_signal) { - irda_send(&payload.message, 1); + infrared_send(&payload.message, 1); } else { - irda_send_raw_ext( + infrared_send_raw_ext( payload.raw.timings, payload.raw.timings_cnt, true, diff --git a/applications/infrared/infrared_app_signal.h b/applications/infrared/infrared_app_signal.h new file mode 100644 index 00000000..7b0b491b --- /dev/null +++ b/applications/infrared/infrared_app_signal.h @@ -0,0 +1,134 @@ +/** + * @file infrared_app_signal.h + * Infrared: Signal class + */ +#pragma once +#include +#include +#include +#include + +/** Infrared application signal class */ +class InfraredAppSignal { +public: + /** Raw signal structure */ + typedef struct { + /** Timings amount */ + size_t timings_cnt; + /** Samples of raw signal in ms */ + uint32_t* timings; + /** PWM Frequency of raw signal */ + uint32_t frequency; + /** PWM Duty cycle of raw signal */ + float duty_cycle; + } RawSignal; + +private: + /** if true - signal is raw, if false - signal is parsed */ + bool raw_signal; + /** signal data, either raw or parsed */ + union { + /** signal data for parsed signal */ + InfraredMessage message; + /** raw signal data */ + RawSignal raw; + } payload; + + /** Copy raw signal into object + * + * @param timings - timings (samples) of raw signal + * @param size - number of timings + * @frequency - PWM frequency of raw signal + * @duty_cycle - PWM duty cycle + */ + void + copy_raw_signal(const uint32_t* timings, size_t size, uint32_t frequency, float duty_cycle); + /** Clear and free timings data */ + void clear_timings(); + +public: + /** Construct Infrared signal class */ + InfraredAppSignal() { + raw_signal = false; + payload.message.protocol = InfraredProtocolUnknown; + } + + /** Destruct signal class and free all allocated data */ + ~InfraredAppSignal() { + clear_timings(); + } + + /** Construct object with raw signal + * + * @param timings - timings (samples) of raw signal + * @param size - number of timings + * @frequency - PWM frequency of raw signal + * @duty_cycle - PWM duty cycle + */ + InfraredAppSignal( + const uint32_t* timings, + size_t timings_cnt, + uint32_t frequency, + float duty_cycle); + + /** Construct object with parsed signal + * + * @param infrared_message - parsed_signal to construct from + */ + InfraredAppSignal(const InfraredMessage* infrared_message); + + /** Copy constructor */ + InfraredAppSignal(const InfraredAppSignal& other); + /** Move constructor */ + InfraredAppSignal(InfraredAppSignal&& other); + + /** Assignment operator */ + InfraredAppSignal& operator=(const InfraredAppSignal& signal); + + /** Set object to parsed signal + * + * @param infrared_message - parsed_signal to construct from + */ + void set_message(const InfraredMessage* infrared_message); + + /** Set object to raw signal + * + * @param timings - timings (samples) of raw signal + * @param size - number of timings + * @frequency - PWM frequency of raw signal + * @duty_cycle - PWM duty cycle + */ + void + set_raw_signal(uint32_t* timings, size_t timings_cnt, uint32_t frequency, float duty_cycle); + + /** Transmit held signal (???) */ + void transmit() const; + + /** Show is held signal raw + * + * @retval true if signal is raw, false if signal is parsed + */ + bool is_raw(void) const { + return raw_signal; + } + + /** Get parsed signal. + * User must check is_raw() signal before calling this function. + * + * @retval parsed signal pointer + */ + const InfraredMessage& get_message(void) const { + furi_assert(!raw_signal); + return payload.message; + } + + /** Get raw signal. + * User must check is_raw() signal before calling this function. + * + * @retval raw signal + */ + const RawSignal& get_raw_signal(void) const { + furi_assert(raw_signal); + return payload.raw; + } +}; diff --git a/applications/irda/irda_app_view_manager.cpp b/applications/infrared/infrared_app_view_manager.cpp similarity index 51% rename from applications/irda/irda_app_view_manager.cpp rename to applications/infrared/infrared_app_view_manager.cpp index 841a3093..1e08471d 100644 --- a/applications/irda/irda_app_view_manager.cpp +++ b/applications/infrared/infrared_app_view_manager.cpp @@ -6,16 +6,16 @@ #include #include -#include "irda/irda_app_view_manager.h" -#include "irda/view/irda_progress_view.h" -#include "irda_app.h" -#include "irda/irda_app_event.h" +#include "infrared/infrared_app_view_manager.h" +#include "infrared/view/infrared_progress_view.h" +#include "infrared_app.h" +#include "infrared/infrared_app_event.h" -IrdaAppViewManager::IrdaAppViewManager() { - event_queue = osMessageQueueNew(10, sizeof(IrdaAppEvent), NULL); +InfraredAppViewManager::InfraredAppViewManager() { + event_queue = osMessageQueueNew(10, sizeof(InfraredAppEvent), NULL); view_dispatcher = view_dispatcher_alloc(); - auto callback = cbc::obtain_connector(this, &IrdaAppViewManager::previous_view_callback); + auto callback = cbc::obtain_connector(this, &InfraredAppViewManager::previous_view_callback); gui = static_cast(furi_record_open("gui")); view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); @@ -26,18 +26,18 @@ IrdaAppViewManager::IrdaAppViewManager() { dialog_ex = dialog_ex_alloc(); text_input = text_input_alloc(); button_panel = button_panel_alloc(); - progress_view = irda_progress_view_alloc(); + progress_view = infrared_progress_view_alloc(); loading_view = loading_alloc(); universal_view_stack = view_stack_alloc(); view_stack_add_view(universal_view_stack, button_panel_get_view(button_panel)); view_set_orientation(view_stack_get_view(universal_view_stack), ViewOrientationVertical); - add_view(ViewType::UniversalRemote, view_stack_get_view(universal_view_stack)); - add_view(ViewType::ButtonMenu, button_menu_get_view(button_menu)); - add_view(ViewType::Submenu, submenu_get_view(submenu)); - add_view(ViewType::Popup, popup_get_view(popup)); - add_view(ViewType::DialogEx, dialog_ex_get_view(dialog_ex)); - add_view(ViewType::TextInput, text_input_get_view(text_input)); + add_view(ViewId::UniversalRemote, view_stack_get_view(universal_view_stack)); + add_view(ViewId::ButtonMenu, button_menu_get_view(button_menu)); + add_view(ViewId::Submenu, submenu_get_view(submenu)); + add_view(ViewId::Popup, popup_get_view(popup)); + add_view(ViewId::DialogEx, dialog_ex_get_view(dialog_ex)); + add_view(ViewId::TextInput, text_input_get_view(text_input)); view_set_previous_callback(view_stack_get_view(universal_view_stack), callback); view_set_previous_callback(button_menu_get_view(button_menu), callback); @@ -47,19 +47,19 @@ IrdaAppViewManager::IrdaAppViewManager() { view_set_previous_callback(text_input_get_view(text_input), callback); } -IrdaAppViewManager::~IrdaAppViewManager() { +InfraredAppViewManager::~InfraredAppViewManager() { view_dispatcher_remove_view( - view_dispatcher, static_cast(IrdaAppViewManager::ViewType::UniversalRemote)); + view_dispatcher, static_cast(InfraredAppViewManager::ViewId::UniversalRemote)); view_dispatcher_remove_view( - view_dispatcher, static_cast(IrdaAppViewManager::ViewType::ButtonMenu)); + view_dispatcher, static_cast(InfraredAppViewManager::ViewId::ButtonMenu)); view_dispatcher_remove_view( - view_dispatcher, static_cast(IrdaAppViewManager::ViewType::TextInput)); + view_dispatcher, static_cast(InfraredAppViewManager::ViewId::TextInput)); view_dispatcher_remove_view( - view_dispatcher, static_cast(IrdaAppViewManager::ViewType::DialogEx)); + view_dispatcher, static_cast(InfraredAppViewManager::ViewId::DialogEx)); view_dispatcher_remove_view( - view_dispatcher, static_cast(IrdaAppViewManager::ViewType::Submenu)); + view_dispatcher, static_cast(InfraredAppViewManager::ViewId::Submenu)); view_dispatcher_remove_view( - view_dispatcher, static_cast(IrdaAppViewManager::ViewType::Popup)); + view_dispatcher, static_cast(InfraredAppViewManager::ViewId::Popup)); view_stack_remove_view(universal_view_stack, button_panel_get_view(button_panel)); view_stack_free(universal_view_stack); @@ -69,7 +69,7 @@ IrdaAppViewManager::~IrdaAppViewManager() { button_menu_free(button_menu); dialog_ex_free(dialog_ex); text_input_free(text_input); - irda_progress_view_free(progress_view); + infrared_progress_view_free(progress_view); loading_free(loading_view); view_dispatcher_free(view_dispatcher); @@ -77,70 +77,70 @@ IrdaAppViewManager::~IrdaAppViewManager() { osMessageQueueDelete(event_queue); } -void IrdaAppViewManager::switch_to(ViewType type) { +void InfraredAppViewManager::switch_to(ViewId type) { view_dispatcher_switch_to_view(view_dispatcher, static_cast(type)); } -TextInput* IrdaAppViewManager::get_text_input() { +TextInput* InfraredAppViewManager::get_text_input() { return text_input; } -DialogEx* IrdaAppViewManager::get_dialog_ex() { +DialogEx* InfraredAppViewManager::get_dialog_ex() { return dialog_ex; } -Submenu* IrdaAppViewManager::get_submenu() { +Submenu* InfraredAppViewManager::get_submenu() { return submenu; } -Popup* IrdaAppViewManager::get_popup() { +Popup* InfraredAppViewManager::get_popup() { return popup; } -ButtonMenu* IrdaAppViewManager::get_button_menu() { +ButtonMenu* InfraredAppViewManager::get_button_menu() { return button_menu; } -ButtonPanel* IrdaAppViewManager::get_button_panel() { +ButtonPanel* InfraredAppViewManager::get_button_panel() { return button_panel; } -IrdaProgressView* IrdaAppViewManager::get_progress() { +InfraredProgressView* InfraredAppViewManager::get_progress() { return progress_view; } -Loading* IrdaAppViewManager::get_loading() { +Loading* InfraredAppViewManager::get_loading() { return loading_view; } -ViewStack* IrdaAppViewManager::get_universal_view_stack() { +ViewStack* InfraredAppViewManager::get_universal_view_stack() { return universal_view_stack; } -osMessageQueueId_t IrdaAppViewManager::get_event_queue() { +osMessageQueueId_t InfraredAppViewManager::get_event_queue() { return event_queue; } -void IrdaAppViewManager::clear_events() { - IrdaAppEvent event; +void InfraredAppViewManager::clear_events() { + InfraredAppEvent event; while(osMessageQueueGet(event_queue, &event, NULL, 0) == osOK) ; } -void IrdaAppViewManager::receive_event(IrdaAppEvent* event) { +void InfraredAppViewManager::receive_event(InfraredAppEvent* event) { if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) { - event->type = IrdaAppEvent::Type::Tick; + event->type = InfraredAppEvent::Type::Tick; } } -void IrdaAppViewManager::send_event(IrdaAppEvent* event) { +void InfraredAppViewManager::send_event(InfraredAppEvent* event) { uint32_t timeout = 0; /* Rapid button hammering on signal send scenes causes queue overflow - ignore it, - * but try to keep button release event - it switches off IRDA DMA sending. */ - if(event->type == IrdaAppEvent::Type::MenuSelectedRelease) { + * but try to keep button release event - it switches off INFRARED DMA sending. */ + if(event->type == InfraredAppEvent::Type::MenuSelectedRelease) { timeout = 200; } - if((event->type == IrdaAppEvent::Type::DialogExSelected) && + if((event->type == InfraredAppEvent::Type::DialogExSelected) && (event->payload.dialog_ex_result == DialogExReleaseCenter)) { timeout = 200; } @@ -148,16 +148,16 @@ void IrdaAppViewManager::send_event(IrdaAppEvent* event) { osMessageQueuePut(event_queue, event, 0, timeout); } -uint32_t IrdaAppViewManager::previous_view_callback(void* context) { +uint32_t InfraredAppViewManager::previous_view_callback(void* context) { if(event_queue != NULL) { - IrdaAppEvent event; - event.type = IrdaAppEvent::Type::Back; + InfraredAppEvent event; + event.type = InfraredAppEvent::Type::Back; send_event(&event); } return VIEW_IGNORE; } -void IrdaAppViewManager::add_view(ViewType view_type, View* view) { +void InfraredAppViewManager::add_view(ViewId view_type, View* view) { view_dispatcher_add_view(view_dispatcher, static_cast(view_type), view); } diff --git a/applications/infrared/infrared_app_view_manager.h b/applications/infrared/infrared_app_view_manager.h new file mode 100644 index 00000000..106d2660 --- /dev/null +++ b/applications/infrared/infrared_app_view_manager.h @@ -0,0 +1,164 @@ +/** + * @file infrared_app_view_manager.h + * Infrared: Scene events description + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "infrared_app_event.h" +#include "view/infrared_progress_view.h" + +/** Infrared View manager class */ +class InfraredAppViewManager { +public: + /** Infrared View Id enum, it is used + * to identify added views */ + enum class ViewId : uint8_t { + DialogEx, + TextInput, + Submenu, + ButtonMenu, + UniversalRemote, + Popup, + }; + + /** Class constructor */ + InfraredAppViewManager(); + /** Class destructor */ + ~InfraredAppViewManager(); + + /** Switch to another view + * + * @param id - view id to switch to + */ + void switch_to(ViewId id); + + /** Receive event from queue + * + * @param event - received event + */ + void receive_event(InfraredAppEvent* event); + + /** Send event to queue + * + * @param event - event to send + */ + void send_event(InfraredAppEvent* event); + + /** Clear events that already in queue + * + * @param event - event to send + */ + void clear_events(); + + /** Get dialog_ex view module + * + * @retval dialog_ex view module + */ + DialogEx* get_dialog_ex(); + + /** Get submenu view module + * + * @retval submenu view module + */ + Submenu* get_submenu(); + + /** Get popup view module + * + * @retval popup view module + */ + Popup* get_popup(); + + /** Get text_input view module + * + * @retval text_input view module + */ + TextInput* get_text_input(); + + /** Get button_menu view module + * + * @retval button_menu view module + */ + ButtonMenu* get_button_menu(); + + /** Get button_panel view module + * + * @retval button_panel view module + */ + ButtonPanel* get_button_panel(); + + /** Get view_stack view module used in universal remote + * + * @retval view_stack view module + */ + ViewStack* get_universal_view_stack(); + + /** Get progress view module + * + * @retval progress view module + */ + InfraredProgressView* get_progress(); + + /** Get loading view module + * + * @retval loading view module + */ + Loading* get_loading(); + + /** Get event queue + * + * @retval event queue + */ + osMessageQueueId_t get_event_queue(); + + /** Callback to handle back button + * + * @param context - context to pass to callback + * @retval always returns VIEW_IGNORE + */ + uint32_t previous_view_callback(void* context); + +private: + /** View Dispatcher instance. + * It handles view switching */ + ViewDispatcher* view_dispatcher; + /** Gui instance */ + Gui* gui; + /** Text input view module instance */ + TextInput* text_input; + /** DialogEx view module instance */ + DialogEx* dialog_ex; + /** Submenu view module instance */ + Submenu* submenu; + /** Popup view module instance */ + Popup* popup; + /** ButtonMenu view module instance */ + ButtonMenu* button_menu; + /** ButtonPanel view module instance */ + ButtonPanel* button_panel; + /** ViewStack view module instance */ + ViewStack* universal_view_stack; + /** ProgressView view module instance */ + InfraredProgressView* progress_view; + /** Loading view module instance */ + Loading* loading_view; + + /** Queue to handle events, which are processed in scenes */ + osMessageQueueId_t event_queue; + + /** Add View to pull of views + * + * @param view_id - id to identify view + * @param view - view to add + */ + void add_view(ViewId view_id, View* view); +}; diff --git a/applications/infrared/infrared_runner.cpp b/applications/infrared/infrared_runner.cpp new file mode 100644 index 00000000..650b0fcd --- /dev/null +++ b/applications/infrared/infrared_runner.cpp @@ -0,0 +1,9 @@ +#include "infrared_app.h" + +extern "C" int32_t infrared_app(void* p) { + InfraredApp* app = new InfraredApp(); + int32_t result = app->run(p); + delete app; + + return result; +} diff --git a/applications/infrared/scene/infrared_app_scene.h b/applications/infrared/scene/infrared_app_scene.h new file mode 100644 index 00000000..9c2e20e9 --- /dev/null +++ b/applications/infrared/scene/infrared_app_scene.h @@ -0,0 +1,305 @@ +/** + * @file infrared_app_scene.h + * Infrared: Application scenes + */ +#pragma once +#include "../infrared_app_event.h" +#include +#include "infrared.h" +#include +#include +#include "../infrared_app_brute_force.h" + +/** Anonymous class */ +class InfraredApp; + +/** Base Scene class */ +class InfraredAppScene { +public: + /** Called when enter scene */ + virtual void on_enter(InfraredApp* app) = 0; + /** Events handler callback */ + virtual bool on_event(InfraredApp* app, InfraredAppEvent* event) = 0; + /** Called when exit scene */ + virtual void on_exit(InfraredApp* app) = 0; + /** Virtual destructor of base class */ + virtual ~InfraredAppScene(){}; + +private: +}; + +/** Start scene + * Main Infrared application menu + */ +class InfraredAppSceneStart : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + +private: + /** Save previously selected submenu index + * to highlight it when get back */ + uint32_t submenu_item_selected = 0; +}; + +/** Universal menu scene + * Scene to select universal remote + */ +class InfraredAppSceneUniversal : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + +private: + /** Save previously selected submenu index + * to highlight it when get back */ + uint32_t submenu_item_selected = 0; +}; + +/** Learn new signal scene + * On this scene catching new IR signal performed. + */ +class InfraredAppSceneLearn : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +/** New signal learn succeeded scene + */ +class InfraredAppSceneLearnSuccess : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + bool button_pressed = false; +}; + +/** Scene to enter name for new button in remote + */ +class InfraredAppSceneLearnEnterName : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +/** Scene where signal is learnt + */ +class InfraredAppSceneLearnDone : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +/** Remote interface scene + * On this scene you can send IR signals from selected remote + */ +class InfraredAppSceneRemote : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + +private: + /** container of button names in current remote. */ + std::vector buttons_names; + /** Save previously selected index + * to highlight it when get back */ + uint32_t buttonmenu_item_selected = 0; + /** state flag to show button is pressed. + * As long as send-signal button pressed no other button + * events are handled. */ + bool button_pressed = false; +}; + +/** List of remotes scene + * Every remote is a file, located on internal/external storage. + * Every file has same format, and same extension. + * Files are parsed as you enter 'Remote scene' and showed + * as a buttons. + */ +class InfraredAppSceneRemoteList : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + +private: + /** Save previously selected index + * to highlight it when get back */ + uint32_t submenu_item_selected = 0; + /** Remote names to show them in submenu */ + std::vector remote_names; +}; + +class InfraredAppSceneAskBack : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +class InfraredAppSceneEdit : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + +private: + /** Save previously selected index + * to highlight it when get back */ + uint32_t submenu_item_selected = 0; +}; + +class InfraredAppSceneEditKeySelect : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + +private: + /** Button names to show them in submenu */ + std::vector buttons_names; +}; + +class InfraredAppSceneEditRename : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +class InfraredAppSceneEditDelete : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +class InfraredAppSceneEditRenameDone : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +class InfraredAppSceneEditDeleteDone : public InfraredAppScene { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; +}; + +class InfraredAppSceneUniversalCommon : public InfraredAppScene { + /** Brute force started flag */ + bool brute_force_started = false; + +protected: + /** Events handler callback */ + bool on_event(InfraredApp* app, InfraredAppEvent* event) final; + /** Called when exit scene */ + void on_exit(InfraredApp* app) final; + + /** Show popup window + * + * @param app - application instance + */ + void show_popup(InfraredApp* app, int record_amount); + + /** Hide popup window + * + * @param app - application instance + */ + void hide_popup(InfraredApp* app); + + /** Propagate progress in popup window + * + * @param app - application instance + */ + bool progress_popup(InfraredApp* app); + + /** Item selected callback + * + * @param context - context + * @param index - selected item index + */ + static void infrared_app_item_callback(void* context, uint32_t index); + + /** Brute Force instance */ + InfraredAppBruteForce brute_force; + + /** Constructor */ + InfraredAppSceneUniversalCommon(const char* filename) + : brute_force(filename) { + } + + /** Destructor */ + ~InfraredAppSceneUniversalCommon() { + } +}; + +class InfraredAppSceneUniversalTV : public InfraredAppSceneUniversalCommon { +public: + /** Called when enter scene */ + void on_enter(InfraredApp* app) final; + + /** Constructor + * Specifies path to brute force db library */ + InfraredAppSceneUniversalTV() + : InfraredAppSceneUniversalCommon("/ext/infrared/assets/tv.ir") { + } + + /** Destructor */ + ~InfraredAppSceneUniversalTV() { + } +}; diff --git a/applications/irda/scene/irda_app_scene_ask_back.cpp b/applications/infrared/scene/infrared_app_scene_ask_back.cpp similarity index 65% rename from applications/irda/scene/irda_app_scene_ask_back.cpp rename to applications/infrared/scene/infrared_app_scene_ask_back.cpp index f7c37110..751816af 100644 --- a/applications/irda/scene/irda_app_scene_ask_back.cpp +++ b/applications/infrared/scene/infrared_app_scene_ask_back.cpp @@ -1,21 +1,21 @@ -#include "../irda_app.h" +#include "../infrared_app.h" #include "gui/modules/dialog_ex.h" -#include "irda.h" -#include "irda/scene/irda_app_scene.h" +#include "infrared.h" +#include "infrared/scene/infrared_app_scene.h" #include static void dialog_result_callback(DialogExResult result, void* context) { - auto app = static_cast(context); - IrdaAppEvent event; + auto app = static_cast(context); + InfraredAppEvent event; - event.type = IrdaAppEvent::Type::DialogExSelected; + event.type = InfraredAppEvent::Type::DialogExSelected; event.payload.dialog_ex_result = result; app->get_view_manager()->send_event(&event); } -void IrdaAppSceneAskBack::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneAskBack::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); DialogEx* dialog_ex = view_manager->get_dialog_ex(); if(app->get_learn_new_remote()) { @@ -33,21 +33,21 @@ void IrdaAppSceneAskBack::on_enter(IrdaApp* app) { dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); dialog_ex_set_context(dialog_ex, app); - view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); + view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx); } -bool IrdaAppSceneAskBack::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneAskBack::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::DialogExSelected) { + if(event->type == InfraredAppEvent::Type::DialogExSelected) { switch(event->payload.dialog_ex_result) { case DialogExResultLeft: consumed = true; if(app->get_learn_new_remote()) { - app->search_and_switch_to_previous_scene({IrdaApp::Scene::Start}); + app->search_and_switch_to_previous_scene({InfraredApp::Scene::Start}); } else { app->search_and_switch_to_previous_scene( - {IrdaApp::Scene::Edit, IrdaApp::Scene::Remote}); + {InfraredApp::Scene::Edit, InfraredApp::Scene::Remote}); } break; case DialogExResultCenter: @@ -62,12 +62,12 @@ bool IrdaAppSceneAskBack::on_event(IrdaApp* app, IrdaAppEvent* event) { } } - if(event->type == IrdaAppEvent::Type::Back) { + if(event->type == InfraredAppEvent::Type::Back) { consumed = true; } return consumed; } -void IrdaAppSceneAskBack::on_exit(IrdaApp* app) { +void InfraredAppSceneAskBack::on_exit(InfraredApp* app) { } diff --git a/applications/irda/scene/irda_app_scene_edit.cpp b/applications/infrared/scene/infrared_app_scene_edit.cpp similarity index 50% rename from applications/irda/scene/irda_app_scene_edit.cpp rename to applications/infrared/scene/infrared_app_scene_edit.cpp index bb26ca11..3d9aafe7 100644 --- a/applications/irda/scene/irda_app_scene_edit.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit.cpp @@ -1,4 +1,4 @@ -#include "../irda_app.h" +#include "../infrared_app.h" #include "gui/modules/submenu.h" typedef enum { @@ -10,17 +10,17 @@ typedef enum { } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; + InfraredApp* app = static_cast(context); + InfraredAppEvent event; - event.type = IrdaAppEvent::Type::MenuSelected; + event.type = InfraredAppEvent::Type::MenuSelected; event.payload.menu_index = index; app->get_view_manager()->send_event(&event); } -void IrdaAppSceneEdit::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneEdit::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); submenu_add_item(submenu, "Add key", SubmenuIndexAddKey, submenu_callback, app); @@ -31,38 +31,38 @@ void IrdaAppSceneEdit::on_enter(IrdaApp* app) { submenu_set_selected_item(submenu, submenu_item_selected); submenu_item_selected = 0; - view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); + view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); } -bool IrdaAppSceneEdit::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneEdit::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::MenuSelected) { + if(event->type == InfraredAppEvent::Type::MenuSelected) { submenu_item_selected = event->payload.menu_index; switch(event->payload.menu_index) { case SubmenuIndexAddKey: app->set_learn_new_remote(false); - app->switch_to_next_scene(IrdaApp::Scene::Learn); + app->switch_to_next_scene(InfraredApp::Scene::Learn); break; case SubmenuIndexRenameKey: - app->set_edit_action(IrdaApp::EditAction::Rename); - app->set_edit_element(IrdaApp::EditElement::Button); - app->switch_to_next_scene(IrdaApp::Scene::EditKeySelect); + app->set_edit_action(InfraredApp::EditAction::Rename); + app->set_edit_element(InfraredApp::EditElement::Button); + app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect); break; case SubmenuIndexDeleteKey: - app->set_edit_action(IrdaApp::EditAction::Delete); - app->set_edit_element(IrdaApp::EditElement::Button); - app->switch_to_next_scene(IrdaApp::Scene::EditKeySelect); + app->set_edit_action(InfraredApp::EditAction::Delete); + app->set_edit_element(InfraredApp::EditElement::Button); + app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect); break; case SubmenuIndexRenameRemote: - app->set_edit_action(IrdaApp::EditAction::Rename); - app->set_edit_element(IrdaApp::EditElement::Remote); - app->switch_to_next_scene(IrdaApp::Scene::EditRename); + app->set_edit_action(InfraredApp::EditAction::Rename); + app->set_edit_element(InfraredApp::EditElement::Remote); + app->switch_to_next_scene(InfraredApp::Scene::EditRename); break; case SubmenuIndexDeleteRemote: - app->set_edit_action(IrdaApp::EditAction::Delete); - app->set_edit_element(IrdaApp::EditElement::Remote); - app->switch_to_next_scene(IrdaApp::Scene::EditDelete); + app->set_edit_action(InfraredApp::EditAction::Delete); + app->set_edit_element(InfraredApp::EditElement::Remote); + app->switch_to_next_scene(InfraredApp::Scene::EditDelete); break; } consumed = true; @@ -71,8 +71,8 @@ bool IrdaAppSceneEdit::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneEdit::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneEdit::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); submenu_reset(submenu); diff --git a/applications/irda/scene/irda_app_scene_edit_delete.cpp b/applications/infrared/scene/infrared_app_scene_edit_delete.cpp similarity index 66% rename from applications/irda/scene/irda_app_scene_edit_delete.cpp rename to applications/infrared/scene/infrared_app_scene_edit_delete.cpp index 26204801..0818b6fb 100644 --- a/applications/irda/scene/irda_app_scene_edit_delete.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit_delete.cpp @@ -1,25 +1,25 @@ -#include "../irda_app.h" -#include "irda.h" -#include "irda/scene/irda_app_scene.h" +#include "../infrared_app.h" +#include "infrared.h" +#include "infrared/scene/infrared_app_scene.h" #include static void dialog_result_callback(DialogExResult result, void* context) { - auto app = static_cast(context); - IrdaAppEvent event; + auto app = static_cast(context); + InfraredAppEvent event; - event.type = IrdaAppEvent::Type::DialogExSelected; + event.type = InfraredAppEvent::Type::DialogExSelected; event.payload.dialog_ex_result = result; app->get_view_manager()->send_event(&event); } -void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneEditDelete::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); DialogEx* dialog_ex = view_manager->get_dialog_ex(); auto remote_manager = app->get_remote_manager(); - if(app->get_edit_element() == IrdaApp::EditElement::Button) { + if(app->get_edit_element() == InfraredApp::EditElement::Button) { auto signal = remote_manager->get_button_data(app->get_current_button()); dialog_ex_set_header(dialog_ex, "Delete button?", 64, 0, AlignCenter, AlignTop); if(!signal.is_raw()) { @@ -28,10 +28,10 @@ void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { 0, "%s\n%s\nA=0x%0*lX C=0x%0*lX", remote_manager->get_button_name(app->get_current_button()).c_str(), - irda_get_protocol_name(message->protocol), - ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), + infrared_get_protocol_name(message->protocol), + ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), message->address, - ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), message->command); } else { app->set_text_store( @@ -56,13 +56,13 @@ void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); dialog_ex_set_context(dialog_ex, app); - view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); + view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx); } -bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneEditDelete::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::DialogExSelected) { + if(event->type == InfraredAppEvent::Type::DialogExSelected) { switch(event->payload.dialog_ex_result) { case DialogExResultLeft: app->switch_to_previous_scene(); @@ -73,18 +73,18 @@ bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { case DialogExResultRight: { auto remote_manager = app->get_remote_manager(); bool result = false; - if(app->get_edit_element() == IrdaApp::EditElement::Remote) { + if(app->get_edit_element() == InfraredApp::EditElement::Remote) { result = remote_manager->delete_remote(); } else { result = remote_manager->delete_button(app->get_current_button()); - app->set_current_button(IrdaApp::ButtonNA); + app->set_current_button(InfraredApp::ButtonNA); } if(!result) { app->search_and_switch_to_previous_scene( - {IrdaApp::Scene::RemoteList, IrdaApp::Scene::Start}); + {InfraredApp::Scene::RemoteList, InfraredApp::Scene::Start}); } else { - app->switch_to_next_scene(IrdaApp::Scene::EditDeleteDone); + app->switch_to_next_scene(InfraredApp::Scene::EditDeleteDone); } break; } @@ -96,5 +96,5 @@ bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneEditDelete::on_exit(IrdaApp* app) { +void InfraredAppSceneEditDelete::on_exit(InfraredApp* app) { } diff --git a/applications/infrared/scene/infrared_app_scene_edit_delete_done.cpp b/applications/infrared/scene/infrared_app_scene_edit_delete_done.cpp new file mode 100644 index 00000000..3f9800e1 --- /dev/null +++ b/applications/infrared/scene/infrared_app_scene_edit_delete_done.cpp @@ -0,0 +1,38 @@ +#include "../infrared_app.h" + +void InfraredAppSceneEditDeleteDone::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); + Popup* popup = view_manager->get_popup(); + + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + + popup_set_callback(popup, InfraredApp::popup_callback); + popup_set_context(popup, app); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); +} + +bool InfraredAppSceneEditDeleteDone::on_event(InfraredApp* app, InfraredAppEvent* event) { + bool consumed = false; + + if(event->type == InfraredAppEvent::Type::PopupTimer) { + if(app->get_edit_element() == InfraredApp::EditElement::Remote) { + app->search_and_switch_to_previous_scene( + {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); + } else { + app->search_and_switch_to_previous_scene({InfraredApp::Scene::Remote}); + } + consumed = true; + } + + return consumed; +} + +void InfraredAppSceneEditDeleteDone::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); + Popup* popup = view_manager->get_popup(); + popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop); +} diff --git a/applications/infrared/scene/infrared_app_scene_edit_key_select.cpp b/applications/infrared/scene/infrared_app_scene_edit_key_select.cpp new file mode 100644 index 00000000..7f94fb15 --- /dev/null +++ b/applications/infrared/scene/infrared_app_scene_edit_key_select.cpp @@ -0,0 +1,57 @@ +#include "../infrared_app.h" +#include "gui/modules/submenu.h" + +static void submenu_callback(void* context, uint32_t index) { + InfraredApp* app = static_cast(context); + InfraredAppEvent event; + + event.type = InfraredAppEvent::Type::MenuSelected; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +void InfraredAppSceneEditKeySelect::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + int item_number = 0; + + const char* header = + app->get_edit_action() == InfraredApp::EditAction::Rename ? "Rename key:" : "Delete key:"; + submenu_set_header(submenu, header); + + auto remote_manager = app->get_remote_manager(); + buttons_names = remote_manager->get_button_list(); + for(const auto& it : buttons_names) { + submenu_add_item(submenu, it.c_str(), item_number++, submenu_callback, app); + } + if((item_number > 0) && (app->get_current_button() != InfraredApp::ButtonNA)) { + submenu_set_selected_item(submenu, app->get_current_button()); + app->set_current_button(InfraredApp::ButtonNA); + } + + view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); +} + +bool InfraredAppSceneEditKeySelect::on_event(InfraredApp* app, InfraredAppEvent* event) { + bool consumed = false; + + if(event->type == InfraredAppEvent::Type::MenuSelected) { + app->set_current_button(event->payload.menu_index); + consumed = true; + if(app->get_edit_action() == InfraredApp::EditAction::Rename) { + app->switch_to_next_scene(InfraredApp::Scene::EditRename); + } else { + app->switch_to_next_scene(InfraredApp::Scene::EditDelete); + } + } + + return consumed; +} + +void InfraredAppSceneEditKeySelect::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); + Submenu* submenu = view_manager->get_submenu(); + + submenu_reset(submenu); +} diff --git a/applications/irda/scene/irda_app_scene_edit_rename.cpp b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp similarity index 60% rename from applications/irda/scene/irda_app_scene_edit_rename.cpp rename to applications/infrared/scene/infrared_app_scene_edit_rename.cpp index 28c13456..4b3578fa 100644 --- a/applications/irda/scene/irda_app_scene_edit_rename.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp @@ -1,16 +1,16 @@ -#include "../irda_app.h" +#include "../infrared_app.h" -void IrdaAppSceneEditRename::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); TextInput* text_input = view_manager->get_text_input(); size_t enter_name_length = 0; auto remote_manager = app->get_remote_manager(); - if(app->get_edit_element() == IrdaApp::EditElement::Button) { - furi_assert(app->get_current_button() != IrdaApp::ButtonNA); + if(app->get_edit_element() == InfraredApp::EditElement::Button) { + furi_assert(app->get_current_button() != InfraredApp::ButtonNA); auto button_name = remote_manager->get_button_name(app->get_current_button()); char* buffer_str = app->get_text_store(0); - size_t max_len = IrdaAppRemoteManager::max_button_name_length; + size_t max_len = InfraredAppRemoteManager::max_button_name_length; strncpy(buffer_str, button_name.c_str(), max_len); buffer_str[max_len + 1] = 0; enter_name_length = max_len; @@ -18,43 +18,43 @@ void IrdaAppSceneEditRename::on_enter(IrdaApp* app) { } else { auto remote_name = remote_manager->get_remote_name(); strncpy(app->get_text_store(0), remote_name.c_str(), app->get_text_store_size()); - enter_name_length = IrdaAppRemoteManager::max_remote_name_length; + enter_name_length = InfraredAppRemoteManager::max_remote_name_length; text_input_set_header_text(text_input, "Name the remote"); ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->irda_directory, app->irda_extension); + validator_is_file_alloc_init(app->infrared_directory, app->infrared_extension); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); } text_input_set_result_callback( text_input, - IrdaApp::text_input_callback, + InfraredApp::text_input_callback, app, app->get_text_store(0), enter_name_length, false); - view_manager->switch_to(IrdaAppViewManager::ViewType::TextInput); + view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput); } -bool IrdaAppSceneEditRename::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneEditRename::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::TextEditDone) { + if(event->type == InfraredAppEvent::Type::TextEditDone) { auto remote_manager = app->get_remote_manager(); bool result = false; - if(app->get_edit_element() == IrdaApp::EditElement::Button) { + if(app->get_edit_element() == InfraredApp::EditElement::Button) { result = remote_manager->rename_button(app->get_current_button(), app->get_text_store(0)); - app->set_current_button(IrdaApp::ButtonNA); + app->set_current_button(InfraredApp::ButtonNA); } else { result = remote_manager->rename_remote(app->get_text_store(0)); } if(!result) { app->search_and_switch_to_previous_scene( - {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); } else { - app->switch_to_next_scene_without_saving(IrdaApp::Scene::EditRenameDone); + app->switch_to_next_scene_without_saving(InfraredApp::Scene::EditRenameDone); } consumed = true; } @@ -62,7 +62,7 @@ bool IrdaAppSceneEditRename::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneEditRename::on_exit(IrdaApp* app) { +void InfraredAppSceneEditRename::on_exit(InfraredApp* app) { TextInput* text_input = app->get_view_manager()->get_text_input(); void* validator_context = text_input_get_validator_callback_context(text_input); diff --git a/applications/infrared/scene/infrared_app_scene_edit_rename_done.cpp b/applications/infrared/scene/infrared_app_scene_edit_rename_done.cpp new file mode 100644 index 00000000..47ef343b --- /dev/null +++ b/applications/infrared/scene/infrared_app_scene_edit_rename_done.cpp @@ -0,0 +1,31 @@ +#include "../infrared_app.h" + +void InfraredAppSceneEditRenameDone::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); + Popup* popup = view_manager->get_popup(); + + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + + popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + + popup_set_callback(popup, InfraredApp::popup_callback); + popup_set_context(popup, app); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); +} + +bool InfraredAppSceneEditRenameDone::on_event(InfraredApp* app, InfraredAppEvent* event) { + bool consumed = false; + + if(event->type == InfraredAppEvent::Type::PopupTimer) { + app->switch_to_next_scene(InfraredApp::Scene::Remote); + consumed = true; + } + + return consumed; +} + +void InfraredAppSceneEditRenameDone::on_exit(InfraredApp* app) { +} diff --git a/applications/infrared/scene/infrared_app_scene_learn.cpp b/applications/infrared/scene/infrared_app_scene_learn.cpp new file mode 100644 index 00000000..1ae3b92c --- /dev/null +++ b/applications/infrared/scene/infrared_app_scene_learn.cpp @@ -0,0 +1,75 @@ +#include "../infrared_app.h" +#include "../infrared_app_event.h" +#include "infrared.h" +#include + +static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { + furi_assert(context); + furi_assert(received_signal); + + InfraredApp* app = static_cast(context); + + if(infrared_worker_signal_is_decoded(received_signal)) { + InfraredAppSignal signal(infrared_worker_get_decoded_signal(received_signal)); + app->set_received_signal(signal); + } else { + const uint32_t* timings; + size_t timings_cnt; + infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); + InfraredAppSignal signal( + timings, timings_cnt, INFRARED_COMMON_CARRIER_FREQUENCY, INFRARED_COMMON_DUTY_CYCLE); + app->set_received_signal(signal); + } + + infrared_worker_rx_set_received_signal_callback(app->get_infrared_worker(), NULL, NULL); + InfraredAppEvent event; + event.type = InfraredAppEvent::Type::InfraredMessageReceived; + auto view_manager = app->get_view_manager(); + view_manager->send_event(&event); +} + +void InfraredAppSceneLearn::on_enter(InfraredApp* app) { + auto view_manager = app->get_view_manager(); + auto popup = view_manager->get_popup(); + + auto worker = app->get_infrared_worker(); + infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, app); + infrared_worker_rx_start(worker); + + popup_set_icon(popup, 0, 32, &I_InfraredLearnShort_128x31); + popup_set_text( + popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); + popup_set_callback(popup, NULL); + + view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); +} + +bool InfraredAppSceneLearn::on_event(InfraredApp* app, InfraredAppEvent* event) { + bool consumed = false; + + switch(event->type) { + case InfraredAppEvent::Type::Tick: + consumed = true; + app->notify_red_blink(); + break; + case InfraredAppEvent::Type::InfraredMessageReceived: + app->notify_success(); + app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnSuccess); + break; + case InfraredAppEvent::Type::Back: + consumed = true; + app->switch_to_previous_scene(); + break; + default: + furi_assert(0); + } + + return consumed; +} + +void InfraredAppSceneLearn::on_exit(InfraredApp* app) { + infrared_worker_rx_stop(app->get_infrared_worker()); + auto view_manager = app->get_view_manager(); + auto popup = view_manager->get_popup(); + popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter); +} diff --git a/applications/irda/scene/irda_app_scene_learn_done.cpp b/applications/infrared/scene/infrared_app_scene_learn_done.cpp similarity index 53% rename from applications/irda/scene/irda_app_scene_learn_done.cpp rename to applications/infrared/scene/infrared_app_scene_learn_done.cpp index f2669baa..6ee13b43 100644 --- a/applications/irda/scene/irda_app_scene_learn_done.cpp +++ b/applications/infrared/scene/infrared_app_scene_learn_done.cpp @@ -1,8 +1,8 @@ -#include "../irda_app.h" +#include "../infrared_app.h" #include -void IrdaAppSceneLearnDone::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneLearnDone::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Popup* popup = view_manager->get_popup(); popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); @@ -14,28 +14,28 @@ void IrdaAppSceneLearnDone::on_enter(IrdaApp* app) { popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); } - popup_set_callback(popup, IrdaApp::popup_callback); + popup_set_callback(popup, InfraredApp::popup_callback); popup_set_context(popup, app); popup_set_timeout(popup, 1500); popup_enable_timeout(popup); - view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); + view_manager->switch_to(InfraredAppViewManager::ViewId::Popup); } -bool IrdaAppSceneLearnDone::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneLearnDone::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::PopupTimer) { - app->switch_to_next_scene(IrdaApp::Scene::Remote); + if(event->type == InfraredAppEvent::Type::PopupTimer) { + app->switch_to_next_scene(InfraredApp::Scene::Remote); consumed = true; } return consumed; } -void IrdaAppSceneLearnDone::on_exit(IrdaApp* app) { +void InfraredAppSceneLearnDone::on_exit(InfraredApp* app) { app->set_learn_new_remote(false); - IrdaAppViewManager* view_manager = app->get_view_manager(); + InfraredAppViewManager* view_manager = app->get_view_manager(); Popup* popup = view_manager->get_popup(); popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop); } diff --git a/applications/irda/scene/irda_app_scene_learn_enter_name.cpp b/applications/infrared/scene/infrared_app_scene_learn_enter_name.cpp similarity index 58% rename from applications/irda/scene/irda_app_scene_learn_enter_name.cpp rename to applications/infrared/scene/infrared_app_scene_learn_enter_name.cpp index 3c871553..e0f2ebd1 100644 --- a/applications/irda/scene/irda_app_scene_learn_enter_name.cpp +++ b/applications/infrared/scene/infrared_app_scene_learn_enter_name.cpp @@ -1,8 +1,8 @@ -#include "../irda_app.h" +#include "../infrared_app.h" #include "gui/modules/text_input.h" -void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneLearnEnterName::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); TextInput* text_input = view_manager->get_text_input(); auto signal = app->get_received_signal(); @@ -12,8 +12,8 @@ void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) { app->set_text_store( 0, "%.4s_%0*lX", - irda_get_protocol_name(message->protocol), - ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), + infrared_get_protocol_name(message->protocol), + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), message->command); } else { auto raw_signal = signal.get_raw_signal(); @@ -23,19 +23,19 @@ void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) { text_input_set_header_text(text_input, "Name the key"); text_input_set_result_callback( text_input, - IrdaApp::text_input_callback, + InfraredApp::text_input_callback, app, app->get_text_store(0), - IrdaAppRemoteManager::max_button_name_length, + InfraredAppRemoteManager::max_button_name_length, true); - view_manager->switch_to(IrdaAppViewManager::ViewType::TextInput); + view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput); } -bool IrdaAppSceneLearnEnterName::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneLearnEnterName::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::TextEditDone) { + if(event->type == InfraredAppEvent::Type::TextEditDone) { auto remote_manager = app->get_remote_manager(); bool result = false; if(app->get_learn_new_remote()) { @@ -48,13 +48,13 @@ bool IrdaAppSceneLearnEnterName::on_event(IrdaApp* app, IrdaAppEvent* event) { if(!result) { app->search_and_switch_to_previous_scene( - {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); } else { - app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnDone); + app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnDone); } } return consumed; } -void IrdaAppSceneLearnEnterName::on_exit(IrdaApp* app) { +void InfraredAppSceneLearnEnterName::on_exit(InfraredApp* app) { } diff --git a/applications/irda/scene/irda_app_scene_learn_success.cpp b/applications/infrared/scene/infrared_app_scene_learn_success.cpp similarity index 61% rename from applications/irda/scene/irda_app_scene_learn_success.cpp rename to applications/infrared/scene/infrared_app_scene_learn_success.cpp index dfb889b0..61d60ab5 100644 --- a/applications/irda/scene/irda_app_scene_learn_success.cpp +++ b/applications/infrared/scene/infrared_app_scene_learn_success.cpp @@ -3,42 +3,44 @@ #include #include -#include "../irda_app.h" -#include "irda.h" +#include "../infrared_app.h" +#include "infrared.h" static void dialog_result_callback(DialogExResult result, void* context) { - auto app = static_cast(context); - IrdaAppEvent event; + auto app = static_cast(context); + InfraredAppEvent event; - event.type = IrdaAppEvent::Type::DialogExSelected; + event.type = InfraredAppEvent::Type::DialogExSelected; event.payload.dialog_ex_result = result; app->get_view_manager()->send_event(&event); } -void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneLearnSuccess::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); DialogEx* dialog_ex = view_manager->get_dialog_ex(); DOLPHIN_DEED(DolphinDeedIrLearnSuccess); app->notify_green_on(); - irda_worker_tx_set_get_signal_callback( - app->get_irda_worker(), irda_worker_tx_get_signal_steady_callback, app); - irda_worker_tx_set_signal_sent_callback( - app->get_irda_worker(), IrdaApp::signal_sent_callback, app); + infrared_worker_tx_set_get_signal_callback( + app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app); + infrared_worker_tx_set_signal_sent_callback( + app->get_infrared_worker(), InfraredApp::signal_sent_callback, app); auto signal = app->get_received_signal(); if(!signal.is_raw()) { auto message = &signal.get_message(); - uint8_t adr_digits = ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4); - uint8_t cmd_digits = ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4); + uint8_t adr_digits = + ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4); + uint8_t cmd_digits = + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4); uint8_t max_digits = MAX(adr_digits, cmd_digits); max_digits = MIN(max_digits, 7); size_t label_x_offset = 63 + (7 - max_digits) * 3; - app->set_text_store(0, "%s", irda_get_protocol_name(message->protocol)); + app->set_text_store(0, "%s", infrared_get_protocol_name(message->protocol)); app->set_text_store( 1, "A: 0x%0*lX\nC: 0x%0*lX\n", @@ -64,24 +66,24 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { dialog_ex_set_context(dialog_ex, app); dialog_ex_enable_extended_events(dialog_ex); - view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); + view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx); } -bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneLearnSuccess::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::Tick) { + if(event->type == InfraredAppEvent::Type::Tick) { /* Send event every tick to suppress any switching off green light */ if(!button_pressed) { app->notify_green_on(); } } - if(event->type == IrdaAppEvent::Type::DialogExSelected) { + if(event->type == InfraredAppEvent::Type::DialogExSelected) { switch(event->payload.dialog_ex_result) { case DialogExResultLeft: consumed = true; if(!button_pressed) { - app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); + app->switch_to_next_scene_without_saving(InfraredApp::Scene::Learn); } break; case DialogExResultRight: { @@ -89,7 +91,7 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { FileWorkerCpp file_worker; if(!button_pressed) { if(file_worker.check_errors()) { - app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); + app->switch_to_next_scene(InfraredApp::Scene::LearnEnterName); } else { app->switch_to_previous_scene(); } @@ -103,21 +105,22 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { auto signal = app->get_received_signal(); if(signal.is_raw()) { - irda_worker_set_raw_signal( - app->get_irda_worker(), + infrared_worker_set_raw_signal( + app->get_infrared_worker(), signal.get_raw_signal().timings, signal.get_raw_signal().timings_cnt); } else { - irda_worker_set_decoded_signal(app->get_irda_worker(), &signal.get_message()); + infrared_worker_set_decoded_signal( + app->get_infrared_worker(), &signal.get_message()); } - irda_worker_tx_start(app->get_irda_worker()); + infrared_worker_tx_start(app->get_infrared_worker()); } break; case DialogExReleaseCenter: if(button_pressed) { button_pressed = false; - irda_worker_tx_stop(app->get_irda_worker()); + infrared_worker_tx_stop(app->get_infrared_worker()); app->notify_green_off(); } break; @@ -126,9 +129,9 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { } } - if(event->type == IrdaAppEvent::Type::Back) { + if(event->type == InfraredAppEvent::Type::Back) { if(!button_pressed) { - app->switch_to_next_scene(IrdaApp::Scene::AskBack); + app->switch_to_next_scene(InfraredApp::Scene::AskBack); } consumed = true; } @@ -136,11 +139,11 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneLearnSuccess::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneLearnSuccess::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); DialogEx* dialog_ex = view_manager->get_dialog_ex(); dialog_ex_reset(dialog_ex); app->notify_green_off(); - irda_worker_tx_set_get_signal_callback(app->get_irda_worker(), nullptr, nullptr); - irda_worker_tx_set_signal_sent_callback(app->get_irda_worker(), nullptr, nullptr); + infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr); + infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr); } diff --git a/applications/irda/scene/irda_app_scene_remote.cpp b/applications/infrared/scene/infrared_app_scene_remote.cpp similarity index 54% rename from applications/irda/scene/irda_app_scene_remote.cpp rename to applications/infrared/scene/infrared_app_scene_remote.cpp index 5f928b41..b99f1c98 100644 --- a/applications/irda/scene/irda_app_scene_remote.cpp +++ b/applications/infrared/scene/infrared_app_scene_remote.cpp @@ -1,9 +1,9 @@ #include #include -#include +#include #include -#include "../irda_app.h" -#include "../irda_app_view_manager.h" +#include "../infrared_app.h" +#include "../infrared_app_view_manager.h" typedef enum { ButtonIndexPlus = -2, @@ -12,15 +12,15 @@ typedef enum { } ButtonIndex; static void button_menu_callback(void* context, int32_t index, InputType type) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; + InfraredApp* app = static_cast(context); + InfraredAppEvent event; if(type == InputTypePress) { - event.type = IrdaAppEvent::Type::MenuSelectedPress; + event.type = InfraredAppEvent::Type::MenuSelectedPress; } else if(type == InputTypeRelease) { - event.type = IrdaAppEvent::Type::MenuSelectedRelease; + event.type = InfraredAppEvent::Type::MenuSelectedRelease; } else if(type == InputTypeShort) { - event.type = IrdaAppEvent::Type::MenuSelected; + event.type = InfraredAppEvent::Type::MenuSelected; } else { furi_assert(0); } @@ -30,17 +30,17 @@ static void button_menu_callback(void* context, int32_t index, InputType type) { app->get_view_manager()->send_event(&event); } -void IrdaAppSceneRemote::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneRemote::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); ButtonMenu* button_menu = view_manager->get_button_menu(); auto remote_manager = app->get_remote_manager(); int i = 0; button_pressed = false; - irda_worker_tx_set_get_signal_callback( - app->get_irda_worker(), irda_worker_tx_get_signal_steady_callback, app); - irda_worker_tx_set_signal_sent_callback( - app->get_irda_worker(), IrdaApp::signal_sent_callback, app); + infrared_worker_tx_set_get_signal_callback( + app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app); + infrared_worker_tx_set_signal_sent_callback( + app->get_infrared_worker(), InfraredApp::signal_sent_callback, app); buttons_names = remote_manager->get_button_list(); i = 0; @@ -60,32 +60,32 @@ void IrdaAppSceneRemote::on_enter(IrdaApp* app) { button_menu_set_selected_item(button_menu, buttonmenu_item_selected); buttonmenu_item_selected = ButtonIndexNA; } - view_manager->switch_to(IrdaAppViewManager::ViewType::ButtonMenu); + view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); } -bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneRemote::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = true; - if((event->type == IrdaAppEvent::Type::MenuSelected) || - (event->type == IrdaAppEvent::Type::MenuSelectedPress) || - (event->type == IrdaAppEvent::Type::MenuSelectedRelease)) { + if((event->type == InfraredAppEvent::Type::MenuSelected) || + (event->type == InfraredAppEvent::Type::MenuSelectedPress) || + (event->type == InfraredAppEvent::Type::MenuSelectedRelease)) { switch(event->payload.menu_index) { case ButtonIndexPlus: - furi_assert(event->type == IrdaAppEvent::Type::MenuSelected); + furi_assert(event->type == InfraredAppEvent::Type::MenuSelected); app->notify_click(); buttonmenu_item_selected = event->payload.menu_index; app->set_learn_new_remote(false); - app->switch_to_next_scene(IrdaApp::Scene::Learn); + app->switch_to_next_scene(InfraredApp::Scene::Learn); break; case ButtonIndexEdit: - furi_assert(event->type == IrdaAppEvent::Type::MenuSelected); + furi_assert(event->type == InfraredAppEvent::Type::MenuSelected); app->notify_click(); buttonmenu_item_selected = event->payload.menu_index; - app->switch_to_next_scene(IrdaApp::Scene::Edit); + app->switch_to_next_scene(InfraredApp::Scene::Edit); break; default: - furi_assert(event->type != IrdaAppEvent::Type::MenuSelected); - bool pressed = (event->type == IrdaAppEvent::Type::MenuSelectedPress); + furi_assert(event->type != InfraredAppEvent::Type::MenuSelected); + bool pressed = (event->type == InfraredAppEvent::Type::MenuSelectedPress); if(pressed && !button_pressed) { button_pressed = true; @@ -94,28 +94,28 @@ bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) { auto button_signal = app->get_remote_manager()->get_button_data(event->payload.menu_index); if(button_signal.is_raw()) { - irda_worker_set_raw_signal( - app->get_irda_worker(), + infrared_worker_set_raw_signal( + app->get_infrared_worker(), button_signal.get_raw_signal().timings, button_signal.get_raw_signal().timings_cnt); } else { - irda_worker_set_decoded_signal( - app->get_irda_worker(), &button_signal.get_message()); + infrared_worker_set_decoded_signal( + app->get_infrared_worker(), &button_signal.get_message()); } DOLPHIN_DEED(DolphinDeedIrSend); - irda_worker_tx_start(app->get_irda_worker()); + infrared_worker_tx_start(app->get_infrared_worker()); } else if(!pressed && button_pressed) { button_pressed = false; - irda_worker_tx_stop(app->get_irda_worker()); + infrared_worker_tx_stop(app->get_infrared_worker()); app->notify_green_off(); } break; } - } else if(event->type == IrdaAppEvent::Type::Back) { + } else if(event->type == InfraredAppEvent::Type::Back) { if(!button_pressed) { app->search_and_switch_to_previous_scene( - {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); + {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList}); } } else { consumed = false; @@ -124,10 +124,10 @@ bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneRemote::on_exit(IrdaApp* app) { - irda_worker_tx_set_get_signal_callback(app->get_irda_worker(), nullptr, nullptr); - irda_worker_tx_set_signal_sent_callback(app->get_irda_worker(), nullptr, nullptr); - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneRemote::on_exit(InfraredApp* app) { + infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr); + infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr); + InfraredAppViewManager* view_manager = app->get_view_manager(); ButtonMenu* button_menu = view_manager->get_button_menu(); button_menu_reset(button_menu); diff --git a/applications/irda/scene/irda_app_scene_remote_list.cpp b/applications/infrared/scene/infrared_app_scene_remote_list.cpp similarity index 57% rename from applications/irda/scene/irda_app_scene_remote_list.cpp rename to applications/infrared/scene/infrared_app_scene_remote_list.cpp index c20fbafb..13962587 100644 --- a/applications/irda/scene/irda_app_scene_remote_list.cpp +++ b/applications/infrared/scene/infrared_app_scene_remote_list.cpp @@ -1,9 +1,9 @@ -#include "../irda_app.h" -#include "irda/irda_app_event.h" +#include "../infrared_app.h" +#include "infrared/infrared_app_event.h" #include #include -void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { +void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { furi_assert(app); FileWorkerCpp file_worker; @@ -13,23 +13,24 @@ void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { auto last_selected_remote = remote_manager->get_remote_name(); const char* last_selected_remote_name = last_selected_remote.size() ? last_selected_remote.c_str() : nullptr; - auto filename_ts = std::make_unique(IrdaAppRemoteManager::max_remote_name_length); + auto filename_ts = + std::make_unique(InfraredAppRemoteManager::max_remote_name_length); - IrdaAppViewManager* view_manager = app->get_view_manager(); + InfraredAppViewManager* view_manager = app->get_view_manager(); ButtonMenu* button_menu = view_manager->get_button_menu(); button_menu_reset(button_menu); - view_manager->switch_to(IrdaAppViewManager::ViewType::ButtonMenu); + view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); file_select_result = file_worker.file_select( - IrdaApp::irda_directory, - IrdaApp::irda_extension, + InfraredApp::infrared_directory, + InfraredApp::infrared_extension, filename_ts->text, filename_ts->text_size, last_selected_remote_name); if(file_select_result) { if(remote_manager->load(std::string(filename_ts->text))) { - app->switch_to_next_scene(IrdaApp::Scene::Remote); + app->switch_to_next_scene(InfraredApp::Scene::Remote); result = true; } } @@ -39,11 +40,11 @@ void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { } } -bool IrdaAppSceneRemoteList::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneRemoteList::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; return consumed; } -void IrdaAppSceneRemoteList::on_exit(IrdaApp* app) { +void InfraredAppSceneRemoteList::on_exit(InfraredApp* app) { } diff --git a/applications/irda/scene/irda_app_scene_start.cpp b/applications/infrared/scene/infrared_app_scene_start.cpp similarity index 62% rename from applications/irda/scene/irda_app_scene_start.cpp rename to applications/infrared/scene/infrared_app_scene_start.cpp index 62dcbbc5..dd9a2c15 100644 --- a/applications/irda/scene/irda_app_scene_start.cpp +++ b/applications/infrared/scene/infrared_app_scene_start.cpp @@ -1,4 +1,4 @@ -#include "../irda_app.h" +#include "../infrared_app.h" typedef enum { SubmenuIndexUniversalLibrary, @@ -7,17 +7,17 @@ typedef enum { } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; + InfraredApp* app = static_cast(context); + InfraredAppEvent event; - event.type = IrdaAppEvent::Type::MenuSelected; + event.type = InfraredAppEvent::Type::MenuSelected; event.payload.menu_index = index; app->get_view_manager()->send_event(&event); } -void IrdaAppSceneStart::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneStart::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); submenu_add_item( @@ -28,24 +28,24 @@ void IrdaAppSceneStart::on_enter(IrdaApp* app) { submenu_set_selected_item(submenu, submenu_item_selected); submenu_item_selected = 0; - view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); + view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); } -bool IrdaAppSceneStart::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneStart::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::MenuSelected) { + if(event->type == InfraredAppEvent::Type::MenuSelected) { submenu_item_selected = event->payload.menu_index; switch(event->payload.menu_index) { case SubmenuIndexUniversalLibrary: - app->switch_to_next_scene(IrdaApp::Scene::Universal); + app->switch_to_next_scene(InfraredApp::Scene::Universal); break; case SubmenuIndexLearnNewRemote: app->set_learn_new_remote(true); - app->switch_to_next_scene(IrdaApp::Scene::Learn); + app->switch_to_next_scene(InfraredApp::Scene::Learn); break; case SubmenuIndexSavedRemotes: - app->switch_to_next_scene(IrdaApp::Scene::RemoteList); + app->switch_to_next_scene(InfraredApp::Scene::RemoteList); break; default: furi_assert(0); @@ -57,8 +57,8 @@ bool IrdaAppSceneStart::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneStart::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneStart::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); app->get_remote_manager()->reset_remote(); diff --git a/applications/irda/scene/irda_app_scene_universal.cpp b/applications/infrared/scene/infrared_app_scene_universal.cpp similarity index 52% rename from applications/irda/scene/irda_app_scene_universal.cpp rename to applications/infrared/scene/infrared_app_scene_universal.cpp index 01134400..b97724fc 100644 --- a/applications/irda/scene/irda_app_scene_universal.cpp +++ b/applications/infrared/scene/infrared_app_scene_universal.cpp @@ -1,4 +1,4 @@ -#include "../irda_app.h" +#include "../infrared_app.h" typedef enum { SubmenuIndexUniversalTV, @@ -7,40 +7,40 @@ typedef enum { } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; + InfraredApp* app = static_cast(context); + InfraredAppEvent event; - event.type = IrdaAppEvent::Type::MenuSelected; + event.type = InfraredAppEvent::Type::MenuSelected; event.payload.menu_index = index; app->get_view_manager()->send_event(&event); } -void IrdaAppSceneUniversal::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneUniversal::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); submenu_add_item(submenu, "TV's", SubmenuIndexUniversalTV, submenu_callback, app); submenu_set_selected_item(submenu, submenu_item_selected); submenu_item_selected = 0; - view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); + view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); } -bool IrdaAppSceneUniversal::on_event(IrdaApp* app, IrdaAppEvent* event) { +bool InfraredAppSceneUniversal::on_event(InfraredApp* app, InfraredAppEvent* event) { bool consumed = false; - if(event->type == IrdaAppEvent::Type::MenuSelected) { + if(event->type == InfraredAppEvent::Type::MenuSelected) { submenu_item_selected = event->payload.menu_index; switch(event->payload.menu_index) { case SubmenuIndexUniversalTV: - app->switch_to_next_scene(IrdaApp::Scene::UniversalTV); + app->switch_to_next_scene(InfraredApp::Scene::UniversalTV); break; case SubmenuIndexUniversalAudio: - // app->switch_to_next_scene(IrdaApp::Scene::UniversalAudio); + // app->switch_to_next_scene(InfraredApp::Scene::UniversalAudio); break; case SubmenuIndexUniversalAirConditioner: - // app->switch_to_next_scene(IrdaApp::Scene::UniversalAirConditioner); + // app->switch_to_next_scene(InfraredApp::Scene::UniversalAirConditioner); break; } consumed = true; @@ -49,8 +49,8 @@ bool IrdaAppSceneUniversal::on_event(IrdaApp* app, IrdaAppEvent* event) { return consumed; } -void IrdaAppSceneUniversal::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneUniversal::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); submenu_reset(submenu); diff --git a/applications/infrared/scene/infrared_app_scene_universal_common.cpp b/applications/infrared/scene/infrared_app_scene_universal_common.cpp new file mode 100644 index 00000000..459401d3 --- /dev/null +++ b/applications/infrared/scene/infrared_app_scene_universal_common.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include + +#include "../infrared_app.h" +#include "infrared/infrared_app_event.h" +#include "infrared/infrared_app_view_manager.h" +#include "infrared/scene/infrared_app_scene.h" +#include "../view/infrared_progress_view.h" + +void InfraredAppSceneUniversalCommon::infrared_app_item_callback(void* context, uint32_t index) { + InfraredApp* app = static_cast(context); + InfraredAppEvent event; + + event.type = InfraredAppEvent::Type::ButtonPanelPressed; + event.payload.menu_index = index; + + app->get_view_manager()->send_event(&event); +} + +static void infrared_progress_back_callback(void* context) { + furi_assert(context); + auto app = static_cast(context); + + InfraredAppEvent infrared_event = { + .type = InfraredAppEvent::Type::Back, + }; + app->get_view_manager()->clear_events(); + app->get_view_manager()->send_event(&infrared_event); +} + +void InfraredAppSceneUniversalCommon::hide_popup(InfraredApp* app) { + auto stack_view = app->get_view_manager()->get_universal_view_stack(); + auto progress_view = app->get_view_manager()->get_progress(); + view_stack_remove_view(stack_view, infrared_progress_view_get_view(progress_view)); +} + +void InfraredAppSceneUniversalCommon::show_popup(InfraredApp* app, int record_amount) { + auto stack_view = app->get_view_manager()->get_universal_view_stack(); + auto progress_view = app->get_view_manager()->get_progress(); + infrared_progress_view_set_progress_total(progress_view, record_amount); + infrared_progress_view_set_back_callback(progress_view, infrared_progress_back_callback, app); + view_stack_add_view(stack_view, infrared_progress_view_get_view(progress_view)); +} + +bool InfraredAppSceneUniversalCommon::progress_popup(InfraredApp* app) { + auto progress_view = app->get_view_manager()->get_progress(); + return infrared_progress_view_increase_progress(progress_view); +} + +bool InfraredAppSceneUniversalCommon::on_event(InfraredApp* app, InfraredAppEvent* event) { + bool consumed = false; + + if(brute_force_started) { + if(event->type == InfraredAppEvent::Type::Tick) { + auto view_manager = app->get_view_manager(); + InfraredAppEvent tick_event = {.type = InfraredAppEvent::Type::Tick}; + view_manager->send_event(&tick_event); + bool result = brute_force.send_next_bruteforce(); + if(result) { + result = progress_popup(app); + } + if(!result) { + brute_force.stop_bruteforce(); + brute_force_started = false; + hide_popup(app); + } + consumed = true; + } else if(event->type == InfraredAppEvent::Type::Back) { + brute_force_started = false; + brute_force.stop_bruteforce(); + hide_popup(app); + consumed = true; + } + } else { + if(event->type == InfraredAppEvent::Type::ButtonPanelPressed) { + int record_amount = 0; + if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) { + DOLPHIN_DEED(DolphinDeedIrBruteForce); + brute_force_started = true; + show_popup(app, record_amount); + } else { + app->switch_to_previous_scene(); + } + consumed = true; + } else if(event->type == InfraredAppEvent::Type::Back) { + app->switch_to_previous_scene(); + consumed = true; + } + } + + return consumed; +} + +void InfraredAppSceneUniversalCommon::on_exit(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); + ButtonPanel* button_panel = view_manager->get_button_panel(); + button_panel_reset(button_panel); +} diff --git a/applications/irda/scene/irda_app_scene_universal_tv.cpp b/applications/infrared/scene/infrared_app_scene_universal_tv.cpp similarity index 80% rename from applications/irda/scene/irda_app_scene_universal_tv.cpp rename to applications/infrared/scene/infrared_app_scene_universal_tv.cpp index 563d59df..a12481ec 100644 --- a/applications/irda/scene/irda_app_scene_universal_tv.cpp +++ b/applications/infrared/scene/infrared_app_scene_universal_tv.cpp @@ -1,11 +1,11 @@ #include #include #include -#include "irda/scene/irda_app_scene.h" -#include "irda/irda_app.h" +#include "infrared/scene/infrared_app_scene.h" +#include "infrared/infrared_app.h" -void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); +void InfraredAppSceneUniversalTV::on_enter(InfraredApp* app) { + InfraredAppViewManager* view_manager = app->get_view_manager(); ButtonPanel* button_panel = view_manager->get_button_panel(); button_panel_reserve(button_panel, 2, 3); @@ -19,7 +19,7 @@ void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { 19, &I_Power_25x27, &I_Power_hvr_25x27, - irda_app_item_callback, + infrared_app_item_callback, app); brute_force.add_record(i, "POWER"); ++i; @@ -32,7 +32,7 @@ void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { 19, &I_Mute_25x27, &I_Mute_hvr_25x27, - irda_app_item_callback, + infrared_app_item_callback, app); brute_force.add_record(i, "MUTE"); ++i; @@ -45,12 +45,21 @@ void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { 66, &I_Vol_up_25x27, &I_Vol_up_hvr_25x27, - irda_app_item_callback, + infrared_app_item_callback, app); brute_force.add_record(i, "VOL+"); ++i; button_panel_add_item( - button_panel, i, 1, 1, 36, 66, &I_Up_25x27, &I_Up_hvr_25x27, irda_app_item_callback, app); + button_panel, + i, + 1, + 1, + 36, + 66, + &I_Up_25x27, + &I_Up_hvr_25x27, + infrared_app_item_callback, + app); brute_force.add_record(i, "CH+"); ++i; button_panel_add_item( @@ -62,7 +71,7 @@ void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { 98, &I_Vol_down_25x27, &I_Vol_down_hvr_25x27, - irda_app_item_callback, + infrared_app_item_callback, app); brute_force.add_record(i, "VOL-"); ++i; @@ -75,7 +84,7 @@ void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { 98, &I_Down_25x27, &I_Down_hvr_25x27, - irda_app_item_callback, + infrared_app_item_callback, app); brute_force.add_record(i, "CH-"); @@ -83,7 +92,7 @@ void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) { button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol"); button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch"); - view_manager->switch_to(IrdaAppViewManager::ViewType::UniversalRemote); + view_manager->switch_to(InfraredAppViewManager::ViewId::UniversalRemote); auto stack_view = app->get_view_manager()->get_universal_view_stack(); auto loading_view = app->get_view_manager()->get_loading(); diff --git a/applications/irda/view/irda_progress_view.c b/applications/infrared/view/infrared_progress_view.c similarity index 62% rename from applications/irda/view/irda_progress_view.c rename to applications/infrared/view/infrared_progress_view.c index 4a914a17..c9075147 100644 --- a/applications/irda/view/irda_progress_view.c +++ b/applications/infrared/view/infrared_progress_view.c @@ -7,26 +7,26 @@ #include "m-string.h" #include #include -#include "irda_progress_view.h" +#include "infrared_progress_view.h" #include "gui/modules/button_panel.h" #include -struct IrdaProgressView { +struct InfraredProgressView { View* view; - IrdaProgressViewBackCallback back_callback; + InfraredProgressViewBackCallback back_callback; void* context; }; typedef struct { size_t progress; size_t progress_total; -} IrdaProgressViewModel; +} InfraredProgressViewModel; -bool irda_progress_view_increase_progress(IrdaProgressView* progress) { +bool infrared_progress_view_increase_progress(InfraredProgressView* progress) { furi_assert(progress); bool result = false; - IrdaProgressViewModel* model = view_get_model(progress->view); + InfraredProgressViewModel* model = view_get_model(progress->view); if(model->progress < model->progress_total) { ++model->progress; result = model->progress < model->progress_total; @@ -36,8 +36,8 @@ bool irda_progress_view_increase_progress(IrdaProgressView* progress) { return result; } -static void irda_progress_view_draw_callback(Canvas* canvas, void* _model) { - IrdaProgressViewModel* model = (IrdaProgressViewModel*)_model; +static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) { + InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model; uint8_t x = 0; uint8_t y = 36; @@ -63,16 +63,18 @@ static void irda_progress_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); } -void irda_progress_view_set_progress_total(IrdaProgressView* progress, uint16_t progress_total) { +void infrared_progress_view_set_progress_total( + InfraredProgressView* progress, + uint16_t progress_total) { furi_assert(progress); - IrdaProgressViewModel* model = view_get_model(progress->view); + InfraredProgressViewModel* model = view_get_model(progress->view); model->progress = 0; model->progress_total = progress_total; view_commit_model(progress->view, false); } -bool irda_progress_view_input_callback(InputEvent* event, void* context) { - IrdaProgressView* instance = context; +bool infrared_progress_view_input_callback(InputEvent* event, void* context) { + InfraredProgressView* instance = context; if((event->type == InputTypeShort) && (event->key == InputKeyBack)) { if(instance->back_callback) { @@ -83,36 +85,36 @@ bool irda_progress_view_input_callback(InputEvent* event, void* context) { return true; } -IrdaProgressView* irda_progress_view_alloc(void) { - IrdaProgressView* instance = malloc(sizeof(IrdaProgressView)); +InfraredProgressView* infrared_progress_view_alloc(void) { + InfraredProgressView* instance = malloc(sizeof(InfraredProgressView)); instance->view = view_alloc(); - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(IrdaProgressViewModel)); - IrdaProgressViewModel* model = view_get_model(instance->view); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(InfraredProgressViewModel)); + InfraredProgressViewModel* model = view_get_model(instance->view); model->progress = 0; model->progress_total = 0; view_commit_model(instance->view, false); - view_set_draw_callback(instance->view, irda_progress_view_draw_callback); - view_set_input_callback(instance->view, irda_progress_view_input_callback); + view_set_draw_callback(instance->view, infrared_progress_view_draw_callback); + view_set_input_callback(instance->view, infrared_progress_view_input_callback); view_set_context(instance->view, instance); return instance; } -void irda_progress_view_free(IrdaProgressView* progress) { +void infrared_progress_view_free(InfraredProgressView* progress) { view_free(progress->view); free(progress); } -void irda_progress_view_set_back_callback( - IrdaProgressView* instance, - IrdaProgressViewBackCallback callback, +void infrared_progress_view_set_back_callback( + InfraredProgressView* instance, + InfraredProgressViewBackCallback callback, void* context) { furi_assert(instance); instance->back_callback = callback; instance->context = context; } -View* irda_progress_view_get_view(IrdaProgressView* instance) { +View* infrared_progress_view_get_view(InfraredProgressView* instance) { furi_assert(instance); furi_assert(instance->view); return instance->view; diff --git a/applications/infrared/view/infrared_progress_view.h b/applications/infrared/view/infrared_progress_view.h new file mode 100644 index 00000000..e8f76ba1 --- /dev/null +++ b/applications/infrared/view/infrared_progress_view.h @@ -0,0 +1,68 @@ +/** + * @file infrared_progress_view.h + * Infrared: Custom Infrared view module. + * It shows popup progress bar during brute force. + */ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Anonumous instance */ +typedef struct InfraredProgressView InfraredProgressView; + +/** Callback for back button handling */ +typedef void (*InfraredProgressViewBackCallback)(void*); + +/** Allocate and initialize Infrared view + * + * @retval new allocated instance + */ +InfraredProgressView* infrared_progress_view_alloc(); + +/** Free previously allocated Progress view module instance + * + * @param instance to free + */ +void infrared_progress_view_free(InfraredProgressView* instance); + +/** Get progress view module view + * + * @param instance view module + * @retval view + */ +View* infrared_progress_view_get_view(InfraredProgressView* instance); + +/** Increase progress on progress view module + * + * @param instance view module + * @retval true - value is incremented and maximum is reached, + * false - value is incremented and maximum is not reached + */ +bool infrared_progress_view_increase_progress(InfraredProgressView* instance); + +/** Set maximum progress value + * + * @param instance - view module + * @param progress_max - maximum value of progress + */ +void infrared_progress_view_set_progress_total( + InfraredProgressView* instance, + uint16_t progress_max); + +/** Set back button callback + * + * @param instance - view module + * @param callback - callback to call for back button + * @param context - context to pass to callback + */ +void infrared_progress_view_set_back_callback( + InfraredProgressView* instance, + InfraredProgressViewBackCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/infrared_monitor/infrared_monitor.c b/applications/infrared_monitor/infrared_monitor.c new file mode 100644 index 00000000..08691dfa --- /dev/null +++ b/applications/infrared_monitor/infrared_monitor.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INFRARED_TIMINGS_SIZE 700 + +typedef struct { + uint32_t timing_cnt; + struct { + uint8_t level; + uint32_t duration; + } timing[INFRARED_TIMINGS_SIZE]; +} InfraredDelaysArray; + +typedef struct { + char display_text[64]; + osMessageQueueId_t event_queue; + InfraredDelaysArray delays; + InfraredWorker* worker; + ViewPort* view_port; +} InfraredMonitor; + +void infrared_monitor_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + InfraredMonitor* infrared_monitor = (InfraredMonitor*)ctx; + + if((input_event->type == InputTypeShort) && (input_event->key == InputKeyBack)) { + osMessageQueuePut(infrared_monitor->event_queue, input_event, 0, 0); + } +} + +static void infrared_monitor_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(canvas); + furi_assert(ctx); + InfraredMonitor* infrared_monitor = (InfraredMonitor*)ctx; + + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "INFRARED monitor\n"); + canvas_set_font(canvas, FontKeyboard); + if(strlen(infrared_monitor->display_text)) { + elements_multiline_text_aligned( + canvas, 64, 43, AlignCenter, AlignCenter, infrared_monitor->display_text); + } +} + +static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { + furi_assert(context); + furi_assert(received_signal); + InfraredMonitor* infrared_monitor = context; + + if(infrared_worker_signal_is_decoded(received_signal)) { + const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); + snprintf( + infrared_monitor->display_text, + sizeof(infrared_monitor->display_text), + "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n", + infrared_get_protocol_name(message->protocol), + ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), + message->address, + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), + message->command, + message->repeat ? " R" : ""); + view_port_update(infrared_monitor->view_port); + printf( + "== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n", + infrared_get_protocol_name(message->protocol), + ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), + message->address, + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), + message->command, + message->repeat ? " R" : ""); + } else { + const uint32_t* timings; + size_t timings_cnt; + infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); + snprintf( + infrared_monitor->display_text, + sizeof(infrared_monitor->display_text), + "RAW\n%d samples\n", + timings_cnt); + view_port_update(infrared_monitor->view_port); + printf("RAW, %d samples:\r\n", timings_cnt); + for(size_t i = 0; i < timings_cnt; ++i) { + printf("%lu ", timings[i]); + } + printf("\r\n"); + } +} + +int32_t infrared_monitor_app(void* p) { + (void)p; + + InfraredMonitor* infrared_monitor = malloc(sizeof(InfraredMonitor)); + infrared_monitor->display_text[0] = 0; + infrared_monitor->event_queue = osMessageQueueNew(1, sizeof(InputEvent), NULL); + infrared_monitor->view_port = view_port_alloc(); + Gui* gui = furi_record_open("gui"); + + view_port_draw_callback_set( + infrared_monitor->view_port, infrared_monitor_draw_callback, infrared_monitor); + view_port_input_callback_set( + infrared_monitor->view_port, infrared_monitor_input_callback, infrared_monitor); + + gui_add_view_port(gui, infrared_monitor->view_port, GuiLayerFullscreen); + + infrared_monitor->worker = infrared_worker_alloc(); + infrared_worker_rx_start(infrared_monitor->worker); + infrared_worker_rx_set_received_signal_callback( + infrared_monitor->worker, signal_received_callback, infrared_monitor); + infrared_worker_rx_enable_blink_on_receiving(infrared_monitor->worker, true); + + while(1) { + InputEvent event; + if(osOK == osMessageQueueGet(infrared_monitor->event_queue, &event, NULL, 50)) { + if((event.type == InputTypeShort) && (event.key == InputKeyBack)) { + break; + } + } + } + + infrared_worker_rx_stop(infrared_monitor->worker); + infrared_worker_free(infrared_monitor->worker); + osMessageQueueDelete(infrared_monitor->event_queue); + view_port_enabled_set(infrared_monitor->view_port, false); + gui_remove_view_port(gui, infrared_monitor->view_port); + view_port_free(infrared_monitor->view_port); + furi_record_close("gui"); + free(infrared_monitor); + + return 0; +} diff --git a/applications/irda/helpers/irda_parser.h b/applications/irda/helpers/irda_parser.h deleted file mode 100644 index d8065607..00000000 --- a/applications/irda/helpers/irda_parser.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "../irda_app_signal.h" -#include -#include - -bool irda_parser_save_signal( - FlipperFormat* ff, - const IrdaAppSignal& signal, - const std::string& name); -bool irda_parser_read_signal(FlipperFormat* ff, IrdaAppSignal& signal, std::string& name); -bool irda_parser_is_parsed_signal_valid(const IrdaMessage* signal); -bool irda_parser_is_raw_signal_valid(uint32_t frequency, float duty_cycle, uint32_t timings_cnt); diff --git a/applications/irda/irda_app.h b/applications/irda/irda_app.h deleted file mode 100644 index 195aff34..00000000 --- a/applications/irda/irda_app.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once -#include -#include -#include -#include "scene/irda_app_scene.h" -#include "scene/irda_app_scene.h" -#include "irda_app_view_manager.h" -#include "irda_app_remote_manager.h" -#include -#include -#include -#include -#include "irda_app_view_manager.h" - -class IrdaApp { -public: - enum class EditElement : uint8_t { - Button, - Remote, - }; - enum class EditAction : uint8_t { - Rename, - Delete, - }; - enum class Scene : uint8_t { - Exit, - Start, - Universal, - UniversalTV, - UniversalAudio, - UniversalAirConditioner, - Learn, - LearnSuccess, - LearnEnterName, - LearnDone, - AskBack, - Remote, - RemoteList, - Edit, - EditKeySelect, - EditRename, - EditDelete, - EditRenameDone, - EditDeleteDone, - }; - - int32_t run(void* args); - void switch_to_next_scene(Scene index); - void switch_to_next_scene_without_saving(Scene index); - bool switch_to_previous_scene(uint8_t count = 1); - Scene get_previous_scene(); - IrdaAppViewManager* get_view_manager(); - void set_text_store(uint8_t index, const char* text...); - char* get_text_store(uint8_t index); - uint8_t get_text_store_size(); - IrdaAppRemoteManager* get_remote_manager(); - - IrdaWorker* get_irda_worker(); - const IrdaAppSignal& get_received_signal() const; - void set_received_signal(const IrdaAppSignal& signal); - - void search_and_switch_to_previous_scene(const std::initializer_list& scenes_list); - - void set_edit_element(EditElement value); - EditElement get_edit_element(void); - - void set_edit_action(EditAction value); - EditAction get_edit_action(void); - - bool get_learn_new_remote(); - void set_learn_new_remote(bool value); - - enum : int { - ButtonNA = -1, - }; - int get_current_button(); - void set_current_button(int value); - - void notify_success(); - void notify_red_blink(); - void notify_sent_just_learnt(); - void notify_green_on(); - void notify_green_off(); - void notify_click(); - void notify_click_and_green_blink(); - void notify_blink_green(); - - static void text_input_callback(void* context); - static void popup_callback(void* context); - static void signal_sent_callback(void* context); - - IrdaApp(); - ~IrdaApp(); - - static constexpr const char* irda_directory = "/any/irda"; - static constexpr const char* irda_extension = ".ir"; - static constexpr const uint32_t max_raw_timings_in_signal = 512; - static constexpr const uint32_t max_line_length = - (9 + 1) * IrdaApp::max_raw_timings_in_signal + 100; - -private: - static constexpr const uint8_t text_store_size = 128; - static constexpr const uint8_t text_store_max = 2; - char text_store[text_store_max][text_store_size + 1]; - bool learn_new_remote; - EditElement element; - EditAction action; - uint32_t current_button; - - NotificationApp* notification; - IrdaAppViewManager view_manager; - IrdaAppRemoteManager remote_manager; - IrdaWorker* irda_worker; - IrdaAppSignal received_signal; - - std::forward_list previous_scenes_list; - Scene current_scene = Scene::Start; - - std::map scenes = { - {Scene::Start, new IrdaAppSceneStart()}, - {Scene::Universal, new IrdaAppSceneUniversal()}, - {Scene::UniversalTV, new IrdaAppSceneUniversalTV()}, - // {Scene::UniversalAudio, new IrdaAppSceneUniversalAudio()}, - {Scene::Learn, new IrdaAppSceneLearn()}, - {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()}, - {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()}, - {Scene::LearnDone, new IrdaAppSceneLearnDone()}, - {Scene::AskBack, new IrdaAppSceneAskBack()}, - {Scene::Remote, new IrdaAppSceneRemote()}, - {Scene::RemoteList, new IrdaAppSceneRemoteList()}, - {Scene::Edit, new IrdaAppSceneEdit()}, - {Scene::EditKeySelect, new IrdaAppSceneEditKeySelect()}, - {Scene::EditRename, new IrdaAppSceneEditRename()}, - {Scene::EditDelete, new IrdaAppSceneEditDelete()}, - {Scene::EditRenameDone, new IrdaAppSceneEditRenameDone()}, - {Scene::EditDeleteDone, new IrdaAppSceneEditDeleteDone()}, - }; -}; diff --git a/applications/irda/irda_app_brute_force.h b/applications/irda/irda_app_brute_force.h deleted file mode 100644 index 97d78dc6..00000000 --- a/applications/irda/irda_app_brute_force.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include - -class IrdaAppBruteForce { - const char* universal_db_filename; - std::string current_record; - FlipperFormat* ff; - - typedef struct { - int index; - int amount; - } Record; - - // 'key' is record name, because we have to search by both, index and name, - // but index search has place once per button press, and should not be - // noticed, but name search should occur during entering universal menu, - // and will go through container for every record in file, that's why - // more critical to have faster search by record name. - std::unordered_map records; - -public: - bool calculate_messages(); - void stop_bruteforce(); - bool send_next_bruteforce(); - bool start_bruteforce(int index, int& record_amount); - void add_record(int index, const char* name); - - IrdaAppBruteForce(const char* filename) - : universal_db_filename(filename) { - } - ~IrdaAppBruteForce() { - } -}; diff --git a/applications/irda/irda_app_event.h b/applications/irda/irda_app_event.h deleted file mode 100644 index 752ce09f..00000000 --- a/applications/irda/irda_app_event.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include -#include - -class IrdaAppEvent { -public: - enum class Type : uint8_t { - Tick, - Exit, - Back, - MenuSelected, - MenuSelectedPress, - MenuSelectedRelease, - DialogExSelected, - NextScene, - IrdaMessageReceived, - TextEditDone, - PopupTimer, - ButtonPanelPressed, - }; - - union { - int32_t menu_index; - DialogExResult dialog_ex_result; - } payload; - - Type type; -}; diff --git a/applications/irda/irda_app_remote_manager.h b/applications/irda/irda_app_remote_manager.h deleted file mode 100644 index 10f1fb12..00000000 --- a/applications/irda/irda_app_remote_manager.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include "irda_app_signal.h" - -#include -#include - -#include -#include -#include -#include - -class IrdaAppRemoteButton { - friend class IrdaAppRemoteManager; - std::string name; - IrdaAppSignal signal; - -public: - IrdaAppRemoteButton(const char* name, const IrdaAppSignal& signal) - : name(name) - , signal(signal) { - } - - IrdaAppRemoteButton(const char* name, IrdaAppSignal&& signal) - : name(name) - , signal(std::move(signal)) { - } - ~IrdaAppRemoteButton() { - } -}; - -class IrdaAppRemote { - friend class IrdaAppRemoteManager; - std::vector buttons; - std::string name; - -public: - IrdaAppRemote(const std::string& name) - : name(name) { - } - - IrdaAppRemote& operator=(std::string& new_name) noexcept { - name = new_name; - buttons.clear(); - return *this; - } -}; - -class IrdaAppRemoteManager { - std::unique_ptr remote; - std::string make_full_name(const std::string& remote_name) const; - std::string make_remote_name(const std::string& full_name) const; - -public: - static constexpr const uint32_t max_button_name_length = 22; - static constexpr const uint32_t max_remote_name_length = 22; - bool add_remote_with_button(const char* button_name, const IrdaAppSignal& signal); - bool add_button(const char* button_name, const IrdaAppSignal& signal); - - int find_remote_name(const std::vector& strings); - bool rename_button(uint32_t index, const char* str); - bool rename_remote(const char* str); - std::string find_vacant_remote_name(const std::string& name); - - std::vector get_button_list() const; - std::string get_button_name(uint32_t index); - std::string get_remote_name(); - size_t get_number_of_buttons(); - const IrdaAppSignal& get_button_data(size_t index) const; - bool delete_button(uint32_t index); - bool delete_remote(); - void reset_remote(); - - bool store(); - bool load(const std::string& name); -}; diff --git a/applications/irda/irda_app_signal.h b/applications/irda/irda_app_signal.h deleted file mode 100644 index 3f922f06..00000000 --- a/applications/irda/irda_app_signal.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once -#include -#include -#include -#include - -class IrdaAppSignal { -public: - typedef struct { - size_t timings_cnt; - uint32_t* timings; - uint32_t frequency; - float duty_cycle; - } RawSignal; - -private: - bool raw_signal; - union { - IrdaMessage message; - RawSignal raw; - } payload; - - void - copy_raw_signal(const uint32_t* timings, size_t size, uint32_t frequency, float duty_cycle); - void clear_timings(); - -public: - IrdaAppSignal() { - raw_signal = false; - payload.message.protocol = IrdaProtocolUnknown; - } - - ~IrdaAppSignal() { - clear_timings(); - } - - IrdaAppSignal( - const uint32_t* timings, - size_t timings_cnt, - uint32_t frequency, - float duty_cycle); - IrdaAppSignal(const IrdaMessage* irda_message); - - IrdaAppSignal(const IrdaAppSignal& other); - IrdaAppSignal(IrdaAppSignal&& other); - - IrdaAppSignal& operator=(const IrdaAppSignal& signal); - - void set_message(const IrdaMessage* irda_message); - void - set_raw_signal(uint32_t* timings, size_t timings_cnt, uint32_t frequency, float duty_cycle); - - void transmit() const; - - bool is_raw(void) const { - return raw_signal; - } - - const IrdaMessage& get_message(void) const { - furi_assert(!raw_signal); - return payload.message; - } - - const RawSignal& get_raw_signal(void) const { - furi_assert(raw_signal); - return payload.raw; - } -}; diff --git a/applications/irda/irda_app_view_manager.h b/applications/irda/irda_app_view_manager.h deleted file mode 100644 index 358a3839..00000000 --- a/applications/irda/irda_app_view_manager.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "irda_app_event.h" -#include "view/irda_progress_view.h" - -class IrdaAppViewManager { -public: - enum class ViewType : uint8_t { - DialogEx, - TextInput, - Submenu, - ButtonMenu, - UniversalRemote, - Popup, - }; - - IrdaAppViewManager(); - ~IrdaAppViewManager(); - - void switch_to(ViewType type); - - void receive_event(IrdaAppEvent* event); - void send_event(IrdaAppEvent* event); - void clear_events(); - - DialogEx* get_dialog_ex(); - Submenu* get_submenu(); - Popup* get_popup(); - TextInput* get_text_input(); - ButtonMenu* get_button_menu(); - ButtonPanel* get_button_panel(); - ViewStack* get_universal_view_stack(); - IrdaProgressView* get_progress(); - Loading* get_loading(); - - osMessageQueueId_t get_event_queue(); - - uint32_t previous_view_callback(void* context); - -private: - ViewDispatcher* view_dispatcher; - Gui* gui; - TextInput* text_input; - DialogEx* dialog_ex; - Submenu* submenu; - Popup* popup; - ButtonMenu* button_menu; - ButtonPanel* button_panel; - ViewStack* universal_view_stack; - IrdaProgressView* progress_view; - Loading* loading_view; - - osMessageQueueId_t event_queue; - - void add_view(ViewType view_type, View* view); -}; diff --git a/applications/irda/irda_runner.cpp b/applications/irda/irda_runner.cpp deleted file mode 100644 index d8cf9b67..00000000 --- a/applications/irda/irda_runner.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "irda_app.h" - -extern "C" int32_t irda_app(void* p) { - IrdaApp* app = new IrdaApp(); - int32_t result = app->run(p); - delete app; - - return result; -} diff --git a/applications/irda/scene/irda_app_scene.h b/applications/irda/scene/irda_app_scene.h deleted file mode 100644 index 458a92ad..00000000 --- a/applications/irda/scene/irda_app_scene.h +++ /dev/null @@ -1,184 +0,0 @@ -#pragma once -#include "../irda_app_event.h" -#include -#include "irda.h" -#include -#include -#include "../irda_app_brute_force.h" - -class IrdaApp; - -class IrdaAppScene { -public: - virtual void on_enter(IrdaApp* app) = 0; - virtual bool on_event(IrdaApp* app, IrdaAppEvent* event) = 0; - virtual void on_exit(IrdaApp* app) = 0; - virtual ~IrdaAppScene(){}; - -private: -}; - -class IrdaAppSceneStart : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - -private: - uint32_t submenu_item_selected = 0; -}; - -class IrdaAppSceneUniversal : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - -private: - uint32_t submenu_item_selected = 0; -}; - -class IrdaAppSceneLearn : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneLearnSuccess : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - bool button_pressed = false; -}; - -class IrdaAppSceneLearnEnterName : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneLearnDone : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneRemote : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - -private: - std::vector buttons_names; - uint32_t buttonmenu_item_selected = 0; - bool button_pressed = false; -}; - -class IrdaAppSceneRemoteList : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - -private: - uint32_t submenu_item_selected = 0; - std::vector remote_names; -}; - -class IrdaAppSceneAskBack : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneEdit : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - -private: - uint32_t submenu_item_selected = 0; -}; - -class IrdaAppSceneEditKeySelect : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - -private: - std::vector buttons_names; -}; - -class IrdaAppSceneEditRename : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneEditDelete : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneEditRenameDone : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneEditDeleteDone : public IrdaAppScene { -public: - void on_enter(IrdaApp* app) final; - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; -}; - -class IrdaAppSceneUniversalCommon : public IrdaAppScene { - bool brute_force_started = false; - -protected: - bool on_event(IrdaApp* app, IrdaAppEvent* event) final; - void on_exit(IrdaApp* app) final; - IrdaAppBruteForce brute_force; - void remove_popup(IrdaApp* app); - void show_popup(IrdaApp* app, int record_amount); - bool progress_popup(IrdaApp* app); - static void irda_app_item_callback(void* context, uint32_t index); - IrdaAppSceneUniversalCommon(const char* filename) - : brute_force(filename) { - } - ~IrdaAppSceneUniversalCommon() { - } -}; - -class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon { -public: - void on_enter(IrdaApp* app) final; - IrdaAppSceneUniversalTV() - : IrdaAppSceneUniversalCommon("/ext/irda/assets/tv.ir") { - } - ~IrdaAppSceneUniversalTV() { - } -}; - -class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon { -public: - void on_enter(IrdaApp* app) final; - IrdaAppSceneUniversalAudio() - : IrdaAppSceneUniversalCommon("/ext/irda/assets/audio.ir") { - } - ~IrdaAppSceneUniversalAudio() { - } -}; diff --git a/applications/irda/scene/irda_app_scene_edit_delete_done.cpp b/applications/irda/scene/irda_app_scene_edit_delete_done.cpp deleted file mode 100644 index e429e72d..00000000 --- a/applications/irda/scene/irda_app_scene_edit_delete_done.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../irda_app.h" - -void IrdaAppSceneEditDeleteDone::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); - Popup* popup = view_manager->get_popup(); - - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); - - popup_set_callback(popup, IrdaApp::popup_callback); - popup_set_context(popup, app); - popup_set_timeout(popup, 1500); - popup_enable_timeout(popup); - - view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); -} - -bool IrdaAppSceneEditDeleteDone::on_event(IrdaApp* app, IrdaAppEvent* event) { - bool consumed = false; - - if(event->type == IrdaAppEvent::Type::PopupTimer) { - if(app->get_edit_element() == IrdaApp::EditElement::Remote) { - app->search_and_switch_to_previous_scene( - {IrdaApp::Scene::Start, IrdaApp::Scene::RemoteList}); - } else { - app->search_and_switch_to_previous_scene({IrdaApp::Scene::Remote}); - } - consumed = true; - } - - return consumed; -} - -void IrdaAppSceneEditDeleteDone::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); - Popup* popup = view_manager->get_popup(); - popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop); -} diff --git a/applications/irda/scene/irda_app_scene_edit_key_select.cpp b/applications/irda/scene/irda_app_scene_edit_key_select.cpp deleted file mode 100644 index 7d805cd6..00000000 --- a/applications/irda/scene/irda_app_scene_edit_key_select.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "../irda_app.h" -#include "gui/modules/submenu.h" - -static void submenu_callback(void* context, uint32_t index) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; - - event.type = IrdaAppEvent::Type::MenuSelected; - event.payload.menu_index = index; - - app->get_view_manager()->send_event(&event); -} - -void IrdaAppSceneEditKeySelect::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); - Submenu* submenu = view_manager->get_submenu(); - int item_number = 0; - - const char* header = app->get_edit_action() == IrdaApp::EditAction::Rename ? "Rename key:" : - "Delete key:"; - submenu_set_header(submenu, header); - - auto remote_manager = app->get_remote_manager(); - buttons_names = remote_manager->get_button_list(); - for(const auto& it : buttons_names) { - submenu_add_item(submenu, it.c_str(), item_number++, submenu_callback, app); - } - if((item_number > 0) && (app->get_current_button() != IrdaApp::ButtonNA)) { - submenu_set_selected_item(submenu, app->get_current_button()); - app->set_current_button(IrdaApp::ButtonNA); - } - - view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); -} - -bool IrdaAppSceneEditKeySelect::on_event(IrdaApp* app, IrdaAppEvent* event) { - bool consumed = false; - - if(event->type == IrdaAppEvent::Type::MenuSelected) { - app->set_current_button(event->payload.menu_index); - consumed = true; - if(app->get_edit_action() == IrdaApp::EditAction::Rename) { - app->switch_to_next_scene(IrdaApp::Scene::EditRename); - } else { - app->switch_to_next_scene(IrdaApp::Scene::EditDelete); - } - } - - return consumed; -} - -void IrdaAppSceneEditKeySelect::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); - Submenu* submenu = view_manager->get_submenu(); - - submenu_reset(submenu); -} diff --git a/applications/irda/scene/irda_app_scene_edit_rename_done.cpp b/applications/irda/scene/irda_app_scene_edit_rename_done.cpp deleted file mode 100644 index 181b9670..00000000 --- a/applications/irda/scene/irda_app_scene_edit_rename_done.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "../irda_app.h" - -void IrdaAppSceneEditRenameDone::on_enter(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); - Popup* popup = view_manager->get_popup(); - - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); - - popup_set_callback(popup, IrdaApp::popup_callback); - popup_set_context(popup, app); - popup_set_timeout(popup, 1500); - popup_enable_timeout(popup); - - view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); -} - -bool IrdaAppSceneEditRenameDone::on_event(IrdaApp* app, IrdaAppEvent* event) { - bool consumed = false; - - if(event->type == IrdaAppEvent::Type::PopupTimer) { - app->switch_to_next_scene(IrdaApp::Scene::Remote); - consumed = true; - } - - return consumed; -} - -void IrdaAppSceneEditRenameDone::on_exit(IrdaApp* app) { -} diff --git a/applications/irda/scene/irda_app_scene_learn.cpp b/applications/irda/scene/irda_app_scene_learn.cpp deleted file mode 100644 index 7075f0f8..00000000 --- a/applications/irda/scene/irda_app_scene_learn.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "../irda_app.h" -#include "../irda_app_event.h" -#include "irda.h" -#include - -static void signal_received_callback(void* context, IrdaWorkerSignal* received_signal) { - furi_assert(context); - furi_assert(received_signal); - - IrdaApp* app = static_cast(context); - - if(irda_worker_signal_is_decoded(received_signal)) { - IrdaAppSignal signal(irda_worker_get_decoded_signal(received_signal)); - app->set_received_signal(signal); - } else { - const uint32_t* timings; - size_t timings_cnt; - irda_worker_get_raw_signal(received_signal, &timings, &timings_cnt); - IrdaAppSignal signal( - timings, timings_cnt, IRDA_COMMON_CARRIER_FREQUENCY, IRDA_COMMON_DUTY_CYCLE); - app->set_received_signal(signal); - } - - irda_worker_rx_set_received_signal_callback(app->get_irda_worker(), NULL, NULL); - IrdaAppEvent event; - event.type = IrdaAppEvent::Type::IrdaMessageReceived; - auto view_manager = app->get_view_manager(); - view_manager->send_event(&event); -} - -void IrdaAppSceneLearn::on_enter(IrdaApp* app) { - auto view_manager = app->get_view_manager(); - auto popup = view_manager->get_popup(); - - auto worker = app->get_irda_worker(); - irda_worker_rx_set_received_signal_callback(worker, signal_received_callback, app); - irda_worker_rx_start(worker); - - popup_set_icon(popup, 0, 32, &I_IrdaLearnShort_128x31); - popup_set_text( - popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); - popup_set_callback(popup, NULL); - - view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); -} - -bool IrdaAppSceneLearn::on_event(IrdaApp* app, IrdaAppEvent* event) { - bool consumed = false; - - switch(event->type) { - case IrdaAppEvent::Type::Tick: - consumed = true; - app->notify_red_blink(); - break; - case IrdaAppEvent::Type::IrdaMessageReceived: - app->notify_success(); - app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnSuccess); - break; - case IrdaAppEvent::Type::Back: - consumed = true; - app->switch_to_previous_scene(); - break; - default: - furi_assert(0); - } - - return consumed; -} - -void IrdaAppSceneLearn::on_exit(IrdaApp* app) { - irda_worker_rx_stop(app->get_irda_worker()); - auto view_manager = app->get_view_manager(); - auto popup = view_manager->get_popup(); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter); -} diff --git a/applications/irda/scene/irda_app_scene_universal_common.cpp b/applications/irda/scene/irda_app_scene_universal_common.cpp deleted file mode 100644 index 5a020ea7..00000000 --- a/applications/irda/scene/irda_app_scene_universal_common.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include -#include -#include - -#include "../irda_app.h" -#include "irda/irda_app_event.h" -#include "irda/irda_app_view_manager.h" -#include "irda/scene/irda_app_scene.h" -#include "../view/irda_progress_view.h" - -void IrdaAppSceneUniversalCommon::irda_app_item_callback(void* context, uint32_t index) { - IrdaApp* app = static_cast(context); - IrdaAppEvent event; - - event.type = IrdaAppEvent::Type::ButtonPanelPressed; - event.payload.menu_index = index; - - app->get_view_manager()->send_event(&event); -} - -static void irda_progress_back_callback(void* context) { - furi_assert(context); - auto app = static_cast(context); - - IrdaAppEvent irda_event = { - .type = IrdaAppEvent::Type::Back, - }; - app->get_view_manager()->clear_events(); - app->get_view_manager()->send_event(&irda_event); -} - -void IrdaAppSceneUniversalCommon::remove_popup(IrdaApp* app) { - auto stack_view = app->get_view_manager()->get_universal_view_stack(); - auto progress_view = app->get_view_manager()->get_progress(); - view_stack_remove_view(stack_view, irda_progress_view_get_view(progress_view)); -} - -void IrdaAppSceneUniversalCommon::show_popup(IrdaApp* app, int record_amount) { - auto stack_view = app->get_view_manager()->get_universal_view_stack(); - auto progress_view = app->get_view_manager()->get_progress(); - irda_progress_view_set_progress_total(progress_view, record_amount); - irda_progress_view_set_back_callback(progress_view, irda_progress_back_callback, app); - view_stack_add_view(stack_view, irda_progress_view_get_view(progress_view)); -} - -bool IrdaAppSceneUniversalCommon::progress_popup(IrdaApp* app) { - auto progress_view = app->get_view_manager()->get_progress(); - return irda_progress_view_increase_progress(progress_view); -} - -bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) { - bool consumed = false; - - if(brute_force_started) { - if(event->type == IrdaAppEvent::Type::Tick) { - auto view_manager = app->get_view_manager(); - IrdaAppEvent tick_event = {.type = IrdaAppEvent::Type::Tick}; - view_manager->send_event(&tick_event); - bool result = brute_force.send_next_bruteforce(); - if(result) { - result = progress_popup(app); - } - if(!result) { - brute_force.stop_bruteforce(); - brute_force_started = false; - remove_popup(app); - } - consumed = true; - } else if(event->type == IrdaAppEvent::Type::Back) { - brute_force_started = false; - brute_force.stop_bruteforce(); - remove_popup(app); - consumed = true; - } - } else { - if(event->type == IrdaAppEvent::Type::ButtonPanelPressed) { - int record_amount = 0; - if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) { - DOLPHIN_DEED(DolphinDeedIrBruteForce); - brute_force_started = true; - show_popup(app, record_amount); - } else { - app->switch_to_previous_scene(); - } - consumed = true; - } else if(event->type == IrdaAppEvent::Type::Back) { - app->switch_to_previous_scene(); - consumed = true; - } - } - - return consumed; -} - -void IrdaAppSceneUniversalCommon::on_exit(IrdaApp* app) { - IrdaAppViewManager* view_manager = app->get_view_manager(); - ButtonPanel* button_panel = view_manager->get_button_panel(); - button_panel_reset(button_panel); -} diff --git a/applications/irda/view/irda_progress_view.h b/applications/irda/view/irda_progress_view.h deleted file mode 100644 index f0e41928..00000000 --- a/applications/irda/view/irda_progress_view.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct IrdaProgressView IrdaProgressView; - -typedef void (*IrdaProgressViewBackCallback)(void*); - -IrdaProgressView* irda_progress_view_alloc(); -void irda_progress_view_free(IrdaProgressView* progress); -View* irda_progress_view_get_view(IrdaProgressView* progress); - -bool irda_progress_view_increase_progress(IrdaProgressView* progress); -void irda_progress_view_set_progress_total(IrdaProgressView* progress, uint16_t progress_max); -void irda_progress_view_set_back_callback( - IrdaProgressView* instance, - IrdaProgressViewBackCallback callback, - void* context); - -#ifdef __cplusplus -} -#endif diff --git a/applications/irda_monitor/irda_monitor.c b/applications/irda_monitor/irda_monitor.c deleted file mode 100644 index 5325d192..00000000 --- a/applications/irda_monitor/irda_monitor.c +++ /dev/null @@ -1,139 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define IRDA_TIMINGS_SIZE 700 - -typedef struct { - uint32_t timing_cnt; - struct { - uint8_t level; - uint32_t duration; - } timing[IRDA_TIMINGS_SIZE]; -} IrdaDelaysArray; - -typedef struct { - char display_text[64]; - osMessageQueueId_t event_queue; - IrdaDelaysArray delays; - IrdaWorker* worker; - ViewPort* view_port; -} IrdaMonitor; - -void irda_monitor_input_callback(InputEvent* input_event, void* ctx) { - furi_assert(ctx); - IrdaMonitor* irda_monitor = (IrdaMonitor*)ctx; - - if((input_event->type == InputTypeShort) && (input_event->key == InputKeyBack)) { - osMessageQueuePut(irda_monitor->event_queue, input_event, 0, 0); - } -} - -static void irda_monitor_draw_callback(Canvas* canvas, void* ctx) { - furi_assert(canvas); - furi_assert(ctx); - IrdaMonitor* irda_monitor = (IrdaMonitor*)ctx; - - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "IRDA monitor\n"); - canvas_set_font(canvas, FontKeyboard); - if(strlen(irda_monitor->display_text)) { - elements_multiline_text_aligned( - canvas, 64, 43, AlignCenter, AlignCenter, irda_monitor->display_text); - } -} - -static void signal_received_callback(void* context, IrdaWorkerSignal* received_signal) { - furi_assert(context); - furi_assert(received_signal); - IrdaMonitor* irda_monitor = context; - - if(irda_worker_signal_is_decoded(received_signal)) { - const IrdaMessage* message = irda_worker_get_decoded_signal(received_signal); - snprintf( - irda_monitor->display_text, - sizeof(irda_monitor->display_text), - "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n", - irda_get_protocol_name(message->protocol), - ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), - message->address, - ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), - message->command, - message->repeat ? " R" : ""); - view_port_update(irda_monitor->view_port); - printf( - "== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n", - irda_get_protocol_name(message->protocol), - ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), - message->address, - ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), - message->command, - message->repeat ? " R" : ""); - } else { - const uint32_t* timings; - size_t timings_cnt; - irda_worker_get_raw_signal(received_signal, &timings, &timings_cnt); - snprintf( - irda_monitor->display_text, - sizeof(irda_monitor->display_text), - "RAW\n%d samples\n", - timings_cnt); - view_port_update(irda_monitor->view_port); - printf("RAW, %d samples:\r\n", timings_cnt); - for(size_t i = 0; i < timings_cnt; ++i) { - printf("%lu ", timings[i]); - } - printf("\r\n"); - } -} - -int32_t irda_monitor_app(void* p) { - (void)p; - - IrdaMonitor* irda_monitor = malloc(sizeof(IrdaMonitor)); - irda_monitor->display_text[0] = 0; - irda_monitor->event_queue = osMessageQueueNew(1, sizeof(InputEvent), NULL); - irda_monitor->view_port = view_port_alloc(); - Gui* gui = furi_record_open("gui"); - - view_port_draw_callback_set(irda_monitor->view_port, irda_monitor_draw_callback, irda_monitor); - view_port_input_callback_set( - irda_monitor->view_port, irda_monitor_input_callback, irda_monitor); - - gui_add_view_port(gui, irda_monitor->view_port, GuiLayerFullscreen); - - irda_monitor->worker = irda_worker_alloc(); - irda_worker_rx_start(irda_monitor->worker); - irda_worker_rx_set_received_signal_callback( - irda_monitor->worker, signal_received_callback, irda_monitor); - irda_worker_rx_enable_blink_on_receiving(irda_monitor->worker, true); - - while(1) { - InputEvent event; - if(osOK == osMessageQueueGet(irda_monitor->event_queue, &event, NULL, 50)) { - if((event.type == InputTypeShort) && (event.key == InputKeyBack)) { - break; - } - } - } - - irda_worker_rx_stop(irda_monitor->worker); - irda_worker_free(irda_monitor->worker); - osMessageQueueDelete(irda_monitor->event_queue); - view_port_enabled_set(irda_monitor->view_port, false); - gui_remove_view_port(gui, irda_monitor->view_port); - view_port_free(irda_monitor->view_port); - furi_record_close("gui"); - free(irda_monitor); - - return 0; -} diff --git a/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c b/applications/tests/infrared_decoder_encoder/infrared_decoder_encoder_test.c similarity index 77% rename from applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c rename to applications/tests/infrared_decoder_encoder/infrared_decoder_encoder_test.c index 7ab27430..ca9120ed 100644 --- a/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c +++ b/applications/tests/infrared_decoder_encoder/infrared_decoder_encoder_test.c @@ -1,13 +1,13 @@ #include #include "../minunit.h" -#include "irda.h" -#include "common/irda_common_i.h" -#include "test_data/irda_nec_test_data.srcdata" -#include "test_data/irda_necext_test_data.srcdata" -#include "test_data/irda_samsung_test_data.srcdata" -#include "test_data/irda_rc6_test_data.srcdata" -#include "test_data/irda_rc5_test_data.srcdata" -#include "test_data/irda_sirc_test_data.srcdata" +#include "infrared.h" +#include "common/infrared_common_i.h" +#include "test_data/infrared_nec_test_data.srcdata" +#include "test_data/infrared_necext_test_data.srcdata" +#include "test_data/infrared_samsung_test_data.srcdata" +#include "test_data/infrared_rc6_test_data.srcdata" +#include "test_data/infrared_rc5_test_data.srcdata" +#include "test_data/infrared_sirc_test_data.srcdata" #define RUN_ENCODER(data, expected) \ run_encoder((data), COUNT_OF(data), (expected), COUNT_OF(expected)) @@ -17,28 +17,28 @@ #define RUN_ENCODER_DECODER(data) run_encoder_decoder((data), COUNT_OF(data)) -static IrdaDecoderHandler* decoder_handler; -static IrdaEncoderHandler* encoder_handler; +static InfraredDecoderHandler* decoder_handler; +static InfraredEncoderHandler* encoder_handler; static void test_setup(void) { - decoder_handler = irda_alloc_decoder(); - encoder_handler = irda_alloc_encoder(); + decoder_handler = infrared_alloc_decoder(); + encoder_handler = infrared_alloc_encoder(); } static void test_teardown(void) { - irda_free_decoder(decoder_handler); - irda_free_encoder(encoder_handler); + infrared_free_decoder(decoder_handler); + infrared_free_encoder(encoder_handler); } static void compare_message_results( - const IrdaMessage* message_decoded, - const IrdaMessage* message_expected) { + 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 == IrdaProtocolSIRC) || - (message_expected->protocol == IrdaProtocolSIRC15) || - (message_expected->protocol == IrdaProtocolSIRC20)) { + 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); @@ -47,19 +47,19 @@ static void compare_message_results( /* Encodes signal and merges same levels (high+high, low+low) */ static void run_encoder_fill_array( - IrdaEncoderHandler* handler, + InfraredEncoderHandler* handler, uint32_t* timings, uint32_t* timings_len, bool* start_level) { uint32_t duration = 0; bool level = false; bool level_read; - IrdaStatus status = IrdaStatusError; + InfraredStatus status = InfraredStatusError; int i = 0; bool first = true; while(1) { - status = irda_encode(handler, &duration, &level_read); + status = infrared_encode(handler, &duration, &level_read); if(first) { if(start_level) *start_level = level_read; first = false; @@ -72,8 +72,8 @@ static void run_encoder_fill_array( level = level_read; timings[i] += duration; - furi_check((status == IrdaStatusOk) || (status == IrdaStatusDone)); - if(status == IrdaStatusDone) break; + furi_check((status == InfraredStatusOk) || (status == InfraredStatusDone)); + if(status == InfraredStatusDone) break; } *timings_len = i + 1; @@ -81,7 +81,7 @@ static void run_encoder_fill_array( // messages in input array for encoder should have one protocol static void run_encoder( - const IrdaMessage input_messages[], + const InfraredMessage input_messages[], uint32_t input_messages_len, const uint32_t expected_timings[], uint32_t expected_timings_len) { @@ -91,9 +91,9 @@ static void run_encoder( timings = malloc(sizeof(uint32_t) * timings_len); for(uint32_t message_counter = 0; message_counter < input_messages_len; ++message_counter) { - const IrdaMessage* message = &input_messages[message_counter]; + const InfraredMessage* message = &input_messages[message_counter]; if(!message->repeat) { - irda_reset_encoder(encoder_handler, message); + infrared_reset_encoder(encoder_handler, message); } timings_len = 200; @@ -109,25 +109,26 @@ static void run_encoder( mu_assert(j == expected_timings_len, "encoded less timings than expected"); } -static void run_encoder_decoder(const IrdaMessage input_messages[], uint32_t input_messages_len) { +static void + run_encoder_decoder(const InfraredMessage input_messages[], uint32_t input_messages_len) { uint32_t* timings = 0; uint32_t timings_len = 200; bool level = false; timings = malloc(sizeof(uint32_t) * timings_len); for(uint32_t message_counter = 0; message_counter < input_messages_len; ++message_counter) { - const IrdaMessage* message_encoded = &input_messages[message_counter]; + const InfraredMessage* message_encoded = &input_messages[message_counter]; if(!message_encoded->repeat) { - irda_reset_encoder(encoder_handler, message_encoded); + infrared_reset_encoder(encoder_handler, message_encoded); } timings_len = 200; run_encoder_fill_array(encoder_handler, timings, &timings_len, &level); furi_check(timings_len <= 200); - const IrdaMessage* message_decoded = 0; + const InfraredMessage* message_decoded = 0; for(int i = 0; i < timings_len; ++i) { - message_decoded = irda_decode(decoder_handler, level, timings[i]); + message_decoded = infrared_decode(decoder_handler, level, timings[i]); if((i == timings_len - 2) && level && message_decoded) { /* In case we end with space timing - message can be decoded at last mark */ break; @@ -135,7 +136,7 @@ static void run_encoder_decoder(const IrdaMessage input_messages[], uint32_t inp mu_check(!message_decoded); } else { if(!message_decoded) { - message_decoded = irda_check_decoder_ready(decoder_handler); + message_decoded = infrared_check_decoder_ready(decoder_handler); } mu_check(message_decoded); } @@ -153,32 +154,32 @@ static void run_encoder_decoder(const IrdaMessage input_messages[], uint32_t inp static void run_decoder( const uint32_t* input_delays, uint32_t input_delays_len, - const IrdaMessage* message_expected, + const InfraredMessage* message_expected, uint32_t message_expected_len) { - IrdaMessage message_decoded_check_local; + InfraredMessage message_decoded_check_local; bool level = 0; uint32_t message_counter = 0; - const IrdaMessage* message_decoded = 0; + const InfraredMessage* message_decoded = 0; for(uint32_t i = 0; i < input_delays_len; ++i) { - const IrdaMessage* message_decoded_check = 0; + const InfraredMessage* message_decoded_check = 0; - if(input_delays[i] > IRDA_RAW_RX_TIMING_DELAY_US) { - message_decoded_check = irda_check_decoder_ready(decoder_handler); + if(input_delays[i] > INFRARED_RAW_RX_TIMING_DELAY_US) { + message_decoded_check = infrared_check_decoder_ready(decoder_handler); if(message_decoded_check) { - /* irda_decode() can reset message, but we have to call irda_decode() to perform real - * simulation: irda_check() by timeout, then irda_decode() when meet edge */ + /* 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 = irda_decode(decoder_handler, level, input_delays[i]); + message_decoded = infrared_decode(decoder_handler, level, input_delays[i]); if(message_decoded_check || message_decoded) { mu_assert( !(message_decoded_check && message_decoded), - "both messages decoded: check_ready() and irda_decode()"); + "both messages decoded: check_ready() and infrared_decode()"); if(message_decoded_check) { message_decoded = message_decoded_check; @@ -192,7 +193,7 @@ static void run_decoder( level = !level; } - message_decoded = irda_check_decoder_ready(decoder_handler); + message_decoded = infrared_check_decoder_ready(decoder_handler); if(message_decoded) { compare_message_results(message_decoded, &message_expected[message_counter]); ++message_counter; @@ -304,7 +305,7 @@ MU_TEST(test_encoder_decoder_all) { RUN_ENCODER_DECODER(test_sirc); } -MU_TEST_SUITE(test_irda_decoder_encoder) { +MU_TEST_SUITE(test_infrared_decoder_encoder) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_encoder_sirc); @@ -323,8 +324,8 @@ MU_TEST_SUITE(test_irda_decoder_encoder) { MU_RUN_TEST(test_encoder_decoder_all); } -int run_minunit_test_irda_decoder_encoder() { - MU_RUN_SUITE(test_irda_decoder_encoder); +int run_minunit_test_infrared_decoder_encoder() { + MU_RUN_SUITE(test_infrared_decoder_encoder); return MU_EXIT_CODE; } diff --git a/applications/tests/irda_decoder_encoder/test_data/irda_nec_test_data.srcdata b/applications/tests/infrared_decoder_encoder/test_data/infrared_nec_test_data.srcdata similarity index 66% rename from applications/tests/irda_decoder_encoder/test_data/irda_nec_test_data.srcdata rename to applications/tests/infrared_decoder_encoder/test_data/infrared_nec_test_data.srcdata index 1214ae9b..ec9a86a6 100644 --- a/applications/tests/irda_decoder_encoder/test_data/irda_nec_test_data.srcdata +++ b/applications/tests/infrared_decoder_encoder/test_data/infrared_nec_test_data.srcdata @@ -10,10 +10,10 @@ const uint32_t test_decoder_nec_input1[] = { /* message */ 1415838, 9080, 4436, 611, 494, 600, 505, 578, 500, 608, 501, 602, 502, 580, 498, 606, 508, 605, 500, 583, 1633, 608, 1608, 611, 1631, 578, 1638, 602, 1614, 606, 1637, 583, 1633, 607, 1609, 611, 494, 600, 505, 570, 500, 604, 501, 602, 502, 581, 497, 606, 499, 605, 499, 583, 1633, 617, 1608, 611, 1631, 579, 1638, 602}; -const IrdaMessage test_decoder_nec_expected1[] = { - {IrdaProtocolNEC, 0x00, 0, false}, - {IrdaProtocolNEC, 0x00, 0, true}, - {IrdaProtocolNEC, 0x00, 0, false}, +const InfraredMessage test_decoder_nec_expected1[] = { + {InfraredProtocolNEC, 0x00, 0, false}, + {InfraredProtocolNEC, 0x00, 0, true}, + {InfraredProtocolNEC, 0x00, 0, false}, }; const uint32_t test_decoder_nec_input2[] = { @@ -123,59 +123,59 @@ const uint32_t test_decoder_nec_input2[] = { 40069,9025,2221,588 }; -const IrdaMessage test_decoder_nec_expected2[] = { - {IrdaProtocolNEC, 0x00, 0x02, false}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, false}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x02, true}, - {IrdaProtocolNEC, 0x00, 0x06, false}, - {IrdaProtocolNEC, 0x00, 0x06, true}, - {IrdaProtocolNEC, 0x00, 0x04, false}, - {IrdaProtocolNEC, 0x00, 0x04, true}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, true}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, true}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x09, false}, - {IrdaProtocolNEC, 0x00, 0x09, false}, - {IrdaProtocolNEC, 0x00, 0x09, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x0A, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, true}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x08, true}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x0A, false}, - {IrdaProtocolNEC, 0x00, 0x08, false}, - {IrdaProtocolNEC, 0x00, 0x0A, false}, - {IrdaProtocolNEC, 0x00, 0x0A, true}, +const InfraredMessage test_decoder_nec_expected2[] = { + {InfraredProtocolNEC, 0x00, 0x02, false}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, false}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x02, true}, + {InfraredProtocolNEC, 0x00, 0x06, false}, + {InfraredProtocolNEC, 0x00, 0x06, true}, + {InfraredProtocolNEC, 0x00, 0x04, false}, + {InfraredProtocolNEC, 0x00, 0x04, true}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, true}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, true}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x09, false}, + {InfraredProtocolNEC, 0x00, 0x09, false}, + {InfraredProtocolNEC, 0x00, 0x09, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x0A, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, true}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x08, true}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x0A, false}, + {InfraredProtocolNEC, 0x00, 0x08, false}, + {InfraredProtocolNEC, 0x00, 0x0A, false}, + {InfraredProtocolNEC, 0x00, 0x0A, true}, }; const uint32_t test_decoder_nec_input3[] = { @@ -198,112 +198,112 @@ const uint32_t test_decoder_nec_input3[] = { 92592, 8861, 4414, 538, }; -const IrdaMessage test_decoder_nec_expected3[] = { - {IrdaProtocolNECext, 0x286, 0xB649, false}, - {IrdaProtocolNECext, 0x286, 0xB649, false}, - {IrdaProtocolNECext, 0x6880, 0xB649, false}, - {IrdaProtocolNECext, 0x6880, 0xB649, false}, - {IrdaProtocolNECext, 0x6380, 0x150F, false}, - {IrdaProtocolNECext, 0x6380, 0x150F, false}, - {IrdaProtocolNECext, 0x6480, 0x849, false}, - {IrdaProtocolNECext, 0x6480, 0x849, false}, - {IrdaProtocolNECext, 0x7A83, 0x8, false}, - {IrdaProtocolNECext, 0x7A83, 0x8, false}, - {IrdaProtocolNEC, 0x71, 0x4A, false}, - {IrdaProtocolNEC, 0x71, 0x4A, false}, - {IrdaProtocolNEC42, 0x7B, 0x0, false}, - {IrdaProtocolNEC42, 0x7B, 0x0, false}, - {IrdaProtocolNEC42, 0x11C, 0x12, false}, +const InfraredMessage test_decoder_nec_expected3[] = { + {InfraredProtocolNECext, 0x286, 0xB649, false}, + {InfraredProtocolNECext, 0x286, 0xB649, false}, + {InfraredProtocolNECext, 0x6880, 0xB649, false}, + {InfraredProtocolNECext, 0x6880, 0xB649, false}, + {InfraredProtocolNECext, 0x6380, 0x150F, false}, + {InfraredProtocolNECext, 0x6380, 0x150F, false}, + {InfraredProtocolNECext, 0x6480, 0x849, false}, + {InfraredProtocolNECext, 0x6480, 0x849, false}, + {InfraredProtocolNECext, 0x7A83, 0x8, false}, + {InfraredProtocolNECext, 0x7A83, 0x8, false}, + {InfraredProtocolNEC, 0x71, 0x4A, false}, + {InfraredProtocolNEC, 0x71, 0x4A, false}, + {InfraredProtocolNEC42, 0x7B, 0x0, false}, + {InfraredProtocolNEC42, 0x7B, 0x0, false}, + {InfraredProtocolNEC42, 0x11C, 0x12, false}, }; -const IrdaMessage test_nec[] = { - {IrdaProtocolNEC, 0x00, 0x00, false}, - {IrdaProtocolNEC, 0x01, 0x00, false}, - {IrdaProtocolNEC, 0x01, 0x80, false}, - {IrdaProtocolNEC, 0x00, 0x80, false}, - {IrdaProtocolNEC, 0x00, 0x00, false}, - {IrdaProtocolNEC, 0x00, 0x00, true}, - {IrdaProtocolNEC, 0x00, 0x00, false}, - {IrdaProtocolNEC, 0x00, 0x00, true}, - {IrdaProtocolNEC, 0xFF, 0xFF, false}, - {IrdaProtocolNEC, 0xFE, 0xFF, false}, - {IrdaProtocolNEC, 0xFE, 0x7F, false}, - {IrdaProtocolNEC, 0xFF, 0x7F, false}, - {IrdaProtocolNEC, 0xFF, 0xFF, false}, - {IrdaProtocolNEC, 0xFF, 0xFF, true}, - {IrdaProtocolNEC, 0xAA, 0x55, false}, - {IrdaProtocolNEC, 0x55, 0xAA, false}, - {IrdaProtocolNEC, 0x55, 0x55, false}, - {IrdaProtocolNEC, 0xAA, 0xAA, false}, - {IrdaProtocolNEC, 0xAA, 0xAA, true}, +const InfraredMessage test_nec[] = { + {InfraredProtocolNEC, 0x00, 0x00, false}, + {InfraredProtocolNEC, 0x01, 0x00, false}, + {InfraredProtocolNEC, 0x01, 0x80, false}, + {InfraredProtocolNEC, 0x00, 0x80, false}, + {InfraredProtocolNEC, 0x00, 0x00, false}, + {InfraredProtocolNEC, 0x00, 0x00, true}, + {InfraredProtocolNEC, 0x00, 0x00, false}, + {InfraredProtocolNEC, 0x00, 0x00, true}, + {InfraredProtocolNEC, 0xFF, 0xFF, false}, + {InfraredProtocolNEC, 0xFE, 0xFF, false}, + {InfraredProtocolNEC, 0xFE, 0x7F, false}, + {InfraredProtocolNEC, 0xFF, 0x7F, false}, + {InfraredProtocolNEC, 0xFF, 0xFF, false}, + {InfraredProtocolNEC, 0xFF, 0xFF, true}, + {InfraredProtocolNEC, 0xAA, 0x55, false}, + {InfraredProtocolNEC, 0x55, 0xAA, false}, + {InfraredProtocolNEC, 0x55, 0x55, false}, + {InfraredProtocolNEC, 0xAA, 0xAA, false}, + {InfraredProtocolNEC, 0xAA, 0xAA, true}, - {IrdaProtocolNEC, 0xAA, 0xAA, false}, - {IrdaProtocolNEC, 0xAA, 0xAA, true}, - {IrdaProtocolNEC, 0xAA, 0xAA, true}, + {InfraredProtocolNEC, 0xAA, 0xAA, false}, + {InfraredProtocolNEC, 0xAA, 0xAA, true}, + {InfraredProtocolNEC, 0xAA, 0xAA, true}, - {IrdaProtocolNEC, 0x55, 0x55, false}, - {IrdaProtocolNEC, 0x55, 0x55, true}, - {IrdaProtocolNEC, 0x55, 0x55, true}, - {IrdaProtocolNEC, 0x55, 0x55, true}, + {InfraredProtocolNEC, 0x55, 0x55, false}, + {InfraredProtocolNEC, 0x55, 0x55, true}, + {InfraredProtocolNEC, 0x55, 0x55, true}, + {InfraredProtocolNEC, 0x55, 0x55, true}, }; -const IrdaMessage test_nec42[] = { - {IrdaProtocolNEC42, 0x0000, 0x00, false}, - {IrdaProtocolNEC42, 0x0001, 0x00, false}, - {IrdaProtocolNEC42, 0x0001, 0x80, false}, - {IrdaProtocolNEC42, 0x0000, 0x80, false}, - {IrdaProtocolNEC42, 0x0000, 0x00, false}, - {IrdaProtocolNEC42, 0x0000, 0x00, true}, - {IrdaProtocolNEC42, 0x0000, 0x00, false}, - {IrdaProtocolNEC42, 0x0000, 0x00, true}, - {IrdaProtocolNEC42, 0x1FFF, 0xFF, false}, - {IrdaProtocolNEC42, 0x1FFE, 0xFF, false}, - {IrdaProtocolNEC42, 0x1FFE, 0x7F, false}, - {IrdaProtocolNEC42, 0x1FFF, 0x7F, false}, - {IrdaProtocolNEC42, 0x1FFF, 0xFF, false}, - {IrdaProtocolNEC42, 0x1FFF, 0xFF, true}, - {IrdaProtocolNEC42, 0x0AAA, 0x55, false}, - {IrdaProtocolNEC42, 0x1555, 0xAA, false}, - {IrdaProtocolNEC42, 0x1555, 0x55, false}, - {IrdaProtocolNEC42, 0x0AAA, 0xAA, false}, - {IrdaProtocolNEC42, 0x0AAA, 0xAA, true}, - {IrdaProtocolNEC42, 0x0AAA, 0xAA, false}, - {IrdaProtocolNEC42, 0x0AAA, 0xAA, true}, - {IrdaProtocolNEC42, 0x0AAA, 0xAA, true}, - {IrdaProtocolNEC42, 0x1555, 0x55, false}, - {IrdaProtocolNEC42, 0x1555, 0x55, true}, - {IrdaProtocolNEC42, 0x1555, 0x55, true}, - {IrdaProtocolNEC42, 0x1555, 0x55, true}, +const InfraredMessage test_nec42[] = { + {InfraredProtocolNEC42, 0x0000, 0x00, false}, + {InfraredProtocolNEC42, 0x0001, 0x00, false}, + {InfraredProtocolNEC42, 0x0001, 0x80, false}, + {InfraredProtocolNEC42, 0x0000, 0x80, false}, + {InfraredProtocolNEC42, 0x0000, 0x00, false}, + {InfraredProtocolNEC42, 0x0000, 0x00, true}, + {InfraredProtocolNEC42, 0x0000, 0x00, false}, + {InfraredProtocolNEC42, 0x0000, 0x00, true}, + {InfraredProtocolNEC42, 0x1FFF, 0xFF, false}, + {InfraredProtocolNEC42, 0x1FFE, 0xFF, false}, + {InfraredProtocolNEC42, 0x1FFE, 0x7F, false}, + {InfraredProtocolNEC42, 0x1FFF, 0x7F, false}, + {InfraredProtocolNEC42, 0x1FFF, 0xFF, false}, + {InfraredProtocolNEC42, 0x1FFF, 0xFF, true}, + {InfraredProtocolNEC42, 0x0AAA, 0x55, false}, + {InfraredProtocolNEC42, 0x1555, 0xAA, false}, + {InfraredProtocolNEC42, 0x1555, 0x55, false}, + {InfraredProtocolNEC42, 0x0AAA, 0xAA, false}, + {InfraredProtocolNEC42, 0x0AAA, 0xAA, true}, + {InfraredProtocolNEC42, 0x0AAA, 0xAA, false}, + {InfraredProtocolNEC42, 0x0AAA, 0xAA, true}, + {InfraredProtocolNEC42, 0x0AAA, 0xAA, true}, + {InfraredProtocolNEC42, 0x1555, 0x55, false}, + {InfraredProtocolNEC42, 0x1555, 0x55, true}, + {InfraredProtocolNEC42, 0x1555, 0x55, true}, + {InfraredProtocolNEC42, 0x1555, 0x55, true}, }; -const IrdaMessage test_nec42ext[] = { - {IrdaProtocolNEC42ext, 0x0000000, 0x0000, false}, - {IrdaProtocolNEC42ext, 0x0000001, 0x0000, false}, - {IrdaProtocolNEC42ext, 0x0000001, 0x8000, false}, - {IrdaProtocolNEC42ext, 0x0000000, 0x8000, false}, - {IrdaProtocolNEC42ext, 0x0000000, 0x0000, false}, - {IrdaProtocolNEC42ext, 0x0000000, 0x0000, true}, - {IrdaProtocolNEC42ext, 0x0000000, 0x0000, false}, - {IrdaProtocolNEC42ext, 0x0000000, 0x0000, true}, - {IrdaProtocolNEC42ext, 0x3F000FF, 0xF00F, false}, - {IrdaProtocolNEC42ext, 0x3F000FE, 0xF00F, false}, - {IrdaProtocolNEC42ext, 0x3F000FE, 0x700F, false}, - {IrdaProtocolNEC42ext, 0x3F000FF, 0x700F, false}, - {IrdaProtocolNEC42ext, 0x3F000FF, 0xF00F, false}, - {IrdaProtocolNEC42ext, 0x3F000FF, 0xF00F, true}, - {IrdaProtocolNEC42ext, 0x2AAAAAA, 0x5555, false}, - {IrdaProtocolNEC42ext, 0x1555555, 0xAAAA, false}, - {IrdaProtocolNEC42ext, 0x1555555, 0x5555, false}, - {IrdaProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, false}, - {IrdaProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, true}, - {IrdaProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, false}, - {IrdaProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, true}, - {IrdaProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, true}, - {IrdaProtocolNEC42ext, 0x1555555, 0x5555, false}, - {IrdaProtocolNEC42ext, 0x1555555, 0x5555, true}, - {IrdaProtocolNEC42ext, 0x1555555, 0x5555, true}, - {IrdaProtocolNEC42ext, 0x1555555, 0x5555, true}, +const InfraredMessage test_nec42ext[] = { + {InfraredProtocolNEC42ext, 0x0000000, 0x0000, false}, + {InfraredProtocolNEC42ext, 0x0000001, 0x0000, false}, + {InfraredProtocolNEC42ext, 0x0000001, 0x8000, false}, + {InfraredProtocolNEC42ext, 0x0000000, 0x8000, false}, + {InfraredProtocolNEC42ext, 0x0000000, 0x0000, false}, + {InfraredProtocolNEC42ext, 0x0000000, 0x0000, true}, + {InfraredProtocolNEC42ext, 0x0000000, 0x0000, false}, + {InfraredProtocolNEC42ext, 0x0000000, 0x0000, true}, + {InfraredProtocolNEC42ext, 0x3F000FF, 0xF00F, false}, + {InfraredProtocolNEC42ext, 0x3F000FE, 0xF00F, false}, + {InfraredProtocolNEC42ext, 0x3F000FE, 0x700F, false}, + {InfraredProtocolNEC42ext, 0x3F000FF, 0x700F, false}, + {InfraredProtocolNEC42ext, 0x3F000FF, 0xF00F, false}, + {InfraredProtocolNEC42ext, 0x3F000FF, 0xF00F, true}, + {InfraredProtocolNEC42ext, 0x2AAAAAA, 0x5555, false}, + {InfraredProtocolNEC42ext, 0x1555555, 0xAAAA, false}, + {InfraredProtocolNEC42ext, 0x1555555, 0x5555, false}, + {InfraredProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, false}, + {InfraredProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, true}, + {InfraredProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, false}, + {InfraredProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, true}, + {InfraredProtocolNEC42ext, 0x2AAAAAA, 0xAAAA, true}, + {InfraredProtocolNEC42ext, 0x1555555, 0x5555, false}, + {InfraredProtocolNEC42ext, 0x1555555, 0x5555, true}, + {InfraredProtocolNEC42ext, 0x1555555, 0x5555, true}, + {InfraredProtocolNEC42ext, 0x1555555, 0x5555, true}, }; const uint32_t test_decoder_nec42ext_input1[] = { @@ -331,11 +331,11 @@ const uint32_t test_decoder_nec42ext_input2[] = { 560, 560, 560, 560, 560, 10000, 560, // 42 OK + 1 failed }; -const IrdaMessage test_decoder_nec42ext_expected1[] = { - {IrdaProtocolNEC42ext, 0x00, 0, false}, +const InfraredMessage test_decoder_nec42ext_expected1[] = { + {InfraredProtocolNEC42ext, 0x00, 0, false}, }; -const IrdaMessage test_decoder_nec42ext_expected2[] = { - {IrdaProtocolNEC42ext, 0x00, 0, false}, +const InfraredMessage test_decoder_nec42ext_expected2[] = { + {InfraredProtocolNEC42ext, 0x00, 0, false}, }; diff --git a/applications/tests/irda_decoder_encoder/test_data/irda_necext_test_data.srcdata b/applications/tests/infrared_decoder_encoder/test_data/infrared_necext_test_data.srcdata similarity index 56% rename from applications/tests/irda_decoder_encoder/test_data/irda_necext_test_data.srcdata rename to applications/tests/infrared_decoder_encoder/test_data/infrared_necext_test_data.srcdata index edd60768..22ccf40b 100644 --- a/applications/tests/irda_decoder_encoder/test_data/irda_necext_test_data.srcdata +++ b/applications/tests/infrared_decoder_encoder/test_data/infrared_necext_test_data.srcdata @@ -110,146 +110,146 @@ const uint32_t test_decoder_necext_input1[] = { 261924, 8965, 4465, 585, 529, 588, 525, 592, 1638, 588, 525, 592, 523, 584, 530, 587, 526, 591, 1639, 587, 1642, 583, 529, 587, 527, 590, 1639, 587, 1643, 584, 1646, 590, }; -const IrdaMessage test_decoder_necext_expected1[] = { - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, false}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, - {IrdaProtocolNECext, 0x7984, 0xed12, true}, +const InfraredMessage test_decoder_necext_expected1[] = { + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, false}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, + {InfraredProtocolNECext, 0x7984, 0xed12, true}, }; -const IrdaMessage test_necext[] = { - {IrdaProtocolNECext, 0x0000, 0x0000, false}, - {IrdaProtocolNECext, 0x0001, 0x0000, false}, - {IrdaProtocolNECext, 0x0001, 0x8000, false}, - {IrdaProtocolNECext, 0x0000, 0x8000, false}, - {IrdaProtocolNECext, 0x0000, 0x0000, false}, - {IrdaProtocolNECext, 0x0000, 0x0000, true}, - {IrdaProtocolNECext, 0x0000, 0x0000, false}, - {IrdaProtocolNECext, 0x0000, 0x0000, true}, - {IrdaProtocolNECext, 0xFFFF, 0xFFFF, false}, - {IrdaProtocolNECext, 0xFFFE, 0xFFFF, false}, - {IrdaProtocolNECext, 0xFFFE, 0x7FFF, false}, - {IrdaProtocolNECext, 0xFFFF, 0x7FFF, false}, - {IrdaProtocolNECext, 0xFFFF, 0xFFFF, false}, - {IrdaProtocolNECext, 0xFFFF, 0xFFFF, true}, - {IrdaProtocolNECext, 0xAAAA, 0x5555, false}, - {IrdaProtocolNECext, 0x5555, 0xAAAA, false}, - {IrdaProtocolNECext, 0x5555, 0x5555, false}, - {IrdaProtocolNECext, 0xAAAA, 0xAAAA, false}, - {IrdaProtocolNECext, 0xAAAA, 0xAAAA, true}, +const InfraredMessage test_necext[] = { + {InfraredProtocolNECext, 0x0000, 0x0000, false}, + {InfraredProtocolNECext, 0x0001, 0x0000, false}, + {InfraredProtocolNECext, 0x0001, 0x8000, false}, + {InfraredProtocolNECext, 0x0000, 0x8000, false}, + {InfraredProtocolNECext, 0x0000, 0x0000, false}, + {InfraredProtocolNECext, 0x0000, 0x0000, true}, + {InfraredProtocolNECext, 0x0000, 0x0000, false}, + {InfraredProtocolNECext, 0x0000, 0x0000, true}, + {InfraredProtocolNECext, 0xFFFF, 0xFFFF, false}, + {InfraredProtocolNECext, 0xFFFE, 0xFFFF, false}, + {InfraredProtocolNECext, 0xFFFE, 0x7FFF, false}, + {InfraredProtocolNECext, 0xFFFF, 0x7FFF, false}, + {InfraredProtocolNECext, 0xFFFF, 0xFFFF, false}, + {InfraredProtocolNECext, 0xFFFF, 0xFFFF, true}, + {InfraredProtocolNECext, 0xAAAA, 0x5555, false}, + {InfraredProtocolNECext, 0x5555, 0xAAAA, false}, + {InfraredProtocolNECext, 0x5555, 0x5555, false}, + {InfraredProtocolNECext, 0xAAAA, 0xAAAA, false}, + {InfraredProtocolNECext, 0xAAAA, 0xAAAA, true}, - {IrdaProtocolNECext, 0xAAAA, 0xAAAA, false}, - {IrdaProtocolNECext, 0xAAAA, 0xAAAA, true}, - {IrdaProtocolNECext, 0xAAAA, 0xAAAA, true}, + {InfraredProtocolNECext, 0xAAAA, 0xAAAA, false}, + {InfraredProtocolNECext, 0xAAAA, 0xAAAA, true}, + {InfraredProtocolNECext, 0xAAAA, 0xAAAA, true}, - {IrdaProtocolNECext, 0x5555, 0x5555, false}, - {IrdaProtocolNECext, 0x5555, 0x5555, true}, - {IrdaProtocolNECext, 0x5555, 0x5555, true}, - {IrdaProtocolNECext, 0x5555, 0x5555, true}, + {InfraredProtocolNECext, 0x5555, 0x5555, false}, + {InfraredProtocolNECext, 0x5555, 0x5555, true}, + {InfraredProtocolNECext, 0x5555, 0x5555, true}, + {InfraredProtocolNECext, 0x5555, 0x5555, true}, }; diff --git a/applications/tests/irda_decoder_encoder/test_data/irda_rc5_test_data.srcdata b/applications/tests/infrared_decoder_encoder/test_data/infrared_rc5_test_data.srcdata similarity index 56% rename from applications/tests/irda_decoder_encoder/test_data/irda_rc5_test_data.srcdata rename to applications/tests/infrared_decoder_encoder/test_data/infrared_rc5_test_data.srcdata index 4dd43bed..07241555 100644 --- a/applications/tests/irda_decoder_encoder/test_data/irda_rc5_test_data.srcdata +++ b/applications/tests/infrared_decoder_encoder/test_data/infrared_rc5_test_data.srcdata @@ -8,8 +8,8 @@ const uint32_t test_decoder_rc5x_input1[] = { 27000 + 888, 1776, 888, 888, 1776, 1776, 888, 888, 1776, 888, 888, 1776, 1776, 1776, 888, 888, 888, 888, 888, 888, }; -const IrdaMessage test_decoder_rc5x_expected1[] = { - {IrdaProtocolRC5X, 0x13, 0x10, false}, // toggle 0 +const InfraredMessage test_decoder_rc5x_expected1[] = { + {InfraredProtocolRC5X, 0x13, 0x10, false}, // toggle 0 }; /* @@ -22,8 +22,8 @@ const uint32_t test_decoder_rc5_input1[] = { 27000 + 888, 888, 888, 1776, 1776, 1776, 888, 888, 1776, 888, 888, 1776, 1776, 1776, 888, 888, 888, 888, 888, 888, }; -const IrdaMessage test_decoder_rc5_expected1[] = { - {IrdaProtocolRC5, 0x13, 0x10, false}, // toggle 0 +const InfraredMessage test_decoder_rc5_expected1[] = { + {InfraredProtocolRC5, 0x13, 0x10, false}, // toggle 0 }; @@ -37,8 +37,8 @@ const uint32_t test_decoder_rc5_input2[] = { 27000 + 888, 888, 888, 888, 888, 888, 888, 1776, 888, 888, 1776, 888, 888, 1776, 1776, 1776, 888, 888, 888, 888, 888, 888, }; -const IrdaMessage test_decoder_rc5_expected2[] = { - {IrdaProtocolRC5, 0x13, 0x10, false}, // toggle 1 +const InfraredMessage test_decoder_rc5_expected2[] = { + {InfraredProtocolRC5, 0x13, 0x10, false}, // toggle 1 }; /* @@ -51,8 +51,8 @@ const uint32_t test_decoder_rc5_input3[] = { 27000 + 888, 888, 888, 1776, 1776, 1776, 888, 888, 1776, 888, 888, 1776, 1776, 1776, 888, 888, 888, 888, 1776, 888, }; -const IrdaMessage test_decoder_rc5_expected3[] = { - {IrdaProtocolRC5, 0x13, 0x11, false}, // toggle 0 +const InfraredMessage test_decoder_rc5_expected3[] = { + {InfraredProtocolRC5, 0x13, 0x11, false}, // toggle 0 }; @@ -66,8 +66,8 @@ const uint32_t test_decoder_rc5_input4[] = { 27000 + 888, 888, 888, 888, 888, 888, 888, 1776, 888, 888, 1776, 888, 888, 1776, 1776, 1776, 888, 888, 888, 888, 1776, 888, }; -const IrdaMessage test_decoder_rc5_expected4[] = { - {IrdaProtocolRC5, 0x13, 0x11, false}, // toggle 1 +const InfraredMessage test_decoder_rc5_expected4[] = { + {InfraredProtocolRC5, 0x13, 0x11, false}, // toggle 1 }; /* @@ -80,8 +80,8 @@ const uint32_t test_decoder_rc5_input5[] = { 27000 + 888, 888, 888, 1776, 1776, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, }; -const IrdaMessage test_decoder_rc5_expected5[] = { - {IrdaProtocolRC5, 0x1F, 0x3F, false}, // toggle 0 +const InfraredMessage test_decoder_rc5_expected5[] = { + {InfraredProtocolRC5, 0x1F, 0x3F, false}, // toggle 0 }; /* @@ -94,8 +94,8 @@ const uint32_t test_decoder_rc5_input6[] = { 27000 + 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, }; -const IrdaMessage test_decoder_rc5_expected6[] = { - {IrdaProtocolRC5, 0x1F, 0x3F, false}, // toggle 1 +const InfraredMessage test_decoder_rc5_expected6[] = { + {InfraredProtocolRC5, 0x1F, 0x3F, false}, // toggle 1 }; @@ -113,48 +113,48 @@ const uint32_t test_decoder_rc5_input_all_repeats[] = { 27000 + 888, 888, 888, 1776, 1776, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, 888, }; -const IrdaMessage test_decoder_rc5_expected_all_repeats[] = { - {IrdaProtocolRC5, 0x13, 0x11, false}, // toggle 0 - {IrdaProtocolRC5, 0x13, 0x11, false}, // toggle 1 - {IrdaProtocolRC5, 0x13, 0x11, true}, // toggle 1 - {IrdaProtocolRC5, 0x13, 0x11, true}, // toggle 1 - {IrdaProtocolRC5, 0x13, 0x11, false}, // toggle 0 - {IrdaProtocolRC5, 0x13, 0x10, false}, // toggle 1 - {IrdaProtocolRC5, 0x13, 0x10, false}, // toggle 0 - {IrdaProtocolRC5, 0x13, 0x10, true}, // toggle 0 - {IrdaProtocolRC5, 0x1F, 0x3F, false}, // toggle 1 - {IrdaProtocolRC5, 0x1F, 0x3F, false}, // toggle 0 - {IrdaProtocolRC5, 0x1F, 0x3F, true}, // toggle 0 +const InfraredMessage test_decoder_rc5_expected_all_repeats[] = { + {InfraredProtocolRC5, 0x13, 0x11, false}, // toggle 0 + {InfraredProtocolRC5, 0x13, 0x11, false}, // toggle 1 + {InfraredProtocolRC5, 0x13, 0x11, true}, // toggle 1 + {InfraredProtocolRC5, 0x13, 0x11, true}, // toggle 1 + {InfraredProtocolRC5, 0x13, 0x11, false}, // toggle 0 + {InfraredProtocolRC5, 0x13, 0x10, false}, // toggle 1 + {InfraredProtocolRC5, 0x13, 0x10, false}, // toggle 0 + {InfraredProtocolRC5, 0x13, 0x10, true}, // toggle 0 + {InfraredProtocolRC5, 0x1F, 0x3F, false}, // toggle 1 + {InfraredProtocolRC5, 0x1F, 0x3F, false}, // toggle 0 + {InfraredProtocolRC5, 0x1F, 0x3F, true}, // toggle 0 }; -const IrdaMessage test_rc5[] = { - {IrdaProtocolRC5, 0x1F, 0x3F, false}, - {IrdaProtocolRC5, 0x00, 0x00, false}, - {IrdaProtocolRC5, 0x10, 0x01, false}, - {IrdaProtocolRC5, 0x01, 0x20, false}, - {IrdaProtocolRC5, 0x01, 0x20, false}, - {IrdaProtocolRC5, 0x01, 0x20, true}, - {IrdaProtocolRC5, 0x01, 0x20, true}, - {IrdaProtocolRC5, 0x01, 0x20, true}, - {IrdaProtocolRC5, 0x01, 0x20, true}, - {IrdaProtocolRC5, 0x1F, 0x3F, false}, - {IrdaProtocolRC5, 0x0A, 0x2A, false}, - {IrdaProtocolRC5, 0x15, 0x15, false}, - {IrdaProtocolRC5, 0x15, 0x15, true}, +const InfraredMessage test_rc5[] = { + {InfraredProtocolRC5, 0x1F, 0x3F, false}, + {InfraredProtocolRC5, 0x00, 0x00, false}, + {InfraredProtocolRC5, 0x10, 0x01, false}, + {InfraredProtocolRC5, 0x01, 0x20, false}, + {InfraredProtocolRC5, 0x01, 0x20, false}, + {InfraredProtocolRC5, 0x01, 0x20, true}, + {InfraredProtocolRC5, 0x01, 0x20, true}, + {InfraredProtocolRC5, 0x01, 0x20, true}, + {InfraredProtocolRC5, 0x01, 0x20, true}, + {InfraredProtocolRC5, 0x1F, 0x3F, false}, + {InfraredProtocolRC5, 0x0A, 0x2A, false}, + {InfraredProtocolRC5, 0x15, 0x15, false}, + {InfraredProtocolRC5, 0x15, 0x15, true}, - {IrdaProtocolRC5X, 0x1F, 0x3F, false}, - {IrdaProtocolRC5X, 0x00, 0x00, false}, - {IrdaProtocolRC5X, 0x10, 0x01, false}, - {IrdaProtocolRC5X, 0x01, 0x20, false}, - {IrdaProtocolRC5X, 0x01, 0x20, false}, - {IrdaProtocolRC5X, 0x01, 0x20, true}, - {IrdaProtocolRC5X, 0x01, 0x20, true}, - {IrdaProtocolRC5X, 0x01, 0x20, true}, - {IrdaProtocolRC5X, 0x01, 0x20, true}, - {IrdaProtocolRC5X, 0x1F, 0x3F, false}, - {IrdaProtocolRC5X, 0x0A, 0x2A, false}, - {IrdaProtocolRC5X, 0x15, 0x15, false}, - {IrdaProtocolRC5X, 0x15, 0x15, true}, + {InfraredProtocolRC5X, 0x1F, 0x3F, false}, + {InfraredProtocolRC5X, 0x00, 0x00, false}, + {InfraredProtocolRC5X, 0x10, 0x01, false}, + {InfraredProtocolRC5X, 0x01, 0x20, false}, + {InfraredProtocolRC5X, 0x01, 0x20, false}, + {InfraredProtocolRC5X, 0x01, 0x20, true}, + {InfraredProtocolRC5X, 0x01, 0x20, true}, + {InfraredProtocolRC5X, 0x01, 0x20, true}, + {InfraredProtocolRC5X, 0x01, 0x20, true}, + {InfraredProtocolRC5X, 0x1F, 0x3F, false}, + {InfraredProtocolRC5X, 0x0A, 0x2A, false}, + {InfraredProtocolRC5X, 0x15, 0x15, false}, + {InfraredProtocolRC5X, 0x15, 0x15, true}, }; diff --git a/applications/tests/irda_decoder_encoder/test_data/irda_rc6_test_data.srcdata b/applications/tests/infrared_decoder_encoder/test_data/infrared_rc6_test_data.srcdata similarity index 65% rename from applications/tests/irda_decoder_encoder/test_data/irda_rc6_test_data.srcdata rename to applications/tests/infrared_decoder_encoder/test_data/infrared_rc6_test_data.srcdata index 25010b0c..94b9be5a 100644 --- a/applications/tests/irda_decoder_encoder/test_data/irda_rc6_test_data.srcdata +++ b/applications/tests/infrared_decoder_encoder/test_data/infrared_rc6_test_data.srcdata @@ -79,28 +79,28 @@ const uint32_t test_decoder_rc6_input1[] = { 27000, 2666, 889, 444, 888, 444, 444, 444, 444, 444 + 888, 888, 444, 888, 444, 444, 888, 888, 888, 888, 888, 444, 444, 888, 888, 888, 444, 444, 444, 444, 444, 444, 444, 444, 444, }; -const IrdaMessage test_decoder_rc6_expected1[] = { - {IrdaProtocolRC6, 0x94, 0xA0, false}, // toggle 0 - {IrdaProtocolRC6, 0x93, 0xA0, false}, // toggle 1 -// {IrdaProtocolRC6, 0x95, 0xA0, false}, failed - {IrdaProtocolRC6, 0x93, 0xA0, false}, // toggle 0 - {IrdaProtocolRC6, 0x94, 0xA0, false}, // toggle 1 - {IrdaProtocolRC6, 0x95, 0xA0, false}, // toggle 0 -// {IrdaProtocolRC6, 0x93, 0xA0, false}, failed -// {IrdaProtocolRC6, 0x93, 0xA0, false}, failed -// {IrdaProtocolRC6, 0x93, 0xA0, false}, failed - {IrdaProtocolRC6, 0x95, 0xA0, false}, // toggle 1 +const InfraredMessage test_decoder_rc6_expected1[] = { + {InfraredProtocolRC6, 0x94, 0xA0, false}, // toggle 0 + {InfraredProtocolRC6, 0x93, 0xA0, false}, // toggle 1 +// {InfraredProtocolRC6, 0x95, 0xA0, false}, failed + {InfraredProtocolRC6, 0x93, 0xA0, false}, // toggle 0 + {InfraredProtocolRC6, 0x94, 0xA0, false}, // toggle 1 + {InfraredProtocolRC6, 0x95, 0xA0, false}, // toggle 0 +// {InfraredProtocolRC6, 0x93, 0xA0, false}, failed +// {InfraredProtocolRC6, 0x93, 0xA0, false}, failed +// {InfraredProtocolRC6, 0x93, 0xA0, false}, failed + {InfraredProtocolRC6, 0x95, 0xA0, false}, // toggle 1 }; -const IrdaMessage test_encoder_rc6_input1[] = { - {IrdaProtocolRC6, 0x93, 0xA0, false}, // Toggle 0 - {IrdaProtocolRC6, 0x93, 0xA0, true}, // Toggle 0 - {IrdaProtocolRC6, 0x93, 0xA1, false}, // Toggle 1 - {IrdaProtocolRC6, 0x93, 0xA1, true}, // Toggle 1 - {IrdaProtocolRC6, 0x93, 0xA1, true}, // Toggle 1 - {IrdaProtocolRC6, 0x93, 0xA0, false}, // Toggle 0 - {IrdaProtocolRC6, 0x93, 0xA0, false}, // Toggle 1 - {IrdaProtocolRC6, 0x93, 0xA0, true}, // Toggle 1 +const InfraredMessage test_encoder_rc6_input1[] = { + {InfraredProtocolRC6, 0x93, 0xA0, false}, // Toggle 0 + {InfraredProtocolRC6, 0x93, 0xA0, true}, // Toggle 0 + {InfraredProtocolRC6, 0x93, 0xA1, false}, // Toggle 1 + {InfraredProtocolRC6, 0x93, 0xA1, true}, // Toggle 1 + {InfraredProtocolRC6, 0x93, 0xA1, true}, // Toggle 1 + {InfraredProtocolRC6, 0x93, 0xA0, false}, // Toggle 0 + {InfraredProtocolRC6, 0x93, 0xA0, false}, // Toggle 1 + {InfraredProtocolRC6, 0x93, 0xA0, true}, // Toggle 1 }; const uint32_t test_encoder_rc6_expected1[] = { @@ -115,48 +115,48 @@ const uint32_t test_encoder_rc6_expected1[] = { }; -const IrdaMessage test_rc6[] = { - {IrdaProtocolRC6, 0x00, 0x00, false}, // t 0 - {IrdaProtocolRC6, 0x80, 0x00, false}, // t 1 - {IrdaProtocolRC6, 0x80, 0x01, false}, // t 0 - {IrdaProtocolRC6, 0x00, 0x01, false}, // t 1 - {IrdaProtocolRC6, 0x00, 0x00, false}, // t 0 - {IrdaProtocolRC6, 0x00, 0x00, true}, // t 0 - {IrdaProtocolRC6, 0x00, 0x00, false}, // t 1 - {IrdaProtocolRC6, 0x00, 0x00, true}, // t 1 - {IrdaProtocolRC6, 0xFF, 0xFF, false}, // t 0 - {IrdaProtocolRC6, 0x7F, 0xFF, false}, // t 1 - {IrdaProtocolRC6, 0x7F, 0xFE, false}, // t 0 - {IrdaProtocolRC6, 0xFF, 0xFE, false}, // t 1 - {IrdaProtocolRC6, 0xFF, 0xFF, false}, // t 0 - {IrdaProtocolRC6, 0xFF, 0xFF, true}, // t 0 - {IrdaProtocolRC6, 0xAA, 0x55, false}, // t 1 - {IrdaProtocolRC6, 0x55, 0xAA, false}, // t 0 - {IrdaProtocolRC6, 0x55, 0x55, false}, // t 1 - {IrdaProtocolRC6, 0xAA, 0xAA, false}, // t 0 - {IrdaProtocolRC6, 0xAA, 0xAA, true}, // t 0 +const InfraredMessage test_rc6[] = { + {InfraredProtocolRC6, 0x00, 0x00, false}, // t 0 + {InfraredProtocolRC6, 0x80, 0x00, false}, // t 1 + {InfraredProtocolRC6, 0x80, 0x01, false}, // t 0 + {InfraredProtocolRC6, 0x00, 0x01, false}, // t 1 + {InfraredProtocolRC6, 0x00, 0x00, false}, // t 0 + {InfraredProtocolRC6, 0x00, 0x00, true}, // t 0 + {InfraredProtocolRC6, 0x00, 0x00, false}, // t 1 + {InfraredProtocolRC6, 0x00, 0x00, true}, // t 1 + {InfraredProtocolRC6, 0xFF, 0xFF, false}, // t 0 + {InfraredProtocolRC6, 0x7F, 0xFF, false}, // t 1 + {InfraredProtocolRC6, 0x7F, 0xFE, false}, // t 0 + {InfraredProtocolRC6, 0xFF, 0xFE, false}, // t 1 + {InfraredProtocolRC6, 0xFF, 0xFF, false}, // t 0 + {InfraredProtocolRC6, 0xFF, 0xFF, true}, // t 0 + {InfraredProtocolRC6, 0xAA, 0x55, false}, // t 1 + {InfraredProtocolRC6, 0x55, 0xAA, false}, // t 0 + {InfraredProtocolRC6, 0x55, 0x55, false}, // t 1 + {InfraredProtocolRC6, 0xAA, 0xAA, false}, // t 0 + {InfraredProtocolRC6, 0xAA, 0xAA, true}, // t 0 // same with inverted toggle bit - {IrdaProtocolRC6, 0x00, 0x00, false}, // t 1 - {IrdaProtocolRC6, 0x80, 0x00, false}, // t 0 - {IrdaProtocolRC6, 0x80, 0x01, false}, // t 1 - {IrdaProtocolRC6, 0x00, 0x01, false}, // t 0 - {IrdaProtocolRC6, 0x00, 0x00, false}, // t 1 - {IrdaProtocolRC6, 0x00, 0x00, true}, // t 1 - {IrdaProtocolRC6, 0x00, 0x00, false}, // t 0 - {IrdaProtocolRC6, 0x00, 0x00, true}, // t 0 - {IrdaProtocolRC6, 0xFF, 0xFF, false}, // t 1 - {IrdaProtocolRC6, 0x7F, 0xFF, false}, // t 0 - {IrdaProtocolRC6, 0x7F, 0xFE, false}, // t 1 - {IrdaProtocolRC6, 0xFF, 0xFE, false}, // t 0 - {IrdaProtocolRC6, 0xFF, 0xFF, false}, // t 1 - {IrdaProtocolRC6, 0xFF, 0xFF, true}, // t 1 - {IrdaProtocolRC6, 0xAA, 0x55, false}, // t 0 - {IrdaProtocolRC6, 0x55, 0xAA, false}, // t 1 - {IrdaProtocolRC6, 0x55, 0x55, false}, // t 0 - {IrdaProtocolRC6, 0xAA, 0xAA, false}, // t 1 - {IrdaProtocolRC6, 0xAA, 0xAA, true}, // t 1 + {InfraredProtocolRC6, 0x00, 0x00, false}, // t 1 + {InfraredProtocolRC6, 0x80, 0x00, false}, // t 0 + {InfraredProtocolRC6, 0x80, 0x01, false}, // t 1 + {InfraredProtocolRC6, 0x00, 0x01, false}, // t 0 + {InfraredProtocolRC6, 0x00, 0x00, false}, // t 1 + {InfraredProtocolRC6, 0x00, 0x00, true}, // t 1 + {InfraredProtocolRC6, 0x00, 0x00, false}, // t 0 + {InfraredProtocolRC6, 0x00, 0x00, true}, // t 0 + {InfraredProtocolRC6, 0xFF, 0xFF, false}, // t 1 + {InfraredProtocolRC6, 0x7F, 0xFF, false}, // t 0 + {InfraredProtocolRC6, 0x7F, 0xFE, false}, // t 1 + {InfraredProtocolRC6, 0xFF, 0xFE, false}, // t 0 + {InfraredProtocolRC6, 0xFF, 0xFF, false}, // t 1 + {InfraredProtocolRC6, 0xFF, 0xFF, true}, // t 1 + {InfraredProtocolRC6, 0xAA, 0x55, false}, // t 0 + {InfraredProtocolRC6, 0x55, 0xAA, false}, // t 1 + {InfraredProtocolRC6, 0x55, 0x55, false}, // t 0 + {InfraredProtocolRC6, 0xAA, 0xAA, false}, // t 1 + {InfraredProtocolRC6, 0xAA, 0xAA, true}, // t 1 - {IrdaProtocolRC6, 0x93, 0xA0, false}, // t 0 - {IrdaProtocolRC6, 0x93, 0xA1, false}, // t 1 + {InfraredProtocolRC6, 0x93, 0xA0, false}, // t 0 + {InfraredProtocolRC6, 0x93, 0xA1, false}, // t 1 }; diff --git a/applications/tests/irda_decoder_encoder/test_data/irda_samsung_test_data.srcdata b/applications/tests/infrared_decoder_encoder/test_data/infrared_samsung_test_data.srcdata similarity index 71% rename from applications/tests/irda_decoder_encoder/test_data/irda_samsung_test_data.srcdata rename to applications/tests/infrared_decoder_encoder/test_data/infrared_samsung_test_data.srcdata index 077a4a94..aea867b9 100644 --- a/applications/tests/irda_decoder_encoder/test_data/irda_samsung_test_data.srcdata +++ b/applications/tests/infrared_decoder_encoder/test_data/infrared_samsung_test_data.srcdata @@ -179,76 +179,76 @@ const uint32_t test_decoder_samsung32_input1[] = { 532, 584, }; -const IrdaMessage test_decoder_samsung32_expected1[] = { - {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x81, false}, {IrdaProtocolSamsung32, 0x0E, 0x81, true}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, true}, - {IrdaProtocolSamsung32, 0x0E, 0x02, false}, {IrdaProtocolSamsung32, 0x0E, 0x02, true}, - {IrdaProtocolSamsung32, 0x0E, 0x03, false}, {IrdaProtocolSamsung32, 0x0E, 0x03, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, - {IrdaProtocolSamsung32, 0x0E, 0x0C, true}, {IrdaProtocolSamsung32, 0x0E, 0x0C, false}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, true}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, true}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, false}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, true}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, false}, - {IrdaProtocolSamsung32, 0x0E, 0x01, true}, {IrdaProtocolSamsung32, 0x0E, 0x01, false}, - {IrdaProtocolSamsung32, 0x0E, 0x01, true}, {IrdaProtocolSamsung32, 0x0E, 0x01, false}, - {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, true}, +const InfraredMessage test_decoder_samsung32_expected1[] = { + {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x81, false}, {InfraredProtocolSamsung32, 0x0E, 0x81, true}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, true}, + {InfraredProtocolSamsung32, 0x0E, 0x02, false}, {InfraredProtocolSamsung32, 0x0E, 0x02, true}, + {InfraredProtocolSamsung32, 0x0E, 0x03, false}, {InfraredProtocolSamsung32, 0x0E, 0x03, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, + {InfraredProtocolSamsung32, 0x0E, 0x0C, true}, {InfraredProtocolSamsung32, 0x0E, 0x0C, false}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, true}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, true}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, false}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, true}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, false}, + {InfraredProtocolSamsung32, 0x0E, 0x01, true}, {InfraredProtocolSamsung32, 0x0E, 0x01, false}, + {InfraredProtocolSamsung32, 0x0E, 0x01, true}, {InfraredProtocolSamsung32, 0x0E, 0x01, false}, + {InfraredProtocolSamsung32, 0x0E, 0x01, false}, {InfraredProtocolSamsung32, 0x0E, 0x01, true}, }; -const IrdaMessage test_samsung32[] = { - {IrdaProtocolSamsung32, 0x00, 0x00, false}, - {IrdaProtocolSamsung32, 0x01, 0x00, false}, - {IrdaProtocolSamsung32, 0x01, 0x80, false}, - {IrdaProtocolSamsung32, 0x00, 0x80, false}, - {IrdaProtocolSamsung32, 0x00, 0x00, false}, - {IrdaProtocolSamsung32, 0x00, 0x00, true}, - {IrdaProtocolSamsung32, 0x00, 0x00, false}, - {IrdaProtocolSamsung32, 0x00, 0x00, true}, - {IrdaProtocolSamsung32, 0xFF, 0xFF, false}, - {IrdaProtocolSamsung32, 0xFE, 0xFF, false}, - {IrdaProtocolSamsung32, 0xFE, 0x7F, false}, - {IrdaProtocolSamsung32, 0xFF, 0x7F, false}, - {IrdaProtocolSamsung32, 0xFF, 0xFF, false}, - {IrdaProtocolSamsung32, 0xFF, 0xFF, true}, - {IrdaProtocolSamsung32, 0xAA, 0x55, false}, - {IrdaProtocolSamsung32, 0x55, 0xAA, false}, - {IrdaProtocolSamsung32, 0x55, 0x55, false}, - {IrdaProtocolSamsung32, 0xAA, 0xAA, false}, - {IrdaProtocolSamsung32, 0xAA, 0xAA, true}, +const InfraredMessage test_samsung32[] = { + {InfraredProtocolSamsung32, 0x00, 0x00, false}, + {InfraredProtocolSamsung32, 0x01, 0x00, false}, + {InfraredProtocolSamsung32, 0x01, 0x80, false}, + {InfraredProtocolSamsung32, 0x00, 0x80, false}, + {InfraredProtocolSamsung32, 0x00, 0x00, false}, + {InfraredProtocolSamsung32, 0x00, 0x00, true}, + {InfraredProtocolSamsung32, 0x00, 0x00, false}, + {InfraredProtocolSamsung32, 0x00, 0x00, true}, + {InfraredProtocolSamsung32, 0xFF, 0xFF, false}, + {InfraredProtocolSamsung32, 0xFE, 0xFF, false}, + {InfraredProtocolSamsung32, 0xFE, 0x7F, false}, + {InfraredProtocolSamsung32, 0xFF, 0x7F, false}, + {InfraredProtocolSamsung32, 0xFF, 0xFF, false}, + {InfraredProtocolSamsung32, 0xFF, 0xFF, true}, + {InfraredProtocolSamsung32, 0xAA, 0x55, false}, + {InfraredProtocolSamsung32, 0x55, 0xAA, false}, + {InfraredProtocolSamsung32, 0x55, 0x55, false}, + {InfraredProtocolSamsung32, 0xAA, 0xAA, false}, + {InfraredProtocolSamsung32, 0xAA, 0xAA, true}, - {IrdaProtocolSamsung32, 0xAA, 0xAA, false}, - {IrdaProtocolSamsung32, 0xAA, 0xAA, true}, - {IrdaProtocolSamsung32, 0xAA, 0xAA, true}, + {InfraredProtocolSamsung32, 0xAA, 0xAA, false}, + {InfraredProtocolSamsung32, 0xAA, 0xAA, true}, + {InfraredProtocolSamsung32, 0xAA, 0xAA, true}, - {IrdaProtocolSamsung32, 0x55, 0x55, false}, - {IrdaProtocolSamsung32, 0x55, 0x55, true}, - {IrdaProtocolSamsung32, 0x55, 0x55, true}, - {IrdaProtocolSamsung32, 0x55, 0x55, true}, + {InfraredProtocolSamsung32, 0x55, 0x55, false}, + {InfraredProtocolSamsung32, 0x55, 0x55, true}, + {InfraredProtocolSamsung32, 0x55, 0x55, true}, + {InfraredProtocolSamsung32, 0x55, 0x55, true}, }; diff --git a/applications/tests/irda_decoder_encoder/test_data/irda_sirc_test_data.srcdata b/applications/tests/infrared_decoder_encoder/test_data/infrared_sirc_test_data.srcdata similarity index 75% rename from applications/tests/irda_decoder_encoder/test_data/irda_sirc_test_data.srcdata rename to applications/tests/infrared_decoder_encoder/test_data/infrared_sirc_test_data.srcdata index bacf4be5..52cc1533 100644 --- a/applications/tests/irda_decoder_encoder/test_data/irda_sirc_test_data.srcdata +++ b/applications/tests/infrared_decoder_encoder/test_data/infrared_sirc_test_data.srcdata @@ -124,128 +124,128 @@ const uint32_t test_decoder_sirc_input1[] = { /* 121 timings */ 26263, 2414, 611, 1192, 607, 544, 606, 1197, 602, 569, 606, 1197, 602, 539, 611, 540, 635, 1168, 606, 565, 610, 541, 608, 563, 587, 564, }; -const IrdaMessage test_decoder_sirc_expected1[] = { - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x60, false}, - {IrdaProtocolSIRC, 0x10, 0x60, false}, - {IrdaProtocolSIRC, 0x10, 0x60, false}, - {IrdaProtocolSIRC, 0x10, 0x65, false}, - {IrdaProtocolSIRC, 0x10, 0x65, false}, - {IrdaProtocolSIRC, 0x10, 0x65, false}, - {IrdaProtocolSIRC20, 0x410, 0x17, false}, - {IrdaProtocolSIRC20, 0x410, 0x17, false}, - {IrdaProtocolSIRC20, 0x410, 0x17, false}, - {IrdaProtocolSIRC, 0x10, 0x21, false}, - {IrdaProtocolSIRC, 0x10, 0x21, false}, - {IrdaProtocolSIRC, 0x10, 0x21, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7B, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7B, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7B, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7A, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7A, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7A, false}, - {IrdaProtocolSIRC20, 0x73A, 0x78, false}, - {IrdaProtocolSIRC20, 0x73A, 0x78, false}, - {IrdaProtocolSIRC20, 0x73A, 0x78, false}, - {IrdaProtocolSIRC20, 0x73A, 0x79, false}, - {IrdaProtocolSIRC20, 0x73A, 0x79, false}, - {IrdaProtocolSIRC20, 0x73A, 0x79, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7A, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7A, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7A, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7C, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7D, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7D, false}, - {IrdaProtocolSIRC20, 0x73A, 0x7D, false}, - {IrdaProtocolSIRC20, 0x73A, 0x73, false}, - {IrdaProtocolSIRC20, 0x73A, 0x73, false}, - {IrdaProtocolSIRC20, 0x73A, 0x73, false}, - {IrdaProtocolSIRC, 0x10, 0x13, false}, - {IrdaProtocolSIRC, 0x10, 0x13, false}, - {IrdaProtocolSIRC, 0x10, 0x13, false}, - {IrdaProtocolSIRC, 0x10, 0x13, false}, - {IrdaProtocolSIRC, 0x10, 0x12, false}, - {IrdaProtocolSIRC, 0x10, 0x12, false}, - {IrdaProtocolSIRC, 0x10, 0x12, false}, - {IrdaProtocolSIRC, 0x10, 0x12, false}, - {IrdaProtocolSIRC20, 0x73A, 0x30, false}, - {IrdaProtocolSIRC20, 0x73A, 0x30, false}, - {IrdaProtocolSIRC20, 0x73A, 0x30, false}, - {IrdaProtocolSIRC20, 0x73A, 0x39, false}, - {IrdaProtocolSIRC20, 0x73A, 0x39, false}, - {IrdaProtocolSIRC20, 0x73A, 0x39, false}, - {IrdaProtocolSIRC20, 0x73A, 0x31, false}, - {IrdaProtocolSIRC20, 0x73A, 0x31, false}, - {IrdaProtocolSIRC20, 0x73A, 0x31, false}, - {IrdaProtocolSIRC20, 0x73A, 0x34, false}, - {IrdaProtocolSIRC20, 0x73A, 0x34, false}, - {IrdaProtocolSIRC20, 0x73A, 0x34, false}, - {IrdaProtocolSIRC20, 0x73A, 0x32, false}, - {IrdaProtocolSIRC20, 0x73A, 0x32, false}, - {IrdaProtocolSIRC20, 0x73A, 0x32, false}, - {IrdaProtocolSIRC20, 0x73A, 0x33, false}, - {IrdaProtocolSIRC20, 0x73A, 0x33, false}, - {IrdaProtocolSIRC20, 0x73A, 0x33, false}, - {IrdaProtocolSIRC20, 0x73A, 0x0F, false}, - {IrdaProtocolSIRC20, 0x73A, 0x0F, false}, - {IrdaProtocolSIRC20, 0x73A, 0x0F, false}, - {IrdaProtocolSIRC20, 0x73A, 0x38, false}, - {IrdaProtocolSIRC20, 0x73A, 0x38, false}, - {IrdaProtocolSIRC20, 0x73A, 0x38, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x10, 0x15, false}, - {IrdaProtocolSIRC, 0x01, 0x2F, false}, - {IrdaProtocolSIRC, 0x01, 0x2F, false}, - {IrdaProtocolSIRC, 0x01, 0x15, false}, - {IrdaProtocolSIRC, 0x01, 0x15, false}, - {IrdaProtocolSIRC, 0x01, 0x15, false}, - {IrdaProtocolSIRC, 0x01, 0x15, false}, +const InfraredMessage test_decoder_sirc_expected1[] = { + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x60, false}, + {InfraredProtocolSIRC, 0x10, 0x60, false}, + {InfraredProtocolSIRC, 0x10, 0x60, false}, + {InfraredProtocolSIRC, 0x10, 0x65, false}, + {InfraredProtocolSIRC, 0x10, 0x65, false}, + {InfraredProtocolSIRC, 0x10, 0x65, false}, + {InfraredProtocolSIRC20, 0x410, 0x17, false}, + {InfraredProtocolSIRC20, 0x410, 0x17, false}, + {InfraredProtocolSIRC20, 0x410, 0x17, false}, + {InfraredProtocolSIRC, 0x10, 0x21, false}, + {InfraredProtocolSIRC, 0x10, 0x21, false}, + {InfraredProtocolSIRC, 0x10, 0x21, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7B, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7B, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7B, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7A, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7A, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7A, false}, + {InfraredProtocolSIRC20, 0x73A, 0x78, false}, + {InfraredProtocolSIRC20, 0x73A, 0x78, false}, + {InfraredProtocolSIRC20, 0x73A, 0x78, false}, + {InfraredProtocolSIRC20, 0x73A, 0x79, false}, + {InfraredProtocolSIRC20, 0x73A, 0x79, false}, + {InfraredProtocolSIRC20, 0x73A, 0x79, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7A, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7A, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7A, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7C, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7D, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7D, false}, + {InfraredProtocolSIRC20, 0x73A, 0x7D, false}, + {InfraredProtocolSIRC20, 0x73A, 0x73, false}, + {InfraredProtocolSIRC20, 0x73A, 0x73, false}, + {InfraredProtocolSIRC20, 0x73A, 0x73, false}, + {InfraredProtocolSIRC, 0x10, 0x13, false}, + {InfraredProtocolSIRC, 0x10, 0x13, false}, + {InfraredProtocolSIRC, 0x10, 0x13, false}, + {InfraredProtocolSIRC, 0x10, 0x13, false}, + {InfraredProtocolSIRC, 0x10, 0x12, false}, + {InfraredProtocolSIRC, 0x10, 0x12, false}, + {InfraredProtocolSIRC, 0x10, 0x12, false}, + {InfraredProtocolSIRC, 0x10, 0x12, false}, + {InfraredProtocolSIRC20, 0x73A, 0x30, false}, + {InfraredProtocolSIRC20, 0x73A, 0x30, false}, + {InfraredProtocolSIRC20, 0x73A, 0x30, false}, + {InfraredProtocolSIRC20, 0x73A, 0x39, false}, + {InfraredProtocolSIRC20, 0x73A, 0x39, false}, + {InfraredProtocolSIRC20, 0x73A, 0x39, false}, + {InfraredProtocolSIRC20, 0x73A, 0x31, false}, + {InfraredProtocolSIRC20, 0x73A, 0x31, false}, + {InfraredProtocolSIRC20, 0x73A, 0x31, false}, + {InfraredProtocolSIRC20, 0x73A, 0x34, false}, + {InfraredProtocolSIRC20, 0x73A, 0x34, false}, + {InfraredProtocolSIRC20, 0x73A, 0x34, false}, + {InfraredProtocolSIRC20, 0x73A, 0x32, false}, + {InfraredProtocolSIRC20, 0x73A, 0x32, false}, + {InfraredProtocolSIRC20, 0x73A, 0x32, false}, + {InfraredProtocolSIRC20, 0x73A, 0x33, false}, + {InfraredProtocolSIRC20, 0x73A, 0x33, false}, + {InfraredProtocolSIRC20, 0x73A, 0x33, false}, + {InfraredProtocolSIRC20, 0x73A, 0x0F, false}, + {InfraredProtocolSIRC20, 0x73A, 0x0F, false}, + {InfraredProtocolSIRC20, 0x73A, 0x0F, false}, + {InfraredProtocolSIRC20, 0x73A, 0x38, false}, + {InfraredProtocolSIRC20, 0x73A, 0x38, false}, + {InfraredProtocolSIRC20, 0x73A, 0x38, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x10, 0x15, false}, + {InfraredProtocolSIRC, 0x01, 0x2F, false}, + {InfraredProtocolSIRC, 0x01, 0x2F, false}, + {InfraredProtocolSIRC, 0x01, 0x15, false}, + {InfraredProtocolSIRC, 0x01, 0x15, false}, + {InfraredProtocolSIRC, 0x01, 0x15, false}, + {InfraredProtocolSIRC, 0x01, 0x15, false}, }; @@ -284,23 +284,23 @@ const uint32_t test_decoder_sirc_input2[] = { 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, }; -const IrdaMessage test_decoder_sirc_expected2[] = { - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, +const InfraredMessage test_decoder_sirc_expected2[] = { + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, /* failed - 13 data bits */ - {IrdaProtocolSIRC, 0x1F, 0x7F, false}, + {InfraredProtocolSIRC, 0x1F, 0x7F, false}, /* failed - 2 data bits */ - {IrdaProtocolSIRC, 0x1F, 0x7F, false}, + {InfraredProtocolSIRC, 0x1F, 0x7F, false}, /* failed - sudden end */ - {IrdaProtocolSIRC, 0x1F, 0x7F, false}, + {InfraredProtocolSIRC, 0x1F, 0x7F, false}, /* failed */ - {IrdaProtocolSIRC, 0x0A, 0x55, false}, + {InfraredProtocolSIRC, 0x0A, 0x55, false}, - {IrdaProtocolSIRC, 0x00, 0x00, false}, + {InfraredProtocolSIRC, 0x00, 0x00, false}, - {IrdaProtocolSIRC, 0x0D, 0x53, false}, + {InfraredProtocolSIRC, 0x0D, 0x53, false}, }; @@ -334,18 +334,18 @@ const uint32_t test_decoder_sirc_input3[] = { 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, }; -const IrdaMessage test_decoder_sirc_expected3[] = { - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x0D, 0x53, false}, - {IrdaProtocolSIRC15, 0x0D, 0x53, false}, - {IrdaProtocolSIRC15, 0x0D, 0x53, false}, - {IrdaProtocolSIRC15, 0x0D, 0x53, false}, - {IrdaProtocolSIRC15, 0x0D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0xFD, 0x13, false}, +const InfraredMessage test_decoder_sirc_expected3[] = { + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x0D, 0x53, false}, + {InfraredProtocolSIRC15, 0x0D, 0x53, false}, + {InfraredProtocolSIRC15, 0x0D, 0x53, false}, + {InfraredProtocolSIRC15, 0x0D, 0x53, false}, + {InfraredProtocolSIRC15, 0x0D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0xFD, 0x13, false}, }; const uint32_t test_decoder_sirc_input4[] = { @@ -357,13 +357,13 @@ const uint32_t test_decoder_sirc_input4[] = { 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, }; -const IrdaMessage test_decoder_sirc_expected4[] = { - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, // {IrdaProtocolSIRC20, 0x15, 0x3ED3, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, +const InfraredMessage test_decoder_sirc_expected4[] = { + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, // {InfraredProtocolSIRC20, 0x15, 0x3ED3, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, }; const uint32_t test_decoder_sirc_input5[] = { @@ -396,48 +396,48 @@ const uint32_t test_decoder_sirc_input5[] = { 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, }; -const IrdaMessage test_decoder_sirc_expected5[] = { - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, +const InfraredMessage test_decoder_sirc_expected5[] = { + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, - {IrdaProtocolSIRC, 0xA, 0x55, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, + {InfraredProtocolSIRC, 0xA, 0x55, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC20, 0xFB5, 0x53, false}, }; -const IrdaMessage test_encoder_sirc_input1[] = { - {IrdaProtocolSIRC, 0xA, 0x55, false}, +const InfraredMessage test_encoder_sirc_input1[] = { + {InfraredProtocolSIRC, 0xA, 0x55, false}, }; const uint32_t test_encoder_sirc_expected1[] = { 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, }; -const IrdaMessage test_encoder_sirc_input2[] = { - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, true}, - {IrdaProtocolSIRC15, 0x7D, 0x53, true}, +const InfraredMessage test_encoder_sirc_input2[] = { + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, true}, + {InfraredProtocolSIRC15, 0x7D, 0x53, true}, }; const uint32_t test_encoder_sirc_expected2[] = { @@ -446,34 +446,34 @@ const uint32_t test_encoder_sirc_expected2[] = { 18600, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, }; -const IrdaMessage test_sirc[] = { - {IrdaProtocolSIRC20, 0x1FFF, 0x7F, false}, - {IrdaProtocolSIRC20, 0x1FFF, 0x7F, true}, - {IrdaProtocolSIRC20, 0x1FFF, 0x7F, true}, - {IrdaProtocolSIRC, 0x00, 0x00, false}, - {IrdaProtocolSIRC, 0x00, 0x00, true}, - {IrdaProtocolSIRC, 0x00, 0x00, true}, +const InfraredMessage test_sirc[] = { + {InfraredProtocolSIRC20, 0x1FFF, 0x7F, false}, + {InfraredProtocolSIRC20, 0x1FFF, 0x7F, true}, + {InfraredProtocolSIRC20, 0x1FFF, 0x7F, true}, + {InfraredProtocolSIRC, 0x00, 0x00, false}, + {InfraredProtocolSIRC, 0x00, 0x00, true}, + {InfraredProtocolSIRC, 0x00, 0x00, true}, - {IrdaProtocolSIRC, 0x1A, 0x22, false}, - {IrdaProtocolSIRC, 0x1A, 0x22, true}, - {IrdaProtocolSIRC, 0x1A, 0x22, true}, - {IrdaProtocolSIRC, 0x17, 0x0A, false}, + {InfraredProtocolSIRC, 0x1A, 0x22, false}, + {InfraredProtocolSIRC, 0x1A, 0x22, true}, + {InfraredProtocolSIRC, 0x1A, 0x22, true}, + {InfraredProtocolSIRC, 0x17, 0x0A, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, false}, - {IrdaProtocolSIRC15, 0x7D, 0x53, true}, - {IrdaProtocolSIRC15, 0x7D, 0x53, true}, - {IrdaProtocolSIRC15, 0x71, 0x0, false}, - {IrdaProtocolSIRC15, 0x15, 0x01, false}, - {IrdaProtocolSIRC15, 0x01, 0x15, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, false}, + {InfraredProtocolSIRC15, 0x7D, 0x53, true}, + {InfraredProtocolSIRC15, 0x7D, 0x53, true}, + {InfraredProtocolSIRC15, 0x71, 0x0, false}, + {InfraredProtocolSIRC15, 0x15, 0x01, false}, + {InfraredProtocolSIRC15, 0x01, 0x15, false}, - {IrdaProtocolSIRC20, 0xAA, 0x55, false}, - {IrdaProtocolSIRC20, 0x331, 0x71, false}, + {InfraredProtocolSIRC20, 0xAA, 0x55, false}, + {InfraredProtocolSIRC20, 0x331, 0x71, false}, - {IrdaProtocolSIRC, 0x00, 0x00, false}, - {IrdaProtocolSIRC, 0x1F, 0x7F, false}, - {IrdaProtocolSIRC15, 0x00, 0x00, false}, - {IrdaProtocolSIRC15, 0xFF, 0x7F, false}, - {IrdaProtocolSIRC20, 0x00, 0x00, false}, - {IrdaProtocolSIRC20, 0x1FFF, 0x7F, false}, + {InfraredProtocolSIRC, 0x00, 0x00, false}, + {InfraredProtocolSIRC, 0x1F, 0x7F, false}, + {InfraredProtocolSIRC15, 0x00, 0x00, false}, + {InfraredProtocolSIRC15, 0xFF, 0x7F, false}, + {InfraredProtocolSIRC20, 0x00, 0x00, false}, + {InfraredProtocolSIRC20, 0x1FFF, 0x7F, false}, }; diff --git a/applications/tests/rpc/rpc_test.c b/applications/tests/rpc/rpc_test.c index 25f641a5..e80d36cb 100644 --- a/applications/tests/rpc/rpc_test.c +++ b/applications/tests/rpc/rpc_test.c @@ -651,7 +651,7 @@ MU_TEST(test_storage_list) { test_rpc_storage_list_run("/int", ++command_id); test_rpc_storage_list_run("/ext", ++command_id); - test_rpc_storage_list_run("/ext/irda", ++command_id); + test_rpc_storage_list_run("/ext/infrared", ++command_id); test_rpc_storage_list_run("/ext/ibutton", ++command_id); test_rpc_storage_list_run("/ext/lfrfid", ++command_id); test_rpc_storage_list_run("error_path", ++command_id); diff --git a/applications/tests/test_index.c b/applications/tests/test_index.c index 6f934318..2e567378 100644 --- a/applications/tests/test_index.c +++ b/applications/tests/test_index.c @@ -11,7 +11,7 @@ #define TAG "UnitTests" int run_minunit(); -int run_minunit_test_irda_decoder_encoder(); +int run_minunit_test_infrared_decoder_encoder(); int run_minunit_test_rpc(); int run_minunit_test_flipper_format(); int run_minunit_test_flipper_format_string(); @@ -53,7 +53,7 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { uint32_t cycle_counter = DWT->CYCCNT; test_result |= run_minunit(); - test_result |= run_minunit_test_irda_decoder_encoder(); + test_result |= run_minunit_test_infrared_decoder_encoder(); test_result |= run_minunit_test_rpc(); test_result |= run_minunit_test_stream(); test_result |= run_minunit_test_flipper_format(); diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index aa2f26cf..03dda94a 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -202,27 +202,6 @@ const uint8_t* const _I_ArrowUpEmpty_14x15[] = {_I_ArrowUpEmpty_14x15_0}; const uint8_t _I_ArrowUpFilled_14x15_0[] = {0x00,0xC0,0x00,0x20,0x01,0xD0,0x02,0xE8,0x05,0xF4,0x0B,0xFA,0x17,0x61,0x21,0xAF,0x3D,0x68,0x05,0xA8,0x05,0x68,0x05,0xA8,0x05,0xE8,0x05,0x08,0x04,0xF8,0x07,}; const uint8_t* const _I_ArrowUpFilled_14x15[] = {_I_ArrowUpFilled_14x15_0}; -const uint8_t _I_Back3_45x8_0[] = {0x00,0x04,0x00,0x10,0x00,0x40,0x00,0x06,0x00,0x18,0x00,0x60,0x00,0x7F,0x00,0xFC,0x01,0xF0,0x07,0x86,0x20,0x18,0x82,0x60,0x08,0x04,0x71,0x10,0xC4,0x41,0x10,0x00,0x21,0x00,0x84,0x00,0x10,0x80,0x00,0x00,0x02,0x00,0x08,0x7E,0x00,0xF8,0x01,0xE0,0x07,}; -const uint8_t* const _I_Back3_45x8[] = {_I_Back3_45x8_0}; - -const uint8_t _I_DoorLeft_70x55_0[] = {0x01,0x00,0x19,0x01,0x00,0x2c,0x32,0x01,0x03,0x04,0x2c,0x18,0x10,0xf0,0x40,0x47,0x82,0x06,0x81,0x03,0xff,0x80,0x08,0x1a,0x20,0x82,0x15,0x28,0x21,0x87,0x82,0x08,0x6f,0xc0,0xb1,0xe6,0x10,0x10,0x8b,0x46,0x20,0x43,0x55,0x8f,0x82,0x10,0x32,0x73,0x0a,0x09,0x89,0x6c,0x1e,0x09,0x00,0x18,0x60,0xf0,0x0c,0x84,0x93,0x82,0x03,0x18,0x0c,0x02,0x1d,0x00,0x90,0x52,0x70,0x50,0x1e,0x00,0x58,0x63,0x90,0x0a,0x06,0x4a,0x09,0x03,0xb0,0x02,0x06,0x70,0x62,0x49,0xf8,0x0c,0x66,0x3f,0xf0,0x41,0x63,0x04,0x43,0x00,0x99,0x60,0x00,0x85,0xc8,0x06,0x14,0xd0,0x80,0x3f,0xc8,0x0d,0xb8,0x10,0x70,0xf8,0x34,0x13,0x03,0x39,0x04,0x1c,0x42,0x19,0xf8,0xa0,0xc2,0x01,0x07,0xef,0x02,0x8c,0x80,0x10,0x9d,0x00,0x43,0xec,0x00,0xa3,0x10,0x04,0x25,0xce,0x19,0xfc,0x88,0x82,0x12,0x0c,0x35,0x10,0x42,0x4c,0xa1,0x90,0x3f,0xc0,0x21,0x22,0x39,0x82,0xc8,0x88,0xd2,0x11,0xf0,0x01,0x88,0xd5,0x18,0xe2,0x08,0x68,0x10,0x0c,0xa8,0x00,0x83,0x81,0xcc,0xd5,0xc3,0x80,0x84,0x82,0x0e,0xcc,0xc0,0x15,0x79,0x02,0x0b,0x98,0xf8,0x11,0x88,0x82,0x0f,0x31,0x19,0x02,0x08,0x2c,0x9f,0x6a,0x1d,0x20,0x41,0x31,0x4c,0x10,0x8d,0x73,0x04,0x23,0xa4,0xc4,0x6c,0xde,0x20,0x42,0xcc,0x01,0x07,0x07,0xff,0x80,0x06,0x3e,0x08,0x38,0x70,0x20,0xa1,0xe0,0x83,0x8e,0x01,0x0c,0xf0,0x73,0x80,0x43,0x70,0x05,0x08,0x00,0x2c,0x04,0xc4,0x46,0x53,0x09,0x98,0x24,0x80,0x65,0x80,0xb0,0xd9,0x84,0x65,0x32,0x06,0x17,0x0f,0x98,0x23,0x63,0xe1,0x88,0xc4,0x08,0x5f,0xc1,0x30,0x9d,0x84,0x4e,0x66,0x94,0x11,0x98,0x75,0x26,0x00,}; -const uint8_t* const _I_DoorLeft_70x55[] = {_I_DoorLeft_70x55_0}; - -const uint8_t _I_DoorLocked_10x56_0[] = {0x01,0x00,0x4e,0x00,0x86,0x40,0x25,0xb0,0x0b,0x6c,0x03,0x9b,0x00,0xc6,0xc0,0x65,0x90,0x10,0x3a,0xc3,0x20,0x31,0xc8,0x04,0xe2,0x01,0x70,0x80,0x78,0x20,0x1c,0x48,0x07,0x22,0x01,0xd0,0x00,0xf0,0x44,0x68,0x90,0x09,0x04,0x02,0x21,0x00,0x84,0x40,0x25,0x80,0x12,0x1e,0x88,0x14,0xc0,0x2e,0x0d,0x11,0xca,0xf8,0x60,0x1c,0x38,0x07,0x1a,0x05,0xcc,0x80,0x72,0x60,0x5c,0x38,0x10,0x1c,0xf9,0x10,0x2e,0x00,0x05,0x60,0x00,0x11,}; -const uint8_t* const _I_DoorLocked_10x56[] = {_I_DoorLocked_10x56_0}; - -const uint8_t _I_DoorRight_70x55_0[] = {0x01,0x00,0x16,0x01,0x81,0xcc,0x01,0x0f,0x60,0x04,0x3f,0x00,0x10,0xf8,0x08,0x0c,0x02,0x05,0x01,0x84,0x02,0x06,0x26,0x0a,0x10,0x8a,0xcc,0xe0,0x1d,0x68,0xe0,0x18,0xab,0xd0,0x0b,0x18,0x10,0x46,0xe6,0x16,0x1e,0x18,0x10,0x46,0xe4,0x28,0x2c,0x98,0x14,0x68,0x00,0x21,0x1d,0x10,0x8c,0x40,0x02,0x0e,0x10,0xa1,0x08,0xc8,0x40,0x42,0x62,0x11,0x94,0x03,0xfd,0xff,0x00,0x0c,0xff,0x0c,0x08,0x28,0x60,0xe4,0xc0,0x85,0x00,0x83,0x00,0x87,0xf1,0x00,0x8c,0x02,0x0b,0x07,0x24,0x84,0xff,0x04,0xc7,0x80,0xa0,0xe4,0xa0,0x81,0x41,0x04,0x17,0x02,0x41,0x49,0x81,0x0e,0x10,0xb2,0xa0,0x82,0x0e,0x9f,0xfc,0x0a,0x62,0xf2,0xc0,0x03,0x92,0xf0,0x08,0x2d,0x78,0x20,0xff,0x02,0x01,0x08,0xae,0x60,0x64,0x38,0x0d,0xb0,0x8d,0x08,0x82,0x11,0x58,0xc4,0x13,0xc0,0x35,0x68,0x62,0x68,0x81,0x09,0x08,0x84,0x40,0x81,0x0d,0x18,0x69,0x10,0x47,0x44,0x66,0x5f,0x21,0xa9,0x29,0x94,0x10,0x2f,0x23,0x53,0x14,0x60,0x42,0x3c,0x08,0xfc,0x02,0x2c,0x62,0x23,0x58,0xd0,0x22,0x00,0x83,0x3e,0x98,0x44,0x43,0x46,0x22,0x30,0x89,0xce,0x01,0x0f,0x70,0x04,0x3f,0x81,0x8a,0x3c,0x21,0xaa,0x70,0x1a,0xe3,0x44,0x1a,0xa6,0x01,0xd2,0x38,0x90,0x8a,0x40,0x20,0xe5,0x96,0x80,0x43,0x81,0x06,0x6b,0x28,0x07,0xf3,0xfe,0x00,0x19,0xf9,0x34,0xc1,0x08,0x8f,0x20,0xf1,0x3e,0x16,0x00,0xa8,0x19,0x00,0x10,0x76,0x03,0xe2,0x3e,0x90,0x45,0x38,0x01,0x42,0x05,0x88,0x44,0x67,0x15,0x70,0x41,0x38,0x04,0x10,0x24,0x03,0x00,0x10,0x20,0x4a,0x46,0xe9,0x46,0xe1,0x04,0x50,0x66,0x40,0x85,0x19,0x98,0x00,0xc0,}; -const uint8_t* const _I_DoorRight_70x55[] = {_I_DoorRight_70x55_0}; - -const uint8_t _I_PassportBottom_128x17_0[] = {0x01,0x00,0x5e,0x00,0x96,0x01,0x97,0xe1,0xff,0x00,0x2e,0x3e,0x68,0x0f,0x5a,0xc5,0x54,0x00,0xb9,0x50,0xfb,0x6a,0x35,0x40,0x05,0xcd,0x4e,0x03,0xfd,0x30,0x0f,0xf8,0x7f,0xa0,0x81,0xfe,0xf9,0x1b,0xfb,0xf3,0x01,0x47,0x66,0x02,0x1b,0x03,0x07,0xe7,0x02,0x0b,0x02,0x07,0xe5,0x82,0x0b,0xf2,0x1c,0xb0,0x01,0x67,0xf0,0x5f,0xd0,0x3f,0x23,0xf0,0x9b,0xc9,0xe5,0x80,0x03,0xd5,0xc0,0x00,0x86,0x01,0xf3,0xe6,0x1e,0x58,0x00,0x36,0xa8,0x06,0xac,0x04,0x30,0x6c,0x30,0xee,0x60,0x1f,0xe0,0x10,0xff,0x0d,0xfb,0x00,}; -const uint8_t* const _I_PassportBottom_128x17[] = {_I_PassportBottom_128x17_0}; - -const uint8_t _I_PassportLeft_6x47_0[] = {0x01,0x00,0x1c,0x00,0x9e,0x40,0xa3,0x32,0x59,0x2c,0x66,0x03,0x01,0x82,0xc2,0x62,0x32,0x50,0x16,0xc8,0x60,0x30,0x28,0x24,0x32,0x39,0x3c,0x9e,0x4d,0x25,0x80,0x1a,}; -const uint8_t* const _I_PassportLeft_6x47[] = {_I_PassportLeft_6x47_0}; - -const uint8_t _I_WarningDolphin_45x42_0[] = {0x01,0x00,0xc6,0x00,0x00,0x1c,0x22,0x04,0x05,0x7f,0xfc,0x1e,0x20,0x05,0x1e,0x04,0x02,0x30,0x05,0x29,0x84,0x02,0xc1,0x20,0x02,0x8c,0x22,0x01,0x80,0x02,0x94,0x10,0x32,0x30,0x10,0x10,0x87,0xca,0x84,0x03,0x10,0x42,0x81,0x48,0x28,0x38,0x08,0x04,0x3e,0x01,0x84,0x83,0xe0,0x30,0x11,0x08,0x05,0xa2,0x11,0x40,0xa0,0x4b,0xc6,0xc5,0x40,0xd0,0x56,0xe0,0x10,0x60,0x29,0x54,0xf0,0x10,0x18,0xf0,0x14,0x6b,0xf6,0x0c,0x04,0x3e,0x40,0x05,0x12,0x80,0xc1,0xe4,0x01,0xd2,0xf8,0x40,0xe4,0x18,0x09,0xf4,0x03,0xf1,0x01,0x90,0x40,0x28,0x30,0x0f,0xe4,0x00,0x16,0x24,0x11,0xbf,0x01,0x44,0xee,0x53,0xf0,0x29,0xf0,0x3e,0x02,0x91,0x3b,0x8c,0xc3,0x81,0x13,0x90,0x48,0x20,0x3f,0xf9,0xfc,0x42,0x60,0x05,0x10,0x98,0x81,0x56,0x11,0x38,0x02,0x9c,0x1a,0x31,0x1e,0x02,0x8f,0x02,0x03,0x1c,0x90,0xc0,0x7c,0x02,0xf1,0xce,0x02,0x07,0x01,0x1f,0x80,0x63,0xa8,0x08,0x71,0x3c,0x8e,0x39,0x24,0x40,0x51,0xc7,0x81,0x53,0x0f,0x3c,0x02,0x9d,0x1e,0x38,0x29,0x10,0x29,0x17,0xc8,0x0a,0x32,0x3a,0x00,0x14,0x4b,0xa2,0x05,0x58,0x98,0x15,0x22,0x20,0x54,0x84,0x81,0x50,}; -const uint8_t* const _I_WarningDolphin_45x42[] = {_I_WarningDolphin_45x42_0}; - const uint8_t _I_Back_15x10_0[] = {0x00,0x04,0x00,0x06,0x00,0xFF,0x0F,0x06,0x10,0x04,0x20,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x10,0xFE,0x0F,}; const uint8_t* const _I_Back_15x10[] = {_I_Back_15x10_0}; @@ -238,23 +217,23 @@ const uint8_t* const _I_Down_hvr_25x27[] = {_I_Down_hvr_25x27_0}; const uint8_t _I_Fill_marker_7x7_0[] = {0x00,0x1C,0x32,0x6F,0x5F,0x7F,0x3E,0x1C,}; const uint8_t* const _I_Fill_marker_7x7[] = {_I_Fill_marker_7x7_0}; -const uint8_t _I_IrdaArrowDown_4x8_0[] = {0x00,0xFF,0x7E,0x3C,0x18,}; -const uint8_t* const _I_IrdaArrowDown_4x8[] = {_I_IrdaArrowDown_4x8_0}; +const uint8_t _I_InfraredArrowDown_4x8_0[] = {0x00,0xFF,0x7E,0x3C,0x18,}; +const uint8_t* const _I_InfraredArrowDown_4x8[] = {_I_InfraredArrowDown_4x8_0}; -const uint8_t _I_IrdaArrowUp_4x8_0[] = {0x00,0x18,0x3C,0x7E,0xFF,}; -const uint8_t* const _I_IrdaArrowUp_4x8[] = {_I_IrdaArrowUp_4x8_0}; +const uint8_t _I_InfraredArrowUp_4x8_0[] = {0x00,0x18,0x3C,0x7E,0xFF,}; +const uint8_t* const _I_InfraredArrowUp_4x8[] = {_I_InfraredArrowUp_4x8_0}; -const uint8_t _I_IrdaLearnShort_128x31_0[] = {0x01,0x00,0x10,0x01,0x00,0x47,0xfb,0xfe,0x00,0x38,0x38,0x3e,0x20,0x20,0x54,0x84,0x03,0x9f,0xc0,0x06,0x58,0x80,0x3d,0xf2,0x00,0x65,0x90,0x03,0xde,0x90,0x06,0x5a,0x07,0xc0,0x8a,0x70,0x1a,0x04,0x02,0x51,0x80,0x03,0x94,0x02,0x3f,0x40,0x20,0x24,0x0b,0x01,0x00,0x92,0x70,0x35,0x40,0x01,0xe0,0xdf,0xf0,0x10,0x40,0x71,0x58,0x20,0x90,0x88,0x0c,0x4a,0x81,0x55,0x00,0x0f,0x87,0xf7,0x00,0x82,0x43,0x36,0x16,0xdc,0x9c,0x12,0x21,0x01,0x85,0x70,0x3f,0xc1,0xf1,0xf8,0xfc,0x60,0x20,0xf5,0x90,0x40,0xa1,0x34,0x08,0x18,0x7c,0x7e,0x24,0x91,0x07,0x8c,0xc0,0x5e,0x52,0x28,0x14,0x17,0x81,0x01,0x0f,0x8f,0xe7,0xe3,0x03,0x1f,0x8e,0x02,0xdb,0x03,0x8e,0x49,0x20,0x50,0x2e,0x04,0x72,0xbd,0x55,0xdc,0xeb,0xa0,0x7c,0x4f,0x68,0xbc,0x60,0x72,0x40,0x79,0x50,0x23,0x9a,0x6d,0x56,0x66,0x5c,0x0f,0x21,0x78,0x9b,0x04,0x1e,0x28,0x21,0x8e,0x5c,0x43,0xe6,0x2f,0x10,0xf9,0x0b,0xc7,0x04,0x99,0x18,0x06,0xe0,0x7e,0x56,0x32,0x78,0x8f,0xc4,0x08,0x32,0x20,0x79,0x48,0x2b,0x85,0xf2,0xf8,0x83,0xc4,0x5c,0x3f,0x03,0x78,0xd0,0x81,0xe3,0xc0,0xdf,0x9f,0xcb,0xf3,0x04,0xc6,0x7d,0xfb,0xdf,0x34,0x78,0xd0,0x45,0xe5,0x7e,0x4f,0x97,0xe2,0x09,0x80,0x07,0x88,0xbc,0x61,0x00,0xf3,0xd8,0x2f,0xcb,0xe0,0xcf,0x60,0x68,0xd0,0x30,0x15,0xfa,0xac,0x36,0x3f,0x60,0x77,0xb3,0x80,0x5d,0xe6,0x4b,0x20,0x03,0x03,0xc4,0x01,0xd0,0x10,0x7f,0x40,0x81,0xfc,0xa7,0x10,0x06,0x99,0xd0,0x01,0x51,0x00,0x7f,0x48,0x01,0xfd,0xc0,0x43,0x98,0x00,0x8e,0xfe,0x00,0xf0,}; -const uint8_t* const _I_IrdaLearnShort_128x31[] = {_I_IrdaLearnShort_128x31_0}; +const uint8_t _I_InfraredLearnShort_128x31_0[] = {0x01,0x00,0x10,0x01,0x00,0x47,0xfb,0xfe,0x00,0x38,0x38,0x3e,0x20,0x20,0x54,0x84,0x03,0x9f,0xc0,0x06,0x58,0x80,0x3d,0xf2,0x00,0x65,0x90,0x03,0xde,0x90,0x06,0x5a,0x07,0xc0,0x8a,0x70,0x1a,0x04,0x02,0x51,0x80,0x03,0x94,0x02,0x3f,0x40,0x20,0x24,0x0b,0x01,0x00,0x92,0x70,0x35,0x40,0x01,0xe0,0xdf,0xf0,0x10,0x40,0x71,0x58,0x20,0x90,0x88,0x0c,0x4a,0x81,0x55,0x00,0x0f,0x87,0xf7,0x00,0x82,0x43,0x36,0x16,0xdc,0x9c,0x12,0x21,0x01,0x85,0x70,0x3f,0xc1,0xf1,0xf8,0xfc,0x60,0x20,0xf5,0x90,0x40,0xa1,0x34,0x08,0x18,0x7c,0x7e,0x24,0x91,0x07,0x8c,0xc0,0x5e,0x52,0x28,0x14,0x17,0x81,0x01,0x0f,0x8f,0xe7,0xe3,0x03,0x1f,0x8e,0x02,0xdb,0x03,0x8e,0x49,0x20,0x50,0x2e,0x04,0x72,0xbd,0x55,0xdc,0xeb,0xa0,0x7c,0x4f,0x68,0xbc,0x60,0x72,0x40,0x79,0x50,0x23,0x9a,0x6d,0x56,0x66,0x5c,0x0f,0x21,0x78,0x9b,0x04,0x1e,0x28,0x21,0x8e,0x5c,0x43,0xe6,0x2f,0x10,0xf9,0x0b,0xc7,0x04,0x99,0x18,0x06,0xe0,0x7e,0x56,0x32,0x78,0x8f,0xc4,0x08,0x32,0x20,0x79,0x48,0x2b,0x85,0xf2,0xf8,0x83,0xc4,0x5c,0x3f,0x03,0x78,0xd0,0x81,0xe3,0xc0,0xdf,0x9f,0xcb,0xf3,0x04,0xc6,0x7d,0xfb,0xdf,0x34,0x78,0xd0,0x45,0xe5,0x7e,0x4f,0x97,0xe2,0x09,0x80,0x07,0x88,0xbc,0x61,0x00,0xf3,0xd8,0x2f,0xcb,0xe0,0xcf,0x60,0x68,0xd0,0x30,0x15,0xfa,0xac,0x36,0x3f,0x60,0x77,0xb3,0x80,0x5d,0xe6,0x4b,0x20,0x03,0x03,0xc4,0x01,0xd0,0x10,0x7f,0x40,0x81,0xfc,0xa7,0x10,0x06,0x99,0xd0,0x01,0x51,0x00,0x7f,0x48,0x01,0xfd,0xc0,0x43,0x98,0x00,0x8e,0xfe,0x00,0xf0,}; +const uint8_t* const _I_InfraredLearnShort_128x31[] = {_I_InfraredLearnShort_128x31_0}; -const uint8_t _I_IrdaLearn_128x64_0[] = {0x01,0x00,0xcc,0x01,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x07,0x80,0x3f,0x01,0x07,0x82,0x41,0x21,0x20,0x73,0x00,0x8e,0x82,0x0f,0x00,0xa0,0x01,0x46,0x11,0x00,0x07,0xc0,0x28,0x41,0xe5,0xc8,0xba,0x63,0xa7,0x70,0x6b,0x3d,0xbb,0x99,0x19,0xee,0x68,0x71,0x16,0x3f,0x70,0x3c,0x64,0xf9,0x58,0x25,0x26,0x13,0x91,0xc9,0x64,0xa4,0x99,0x2d,0x06,0x1f,0x29,0x42,0x07,0x8c,0x80,0x1e,0x50,0xff,0x88,0x3c,0x67,0x80,0xf1,0xc1,0x03,0xde,0x03,0x11,0x07,0x8c,0x10,0x1e,0x38,0x40,0x79,0xf0,0x32,0x80,0xf1,0x83,0x58,0x72,0x58,0xc8,0xc6,0x73,0x40,0x3f,0x10,0x78,0x9e,0xf1,0x17,0xe9,0xcf,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x02,0x44,0x18,0xa3,0x80,0x82,0x32,0x06,0x44,0x0f,0xf0,0x73,0x5d,0xe3,0x92,0x7e,0xcf,0x06,0x3b,0xc3,0xa4,0xdd,0xfc,0xc8,0x35,0xca,0x44,0xa5,0x34,0x5c,0x16,0x92,0x89,0x4a,0x91,0x4a,0x60,0x20,0xf7,0xa4,0x83,0xc6,0x8e,0x0f,0xba,0x88,0x3c,0x68,0x00,0xf7,0x80,0x65,0xe3,0x9c,0x7a,0x6e,0x0a,0x49,0xc3,0xb8,0xc8,0xa4,0xc0,0xf5,0x00,0x08,0x1d,0xc0,0x0e,0x0f,0xf0,0x07,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x2f,0xfb,0xfe,0x00,0x38,0x39,0x97,0xa1,0x00,0xe7,0xf0,0x3b,0x1c,0x00,0xd9,0x00,0x32,0xc8,0x01,0xef,0x48,0x03,0x2d,0x03,0xe0,0x45,0x38,0x0d,0x02,0x01,0x28,0xc0,0x01,0xca,0x01,0x1f,0xa0,0x10,0x12,0x05,0x80,0x80,0x49,0x38,0x1a,0xa0,0x00,0xf0,0x6f,0xf8,0x08,0x20,0x38,0xac,0x10,0x48,0x44,0x06,0x25,0x40,0xaa,0x80,0x07,0xc3,0xfb,0x80,0x41,0x21,0x9b,0x0b,0x6e,0x4e,0x09,0x10,0x80,0xc2,0xb8,0x1f,0xe0,0xf8,0xfc,0x7e,0x30,0x10,0x7a,0xc8,0x20,0x50,0x9a,0x04,0x0c,0x3e,0x3f,0x12,0x48,0x83,0xc6,0x60,0x2f,0x29,0x14,0x0a,0x0b,0xc0,0x80,0x87,0xc7,0xf3,0xf1,0x81,0x8f,0xc7,0x01,0x6d,0x81,0xc7,0x24,0x90,0x28,0x17,0x02,0x39,0x5e,0xaa,0xee,0x75,0xd0,0x3e,0x27,0xb4,0x5e,0x30,0x39,0x20,0x3c,0xa8,0x11,0xcd,0x36,0xab,0x33,0x2e,0x07,0x90,0xbc,0x4d,0x82,0x0f,0x14,0x10,0xc7,0x2e,0x21,0xf3,0x17,0x88,0x7c,0x85,0xe3,0x82,0x4c,0x8c,0x03,0x70,0x3f,0x2b,0x19,0x3c,0x47,0xe2,0x04,0x19,0x10,0x3c,0xa4,0x15,0xc2,0xf9,0x7c,0x41,0xe2,0x2e,0x1f,0x81,0xbc,0x68,0x40,0xf1,0xe0,0x6f,0xcf,0xe5,0xf9,0x82,0x63,0x3e,0xfd,0xef,0x9a,0x3c,0x68,0x22,0xf2,0xbf,0x27,0xcb,0xf1,0x04,0xc0,0x03,0xc4,0x5e,0x30,0x80,0x79,0xec,0x17,0xe5,0xf0,0x67,0xb0,0x34,0x68,0x18,0x0a,0xfd,0x56,0x1b,0x1f,0xb0,0x3b,0xd9,0xc0,0x2e,0xf3,0x25,0x90,0x01,0x81,0xe2,0x00,0xe8,0x08,0x3f,0xa0,0x40,0xfe,0x53,0x88,0x03,0x4c,0xe8,0x00,0xa8,0x80,0x3f,0xa4,0x00,0xfe,0xe0,0x21,0xcc,0x00,0x47,0x7f,0x00,0x78,}; -const uint8_t* const _I_IrdaLearn_128x64[] = {_I_IrdaLearn_128x64_0}; +const uint8_t _I_InfraredLearn_128x64_0[] = {0x01,0x00,0xcc,0x01,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x07,0x80,0x3f,0x01,0x07,0x82,0x41,0x21,0x20,0x73,0x00,0x8e,0x82,0x0f,0x00,0xa0,0x01,0x46,0x11,0x00,0x07,0xc0,0x28,0x41,0xe5,0xc8,0xba,0x63,0xa7,0x70,0x6b,0x3d,0xbb,0x99,0x19,0xee,0x68,0x71,0x16,0x3f,0x70,0x3c,0x64,0xf9,0x58,0x25,0x26,0x13,0x91,0xc9,0x64,0xa4,0x99,0x2d,0x06,0x1f,0x29,0x42,0x07,0x8c,0x80,0x1e,0x50,0xff,0x88,0x3c,0x67,0x80,0xf1,0xc1,0x03,0xde,0x03,0x11,0x07,0x8c,0x10,0x1e,0x38,0x40,0x79,0xf0,0x32,0x80,0xf1,0x83,0x58,0x72,0x58,0xc8,0xc6,0x73,0x40,0x3f,0x10,0x78,0x9e,0xf1,0x17,0xe9,0xcf,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x02,0x44,0x18,0xa3,0x80,0x82,0x32,0x06,0x44,0x0f,0xf0,0x73,0x5d,0xe3,0x92,0x7e,0xcf,0x06,0x3b,0xc3,0xa4,0xdd,0xfc,0xc8,0x35,0xca,0x44,0xa5,0x34,0x5c,0x16,0x92,0x89,0x4a,0x91,0x4a,0x60,0x20,0xf7,0xa4,0x83,0xc6,0x8e,0x0f,0xba,0x88,0x3c,0x68,0x00,0xf7,0x80,0x65,0xe3,0x9c,0x7a,0x6e,0x0a,0x49,0xc3,0xb8,0xc8,0xa4,0xc0,0xf5,0x00,0x08,0x1d,0xc0,0x0e,0x0f,0xf0,0x07,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x2f,0xfb,0xfe,0x00,0x38,0x39,0x97,0xa1,0x00,0xe7,0xf0,0x3b,0x1c,0x00,0xd9,0x00,0x32,0xc8,0x01,0xef,0x48,0x03,0x2d,0x03,0xe0,0x45,0x38,0x0d,0x02,0x01,0x28,0xc0,0x01,0xca,0x01,0x1f,0xa0,0x10,0x12,0x05,0x80,0x80,0x49,0x38,0x1a,0xa0,0x00,0xf0,0x6f,0xf8,0x08,0x20,0x38,0xac,0x10,0x48,0x44,0x06,0x25,0x40,0xaa,0x80,0x07,0xc3,0xfb,0x80,0x41,0x21,0x9b,0x0b,0x6e,0x4e,0x09,0x10,0x80,0xc2,0xb8,0x1f,0xe0,0xf8,0xfc,0x7e,0x30,0x10,0x7a,0xc8,0x20,0x50,0x9a,0x04,0x0c,0x3e,0x3f,0x12,0x48,0x83,0xc6,0x60,0x2f,0x29,0x14,0x0a,0x0b,0xc0,0x80,0x87,0xc7,0xf3,0xf1,0x81,0x8f,0xc7,0x01,0x6d,0x81,0xc7,0x24,0x90,0x28,0x17,0x02,0x39,0x5e,0xaa,0xee,0x75,0xd0,0x3e,0x27,0xb4,0x5e,0x30,0x39,0x20,0x3c,0xa8,0x11,0xcd,0x36,0xab,0x33,0x2e,0x07,0x90,0xbc,0x4d,0x82,0x0f,0x14,0x10,0xc7,0x2e,0x21,0xf3,0x17,0x88,0x7c,0x85,0xe3,0x82,0x4c,0x8c,0x03,0x70,0x3f,0x2b,0x19,0x3c,0x47,0xe2,0x04,0x19,0x10,0x3c,0xa4,0x15,0xc2,0xf9,0x7c,0x41,0xe2,0x2e,0x1f,0x81,0xbc,0x68,0x40,0xf1,0xe0,0x6f,0xcf,0xe5,0xf9,0x82,0x63,0x3e,0xfd,0xef,0x9a,0x3c,0x68,0x22,0xf2,0xbf,0x27,0xcb,0xf1,0x04,0xc0,0x03,0xc4,0x5e,0x30,0x80,0x79,0xec,0x17,0xe5,0xf0,0x67,0xb0,0x34,0x68,0x18,0x0a,0xfd,0x56,0x1b,0x1f,0xb0,0x3b,0xd9,0xc0,0x2e,0xf3,0x25,0x90,0x01,0x81,0xe2,0x00,0xe8,0x08,0x3f,0xa0,0x40,0xfe,0x53,0x88,0x03,0x4c,0xe8,0x00,0xa8,0x80,0x3f,0xa4,0x00,0xfe,0xe0,0x21,0xcc,0x00,0x47,0x7f,0x00,0x78,}; +const uint8_t* const _I_InfraredLearn_128x64[] = {_I_InfraredLearn_128x64_0}; -const uint8_t _I_IrdaSendShort_128x34_0[] = {0x01,0x00,0x42,0x01,0xfe,0x7f,0xc0,0x07,0x03,0x07,0xc4,0x10,0x0a,0x90,0x20,0x7f,0x83,0xfc,0x04,0x3c,0x01,0xc2,0x7f,0xf8,0x80,0x43,0x9f,0x83,0xca,0x40,0x1f,0x5e,0x27,0x7e,0xab,0x55,0xee,0x83,0xce,0x38,0x0f,0x6d,0x50,0x00,0xa5,0xc0,0xf2,0x89,0x03,0xda,0xfe,0x1f,0x1f,0xa8,0x7c,0x48,0xc3,0x09,0x07,0xb6,0xae,0x54,0x1f,0x19,0xd4,0x08,0x40,0x30,0x5f,0x81,0x1c,0x63,0xfe,0x08,0x1f,0x12,0xbe,0x3f,0x49,0x0e,0x02,0x09,0x58,0x04,0x0c,0xd7,0xf1,0x0f,0x1f,0x8e,0x2b,0x11,0xaa,0x95,0x40,0xa2,0x34,0x08,0x16,0xa0,0x4e,0x32,0xab,0xe4,0x7f,0x89,0x77,0x0b,0x0d,0xd6,0x7f,0x82,0x84,0x50,0x20,0x3d,0x81,0x48,0xcd,0x67,0xd3,0xe1,0xf8,0xc8,0xb4,0x43,0xf1,0xc1,0x62,0x24,0x10,0x1b,0x46,0x80,0x3e,0x3f,0xe9,0xf8,0xfc,0xfa,0xa1,0xf1,0xa4,0x68,0x20,0x13,0x8a,0x00,0x7c,0x67,0xf7,0xe3,0xfa,0x4a,0x81,0xe3,0x40,0x80,0x66,0x38,0x66,0xa1,0xeb,0xdd,0x47,0xec,0x0f,0x2c,0x47,0x0e,0xa9,0x35,0xe9,0xd9,0x47,0xe2,0x1f,0x21,0xf8,0xd2,0x17,0xc3,0x88,0x91,0xeb,0x83,0xe6,0xbf,0x42,0x78,0xc4,0x20,0x10,0x88,0x05,0x5c,0x7e,0x7a,0xe1,0xfa,0x42,0x01,0xe5,0x84,0x1f,0x89,0x7c,0xbf,0xf7,0x7b,0xaf,0xdd,0x3e,0x31,0x10,0xe8,0xc2,0x3f,0x01,0xf1,0x3f,0x98,0x7c,0xa7,0x6a,0xf1,0x07,0x97,0x03,0x5e,0x9f,0x36,0x28,0xf7,0x7f,0xa1,0xf1,0x81,0x03,0xca,0x01,0x56,0x5f,0x9f,0xb8,0x3c,0x3e,0xa7,0xf8,0xc1,0x01,0xe5,0xf0,0x15,0x0f,0x85,0xbe,0x21,0xf1,0x00,0x08,0x7c,0x60,0x04,0xf1,0x77,0x96,0x7e,0x02,0xff,0x10,0x7c,0x00,0x16,0x08,0x05,0x40,0x78,0xa3,0xc4,0x00,0xb2,0x40,0x7b,0x2b,0xc4,0x00,0xb5,0x48,0x78,0x3d,0x70,0x01,0xf7,0x07,0xb4,0x00,0x94,0x23,0xfc,0x01,0x18,0x00,0xff,0x85,0xf3,0xff,0xc0,0xc3,0x0f,0x00,0xf0,0x09,0xce,0xf0,0x03,0x2f,0xc0,0x61,0x3f,0xe0,0xf8,0x00,0x30,0x3f,0xc0,}; -const uint8_t* const _I_IrdaSendShort_128x34[] = {_I_IrdaSendShort_128x34_0}; +const uint8_t _I_InfraredSendShort_128x34_0[] = {0x01,0x00,0x42,0x01,0xfe,0x7f,0xc0,0x07,0x03,0x07,0xc4,0x10,0x0a,0x90,0x20,0x7f,0x83,0xfc,0x04,0x3c,0x01,0xc2,0x7f,0xf8,0x80,0x43,0x9f,0x83,0xca,0x40,0x1f,0x5e,0x27,0x7e,0xab,0x55,0xee,0x83,0xce,0x38,0x0f,0x6d,0x50,0x00,0xa5,0xc0,0xf2,0x89,0x03,0xda,0xfe,0x1f,0x1f,0xa8,0x7c,0x48,0xc3,0x09,0x07,0xb6,0xae,0x54,0x1f,0x19,0xd4,0x08,0x40,0x30,0x5f,0x81,0x1c,0x63,0xfe,0x08,0x1f,0x12,0xbe,0x3f,0x49,0x0e,0x02,0x09,0x58,0x04,0x0c,0xd7,0xf1,0x0f,0x1f,0x8e,0x2b,0x11,0xaa,0x95,0x40,0xa2,0x34,0x08,0x16,0xa0,0x4e,0x32,0xab,0xe4,0x7f,0x89,0x77,0x0b,0x0d,0xd6,0x7f,0x82,0x84,0x50,0x20,0x3d,0x81,0x48,0xcd,0x67,0xd3,0xe1,0xf8,0xc8,0xb4,0x43,0xf1,0xc1,0x62,0x24,0x10,0x1b,0x46,0x80,0x3e,0x3f,0xe9,0xf8,0xfc,0xfa,0xa1,0xf1,0xa4,0x68,0x20,0x13,0x8a,0x00,0x7c,0x67,0xf7,0xe3,0xfa,0x4a,0x81,0xe3,0x40,0x80,0x66,0x38,0x66,0xa1,0xeb,0xdd,0x47,0xec,0x0f,0x2c,0x47,0x0e,0xa9,0x35,0xe9,0xd9,0x47,0xe2,0x1f,0x21,0xf8,0xd2,0x17,0xc3,0x88,0x91,0xeb,0x83,0xe6,0xbf,0x42,0x78,0xc4,0x20,0x10,0x88,0x05,0x5c,0x7e,0x7a,0xe1,0xfa,0x42,0x01,0xe5,0x84,0x1f,0x89,0x7c,0xbf,0xf7,0x7b,0xaf,0xdd,0x3e,0x31,0x10,0xe8,0xc2,0x3f,0x01,0xf1,0x3f,0x98,0x7c,0xa7,0x6a,0xf1,0x07,0x97,0x03,0x5e,0x9f,0x36,0x28,0xf7,0x7f,0xa1,0xf1,0x81,0x03,0xca,0x01,0x56,0x5f,0x9f,0xb8,0x3c,0x3e,0xa7,0xf8,0xc1,0x01,0xe5,0xf0,0x15,0x0f,0x85,0xbe,0x21,0xf1,0x00,0x08,0x7c,0x60,0x04,0xf1,0x77,0x96,0x7e,0x02,0xff,0x10,0x7c,0x00,0x16,0x08,0x05,0x40,0x78,0xa3,0xc4,0x00,0xb2,0x40,0x7b,0x2b,0xc4,0x00,0xb5,0x48,0x78,0x3d,0x70,0x01,0xf7,0x07,0xb4,0x00,0x94,0x23,0xfc,0x01,0x18,0x00,0xff,0x85,0xf3,0xff,0xc0,0xc3,0x0f,0x00,0xf0,0x09,0xce,0xf0,0x03,0x2f,0xc0,0x61,0x3f,0xe0,0xf8,0x00,0x30,0x3f,0xc0,}; +const uint8_t* const _I_InfraredSendShort_128x34[] = {_I_InfraredSendShort_128x34_0}; -const uint8_t _I_IrdaSend_128x64_0[] = {0x01,0x00,0xe2,0x01,0x00,0x78,0x03,0xc0,0x1e,0x00,0xfe,0x04,0x0e,0x05,0x82,0xd7,0x81,0xca,0x21,0x08,0x01,0x8c,0x10,0x0e,0x54,0x00,0x20,0xe0,0xa4,0x00,0xfb,0xb2,0x4e,0xb0,0xfa,0x0e,0x74,0xc7,0x0f,0x3b,0xce,0x4e,0xec,0xf0,0xe1,0x79,0xe4,0xe9,0x58,0x2d,0x3d,0x4a,0x95,0x41,0x89,0x52,0x31,0x59,0x40,0xfa,0x64,0x01,0xe3,0xa0,0xa9,0x5e,0x81,0xe7,0xf4,0x07,0xcc,0x28,0x1e,0x71,0x40,0x7a,0x58,0x01,0xe4,0x3f,0x1c,0x0c,0x4f,0x11,0x0b,0xb3,0x83,0xcc,0x00,0x94,0x20,0x2a,0x03,0xa0,0x1e,0xd0,0x34,0xdf,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x4c,0xf0,0x17,0x4c,0x81,0xa0,0x18,0x18,0x1f,0x39,0x90,0x6c,0x60,0x27,0x70,0xe9,0x3f,0x67,0x03,0x3c,0x80,0x83,0xde,0x81,0x4a,0x84,0xca,0x68,0xb8,0x2b,0xf0,0x3f,0x29,0x20,0xfe,0xa8,0xe0,0x85,0xf3,0x80,0xa5,0xc3,0xb8,0xf4,0xd8,0x11,0x3e,0x40,0x04,0x1b,0x23,0x7d,0x83,0xcd,0x1f,0x60,0x0f,0x00,0x78,0x03,0x7f,0x9f,0xf0,0x01,0xc0,0xc1,0xf1,0x04,0x02,0xa4,0x08,0x1f,0xe0,0xff,0x01,0x0f,0x00,0x70,0x9f,0xfe,0x20,0x10,0xe7,0xe0,0xf2,0x90,0x07,0xd7,0x89,0xdf,0xaa,0xd5,0x7b,0xa0,0xf3,0x8e,0x03,0xdb,0x54,0x00,0x29,0x70,0x3c,0xa2,0x40,0xf6,0xbf,0x87,0xc7,0xea,0x1f,0x12,0x30,0xc2,0x41,0xed,0xab,0x95,0x07,0xc6,0x75,0x02,0x10,0x0c,0x17,0xe0,0x47,0x18,0xff,0x82,0x07,0xc4,0xaf,0x8f,0xd2,0x43,0x80,0x82,0x56,0x01,0x03,0x35,0xfc,0x43,0xc7,0xe3,0x8a,0xc4,0x6a,0xa5,0x50,0x28,0x8d,0x02,0x05,0xa8,0x13,0x8c,0xaa,0xf9,0x1f,0xe2,0x5d,0xc2,0xc3,0x75,0x9f,0xe0,0xa1,0x14,0x08,0x0f,0x60,0x52,0x33,0x59,0xf4,0xf8,0x7e,0x32,0x2d,0x10,0xfc,0x70,0x58,0x89,0x04,0x06,0xd1,0xa0,0x0f,0x8f,0xfa,0x7e,0x3f,0x3e,0xa8,0x7c,0x69,0x1a,0x08,0x04,0xe2,0x80,0x1f,0x19,0xfd,0xf8,0xfe,0x92,0xa0,0x78,0xd0,0x20,0x19,0x8e,0x19,0xa8,0x7a,0xf7,0x51,0xfb,0x03,0xcb,0x11,0xc3,0xaa,0x4d,0x7a,0x76,0x51,0xf8,0x87,0xc8,0x7e,0x34,0x85,0xf0,0xe2,0x24,0x7a,0xe0,0xf9,0xaf,0xd0,0x9e,0x31,0x08,0x04,0x22,0x01,0x57,0x1f,0x9e,0xb8,0x7e,0x90,0x80,0x79,0x61,0x07,0xe2,0x5f,0x2f,0xfd,0xde,0xeb,0xf7,0x4f,0x8c,0x44,0x3a,0x30,0x8f,0xc0,0x7c,0x4f,0xe6,0x1f,0x29,0xda,0xbc,0x41,0xe5,0xc0,0xd7,0xa7,0xcd,0x8a,0x3d,0xdf,0xe8,0x7c,0x60,0x40,0xf2,0x80,0x55,0x97,0xe7,0xee,0x0f,0x0f,0xa9,0xfe,0x30,0x40,0x79,0x7c,0x05,0x43,0xe1,0x6f,0x88,0x7c,0x40,0x02,0x1f,0x18,0x01,0x3c,0x5d,0xe5,0x9f,0x80,0xbf,0xc4,0x1f,0x00,0x05,0x82,0x01,0x50,0x1e,0x28,0xf1,0x00,0x2c,0x90,0x1e,0xca,0xf1,0x00,0x2d,0x52,0x1e,0x0f,0x5c,0x00,0x7d,0xc1,0xed,0x00,0x25,0x08,0xff,0x00,0x46,0x00,0x3f,0xe1,0x7c,0xff,0xf0,0x30,0xc3,0xc0,0x3c,0x02,0x73,0xbc,0x00,0xcb,0xf0,0x18,0x4f,0xf8,0x3e,0x00,0x0c,0x0f,0xf0,}; -const uint8_t* const _I_IrdaSend_128x64[] = {_I_IrdaSend_128x64_0}; +const uint8_t _I_InfraredSend_128x64_0[] = {0x01,0x00,0xe2,0x01,0x00,0x78,0x03,0xc0,0x1e,0x00,0xfe,0x04,0x0e,0x05,0x82,0xd7,0x81,0xca,0x21,0x08,0x01,0x8c,0x10,0x0e,0x54,0x00,0x20,0xe0,0xa4,0x00,0xfb,0xb2,0x4e,0xb0,0xfa,0x0e,0x74,0xc7,0x0f,0x3b,0xce,0x4e,0xec,0xf0,0xe1,0x79,0xe4,0xe9,0x58,0x2d,0x3d,0x4a,0x95,0x41,0x89,0x52,0x31,0x59,0x40,0xfa,0x64,0x01,0xe3,0xa0,0xa9,0x5e,0x81,0xe7,0xf4,0x07,0xcc,0x28,0x1e,0x71,0x40,0x7a,0x58,0x01,0xe4,0x3f,0x1c,0x0c,0x4f,0x11,0x0b,0xb3,0x83,0xcc,0x00,0x94,0x20,0x2a,0x03,0xa0,0x1e,0xd0,0x34,0xdf,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x4c,0xf0,0x17,0x4c,0x81,0xa0,0x18,0x18,0x1f,0x39,0x90,0x6c,0x60,0x27,0x70,0xe9,0x3f,0x67,0x03,0x3c,0x80,0x83,0xde,0x81,0x4a,0x84,0xca,0x68,0xb8,0x2b,0xf0,0x3f,0x29,0x20,0xfe,0xa8,0xe0,0x85,0xf3,0x80,0xa5,0xc3,0xb8,0xf4,0xd8,0x11,0x3e,0x40,0x04,0x1b,0x23,0x7d,0x83,0xcd,0x1f,0x60,0x0f,0x00,0x78,0x03,0x7f,0x9f,0xf0,0x01,0xc0,0xc1,0xf1,0x04,0x02,0xa4,0x08,0x1f,0xe0,0xff,0x01,0x0f,0x00,0x70,0x9f,0xfe,0x20,0x10,0xe7,0xe0,0xf2,0x90,0x07,0xd7,0x89,0xdf,0xaa,0xd5,0x7b,0xa0,0xf3,0x8e,0x03,0xdb,0x54,0x00,0x29,0x70,0x3c,0xa2,0x40,0xf6,0xbf,0x87,0xc7,0xea,0x1f,0x12,0x30,0xc2,0x41,0xed,0xab,0x95,0x07,0xc6,0x75,0x02,0x10,0x0c,0x17,0xe0,0x47,0x18,0xff,0x82,0x07,0xc4,0xaf,0x8f,0xd2,0x43,0x80,0x82,0x56,0x01,0x03,0x35,0xfc,0x43,0xc7,0xe3,0x8a,0xc4,0x6a,0xa5,0x50,0x28,0x8d,0x02,0x05,0xa8,0x13,0x8c,0xaa,0xf9,0x1f,0xe2,0x5d,0xc2,0xc3,0x75,0x9f,0xe0,0xa1,0x14,0x08,0x0f,0x60,0x52,0x33,0x59,0xf4,0xf8,0x7e,0x32,0x2d,0x10,0xfc,0x70,0x58,0x89,0x04,0x06,0xd1,0xa0,0x0f,0x8f,0xfa,0x7e,0x3f,0x3e,0xa8,0x7c,0x69,0x1a,0x08,0x04,0xe2,0x80,0x1f,0x19,0xfd,0xf8,0xfe,0x92,0xa0,0x78,0xd0,0x20,0x19,0x8e,0x19,0xa8,0x7a,0xf7,0x51,0xfb,0x03,0xcb,0x11,0xc3,0xaa,0x4d,0x7a,0x76,0x51,0xf8,0x87,0xc8,0x7e,0x34,0x85,0xf0,0xe2,0x24,0x7a,0xe0,0xf9,0xaf,0xd0,0x9e,0x31,0x08,0x04,0x22,0x01,0x57,0x1f,0x9e,0xb8,0x7e,0x90,0x80,0x79,0x61,0x07,0xe2,0x5f,0x2f,0xfd,0xde,0xeb,0xf7,0x4f,0x8c,0x44,0x3a,0x30,0x8f,0xc0,0x7c,0x4f,0xe6,0x1f,0x29,0xda,0xbc,0x41,0xe5,0xc0,0xd7,0xa7,0xcd,0x8a,0x3d,0xdf,0xe8,0x7c,0x60,0x40,0xf2,0x80,0x55,0x97,0xe7,0xee,0x0f,0x0f,0xa9,0xfe,0x30,0x40,0x79,0x7c,0x05,0x43,0xe1,0x6f,0x88,0x7c,0x40,0x02,0x1f,0x18,0x01,0x3c,0x5d,0xe5,0x9f,0x80,0xbf,0xc4,0x1f,0x00,0x05,0x82,0x01,0x50,0x1e,0x28,0xf1,0x00,0x2c,0x90,0x1e,0xca,0xf1,0x00,0x2d,0x52,0x1e,0x0f,0x5c,0x00,0x7d,0xc1,0xed,0x00,0x25,0x08,0xff,0x00,0x46,0x00,0x3f,0xe1,0x7c,0xff,0xf0,0x30,0xc3,0xc0,0x3c,0x02,0x73,0xbc,0x00,0xcb,0xf0,0x18,0x4f,0xf8,0x3e,0x00,0x0c,0x0f,0xf0,}; +const uint8_t* const _I_InfraredSend_128x64[] = {_I_InfraredSend_128x64_0}; const uint8_t _I_Mute_25x27_0[] = {0x01,0x00,0x51,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x31,0x81,0xc0,0x64,0x38,0x08,0xa4,0x06,0x83,0x40,0x86,0x40,0x70,0x32,0x08,0x20,0x3c,0x63,0xf0,0x60,0x38,0xc0,0xa0,0xa0,0x31,0xc2,0x02,0xc7,0x03,0x48,0x01,0x94,0xc0,0x06,0xc0,0xb3,0x09,0x98,0x6c,0x84,0x68,0x2b,0x21,0x99,0x8e,0xcc,0x86,0x64,0xb3,0x81,0x94,0xc6,0x03,0x06,0x80,0x70,0x20,0x1f,0xcf,0xfd,0xfc,0xce,0x80,}; const uint8_t* const _I_Mute_25x27[] = {_I_Mute_25x27_0}; @@ -286,6 +265,27 @@ const uint8_t* const _I_Vol_up_25x27[] = {_I_Vol_up_25x27_0}; const uint8_t _I_Vol_up_hvr_25x27_0[] = {0x01,0x00,0x28,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x38,0xf7,0x80,0xfc,0x06,0xa2,0xd1,0xfc,0x00,0xd0,0x2f,0xe0,0x38,0x21,0xd8,0x0c,0x8a,0xe6,0x5f,0x33,0x39,0x80,}; const uint8_t* const _I_Vol_up_hvr_25x27[] = {_I_Vol_up_hvr_25x27_0}; +const uint8_t _I_Back3_45x8_0[] = {0x00,0x04,0x00,0x10,0x00,0x40,0x00,0x06,0x00,0x18,0x00,0x60,0x00,0x7F,0x00,0xFC,0x01,0xF0,0x07,0x86,0x20,0x18,0x82,0x60,0x08,0x04,0x71,0x10,0xC4,0x41,0x10,0x00,0x21,0x00,0x84,0x00,0x10,0x80,0x00,0x00,0x02,0x00,0x08,0x7E,0x00,0xF8,0x01,0xE0,0x07,}; +const uint8_t* const _I_Back3_45x8[] = {_I_Back3_45x8_0}; + +const uint8_t _I_DoorLeft_70x55_0[] = {0x01,0x00,0x19,0x01,0x00,0x2c,0x32,0x01,0x03,0x04,0x2c,0x18,0x10,0xf0,0x40,0x47,0x82,0x06,0x81,0x03,0xff,0x80,0x08,0x1a,0x20,0x82,0x15,0x28,0x21,0x87,0x82,0x08,0x6f,0xc0,0xb1,0xe6,0x10,0x10,0x8b,0x46,0x20,0x43,0x55,0x8f,0x82,0x10,0x32,0x73,0x0a,0x09,0x89,0x6c,0x1e,0x09,0x00,0x18,0x60,0xf0,0x0c,0x84,0x93,0x82,0x03,0x18,0x0c,0x02,0x1d,0x00,0x90,0x52,0x70,0x50,0x1e,0x00,0x58,0x63,0x90,0x0a,0x06,0x4a,0x09,0x03,0xb0,0x02,0x06,0x70,0x62,0x49,0xf8,0x0c,0x66,0x3f,0xf0,0x41,0x63,0x04,0x43,0x00,0x99,0x60,0x00,0x85,0xc8,0x06,0x14,0xd0,0x80,0x3f,0xc8,0x0d,0xb8,0x10,0x70,0xf8,0x34,0x13,0x03,0x39,0x04,0x1c,0x42,0x19,0xf8,0xa0,0xc2,0x01,0x07,0xef,0x02,0x8c,0x80,0x10,0x9d,0x00,0x43,0xec,0x00,0xa3,0x10,0x04,0x25,0xce,0x19,0xfc,0x88,0x82,0x12,0x0c,0x35,0x10,0x42,0x4c,0xa1,0x90,0x3f,0xc0,0x21,0x22,0x39,0x82,0xc8,0x88,0xd2,0x11,0xf0,0x01,0x88,0xd5,0x18,0xe2,0x08,0x68,0x10,0x0c,0xa8,0x00,0x83,0x81,0xcc,0xd5,0xc3,0x80,0x84,0x82,0x0e,0xcc,0xc0,0x15,0x79,0x02,0x0b,0x98,0xf8,0x11,0x88,0x82,0x0f,0x31,0x19,0x02,0x08,0x2c,0x9f,0x6a,0x1d,0x20,0x41,0x31,0x4c,0x10,0x8d,0x73,0x04,0x23,0xa4,0xc4,0x6c,0xde,0x20,0x42,0xcc,0x01,0x07,0x07,0xff,0x80,0x06,0x3e,0x08,0x38,0x70,0x20,0xa1,0xe0,0x83,0x8e,0x01,0x0c,0xf0,0x73,0x80,0x43,0x70,0x05,0x08,0x00,0x2c,0x04,0xc4,0x46,0x53,0x09,0x98,0x24,0x80,0x65,0x80,0xb0,0xd9,0x84,0x65,0x32,0x06,0x17,0x0f,0x98,0x23,0x63,0xe1,0x88,0xc4,0x08,0x5f,0xc1,0x30,0x9d,0x84,0x4e,0x66,0x94,0x11,0x98,0x75,0x26,0x00,}; +const uint8_t* const _I_DoorLeft_70x55[] = {_I_DoorLeft_70x55_0}; + +const uint8_t _I_DoorLocked_10x56_0[] = {0x01,0x00,0x4e,0x00,0x86,0x40,0x25,0xb0,0x0b,0x6c,0x03,0x9b,0x00,0xc6,0xc0,0x65,0x90,0x10,0x3a,0xc3,0x20,0x31,0xc8,0x04,0xe2,0x01,0x70,0x80,0x78,0x20,0x1c,0x48,0x07,0x22,0x01,0xd0,0x00,0xf0,0x44,0x68,0x90,0x09,0x04,0x02,0x21,0x00,0x84,0x40,0x25,0x80,0x12,0x1e,0x88,0x14,0xc0,0x2e,0x0d,0x11,0xca,0xf8,0x60,0x1c,0x38,0x07,0x1a,0x05,0xcc,0x80,0x72,0x60,0x5c,0x38,0x10,0x1c,0xf9,0x10,0x2e,0x00,0x05,0x60,0x00,0x11,}; +const uint8_t* const _I_DoorLocked_10x56[] = {_I_DoorLocked_10x56_0}; + +const uint8_t _I_DoorRight_70x55_0[] = {0x01,0x00,0x16,0x01,0x81,0xcc,0x01,0x0f,0x60,0x04,0x3f,0x00,0x10,0xf8,0x08,0x0c,0x02,0x05,0x01,0x84,0x02,0x06,0x26,0x0a,0x10,0x8a,0xcc,0xe0,0x1d,0x68,0xe0,0x18,0xab,0xd0,0x0b,0x18,0x10,0x46,0xe6,0x16,0x1e,0x18,0x10,0x46,0xe4,0x28,0x2c,0x98,0x14,0x68,0x00,0x21,0x1d,0x10,0x8c,0x40,0x02,0x0e,0x10,0xa1,0x08,0xc8,0x40,0x42,0x62,0x11,0x94,0x03,0xfd,0xff,0x00,0x0c,0xff,0x0c,0x08,0x28,0x60,0xe4,0xc0,0x85,0x00,0x83,0x00,0x87,0xf1,0x00,0x8c,0x02,0x0b,0x07,0x24,0x84,0xff,0x04,0xc7,0x80,0xa0,0xe4,0xa0,0x81,0x41,0x04,0x17,0x02,0x41,0x49,0x81,0x0e,0x10,0xb2,0xa0,0x82,0x0e,0x9f,0xfc,0x0a,0x62,0xf2,0xc0,0x03,0x92,0xf0,0x08,0x2d,0x78,0x20,0xff,0x02,0x01,0x08,0xae,0x60,0x64,0x38,0x0d,0xb0,0x8d,0x08,0x82,0x11,0x58,0xc4,0x13,0xc0,0x35,0x68,0x62,0x68,0x81,0x09,0x08,0x84,0x40,0x81,0x0d,0x18,0x69,0x10,0x47,0x44,0x66,0x5f,0x21,0xa9,0x29,0x94,0x10,0x2f,0x23,0x53,0x14,0x60,0x42,0x3c,0x08,0xfc,0x02,0x2c,0x62,0x23,0x58,0xd0,0x22,0x00,0x83,0x3e,0x98,0x44,0x43,0x46,0x22,0x30,0x89,0xce,0x01,0x0f,0x70,0x04,0x3f,0x81,0x8a,0x3c,0x21,0xaa,0x70,0x1a,0xe3,0x44,0x1a,0xa6,0x01,0xd2,0x38,0x90,0x8a,0x40,0x20,0xe5,0x96,0x80,0x43,0x81,0x06,0x6b,0x28,0x07,0xf3,0xfe,0x00,0x19,0xf9,0x34,0xc1,0x08,0x8f,0x20,0xf1,0x3e,0x16,0x00,0xa8,0x19,0x00,0x10,0x76,0x03,0xe2,0x3e,0x90,0x45,0x38,0x01,0x42,0x05,0x88,0x44,0x67,0x15,0x70,0x41,0x38,0x04,0x10,0x24,0x03,0x00,0x10,0x20,0x4a,0x46,0xe9,0x46,0xe1,0x04,0x50,0x66,0x40,0x85,0x19,0x98,0x00,0xc0,}; +const uint8_t* const _I_DoorRight_70x55[] = {_I_DoorRight_70x55_0}; + +const uint8_t _I_PassportBottom_128x17_0[] = {0x01,0x00,0x5e,0x00,0x96,0x01,0x97,0xe1,0xff,0x00,0x2e,0x3e,0x68,0x0f,0x5a,0xc5,0x54,0x00,0xb9,0x50,0xfb,0x6a,0x35,0x40,0x05,0xcd,0x4e,0x03,0xfd,0x30,0x0f,0xf8,0x7f,0xa0,0x81,0xfe,0xf9,0x1b,0xfb,0xf3,0x01,0x47,0x66,0x02,0x1b,0x03,0x07,0xe7,0x02,0x0b,0x02,0x07,0xe5,0x82,0x0b,0xf2,0x1c,0xb0,0x01,0x67,0xf0,0x5f,0xd0,0x3f,0x23,0xf0,0x9b,0xc9,0xe5,0x80,0x03,0xd5,0xc0,0x00,0x86,0x01,0xf3,0xe6,0x1e,0x58,0x00,0x36,0xa8,0x06,0xac,0x04,0x30,0x6c,0x30,0xee,0x60,0x1f,0xe0,0x10,0xff,0x0d,0xfb,0x00,}; +const uint8_t* const _I_PassportBottom_128x17[] = {_I_PassportBottom_128x17_0}; + +const uint8_t _I_PassportLeft_6x47_0[] = {0x01,0x00,0x1c,0x00,0x9e,0x40,0xa3,0x32,0x59,0x2c,0x66,0x03,0x01,0x82,0xc2,0x62,0x32,0x50,0x16,0xc8,0x60,0x30,0x28,0x24,0x32,0x39,0x3c,0x9e,0x4d,0x25,0x80,0x1a,}; +const uint8_t* const _I_PassportLeft_6x47[] = {_I_PassportLeft_6x47_0}; + +const uint8_t _I_WarningDolphin_45x42_0[] = {0x01,0x00,0xc6,0x00,0x00,0x1c,0x22,0x04,0x05,0x7f,0xfc,0x1e,0x20,0x05,0x1e,0x04,0x02,0x30,0x05,0x29,0x84,0x02,0xc1,0x20,0x02,0x8c,0x22,0x01,0x80,0x02,0x94,0x10,0x32,0x30,0x10,0x10,0x87,0xca,0x84,0x03,0x10,0x42,0x81,0x48,0x28,0x38,0x08,0x04,0x3e,0x01,0x84,0x83,0xe0,0x30,0x11,0x08,0x05,0xa2,0x11,0x40,0xa0,0x4b,0xc6,0xc5,0x40,0xd0,0x56,0xe0,0x10,0x60,0x29,0x54,0xf0,0x10,0x18,0xf0,0x14,0x6b,0xf6,0x0c,0x04,0x3e,0x40,0x05,0x12,0x80,0xc1,0xe4,0x01,0xd2,0xf8,0x40,0xe4,0x18,0x09,0xf4,0x03,0xf1,0x01,0x90,0x40,0x28,0x30,0x0f,0xe4,0x00,0x16,0x24,0x11,0xbf,0x01,0x44,0xee,0x53,0xf0,0x29,0xf0,0x3e,0x02,0x91,0x3b,0x8c,0xc3,0x81,0x13,0x90,0x48,0x20,0x3f,0xf9,0xfc,0x42,0x60,0x05,0x10,0x98,0x81,0x56,0x11,0x38,0x02,0x9c,0x1a,0x31,0x1e,0x02,0x8f,0x02,0x03,0x1c,0x90,0xc0,0x7c,0x02,0xf1,0xce,0x02,0x07,0x01,0x1f,0x80,0x63,0xa8,0x08,0x71,0x3c,0x8e,0x39,0x24,0x40,0x51,0xc7,0x81,0x53,0x0f,0x3c,0x02,0x9d,0x1e,0x38,0x29,0x10,0x29,0x17,0xc8,0x0a,0x32,0x3a,0x00,0x14,0x4b,0xa2,0x05,0x58,0x98,0x15,0x22,0x20,0x54,0x84,0x81,0x50,}; +const uint8_t* const _I_WarningDolphin_45x42[] = {_I_WarningDolphin_45x42_0}; + const uint8_t _I_KeyBackspaceSelected_16x9_0[] = {0x00,0xFE,0x7F,0xFF,0xFF,0xEF,0xFF,0xE7,0xFF,0x03,0xC0,0xE7,0xFF,0xEF,0xFF,0xFF,0xFF,0xFE,0x7F,}; const uint8_t* const _I_KeyBackspaceSelected_16x9[] = {_I_KeyBackspaceSelected_16x9_0}; @@ -718,24 +718,17 @@ const Icon I_ArrowDownEmpty_14x15 = {.width=14,.height=15,.frame_count=1,.frame_ const Icon I_ArrowDownFilled_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowDownFilled_14x15}; const Icon I_ArrowUpEmpty_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowUpEmpty_14x15}; const Icon I_ArrowUpFilled_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowUpFilled_14x15}; -const Icon I_Back3_45x8 = {.width=45,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Back3_45x8}; -const Icon I_DoorLeft_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorLeft_70x55}; -const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; -const Icon I_DoorRight_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorRight_70x55}; -const Icon I_PassportBottom_128x17 = {.width=128,.height=17,.frame_count=1,.frame_rate=0,.frames=_I_PassportBottom_128x17}; -const Icon I_PassportLeft_6x47 = {.width=6,.height=47,.frame_count=1,.frame_rate=0,.frames=_I_PassportLeft_6x47}; -const Icon I_WarningDolphin_45x42 = {.width=45,.height=42,.frame_count=1,.frame_rate=0,.frames=_I_WarningDolphin_45x42}; const Icon I_Back_15x10 = {.width=15,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Back_15x10}; const Icon I_DolphinReadingSuccess_59x63 = {.width=59,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinReadingSuccess_59x63}; const Icon I_Down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_25x27}; const Icon I_Down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_hvr_25x27}; const Icon I_Fill_marker_7x7 = {.width=7,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_Fill_marker_7x7}; -const Icon I_IrdaArrowDown_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_IrdaArrowDown_4x8}; -const Icon I_IrdaArrowUp_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_IrdaArrowUp_4x8}; -const Icon I_IrdaLearnShort_128x31 = {.width=128,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_IrdaLearnShort_128x31}; -const Icon I_IrdaLearn_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_IrdaLearn_128x64}; -const Icon I_IrdaSendShort_128x34 = {.width=128,.height=34,.frame_count=1,.frame_rate=0,.frames=_I_IrdaSendShort_128x34}; -const Icon I_IrdaSend_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_IrdaSend_128x64}; +const Icon I_InfraredArrowDown_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_InfraredArrowDown_4x8}; +const Icon I_InfraredArrowUp_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_InfraredArrowUp_4x8}; +const Icon I_InfraredLearnShort_128x31 = {.width=128,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_InfraredLearnShort_128x31}; +const Icon I_InfraredLearn_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_InfraredLearn_128x64}; +const Icon I_InfraredSendShort_128x34 = {.width=128,.height=34,.frame_count=1,.frame_rate=0,.frames=_I_InfraredSendShort_128x34}; +const Icon I_InfraredSend_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_InfraredSend_128x64}; const Icon I_Mute_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Mute_25x27}; const Icon I_Mute_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Mute_hvr_25x27}; const Icon I_Power_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Power_25x27}; @@ -746,6 +739,13 @@ const Icon I_Vol_down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0 const Icon I_Vol_down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_down_hvr_25x27}; const Icon I_Vol_up_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_up_25x27}; const Icon I_Vol_up_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_up_hvr_25x27}; +const Icon I_Back3_45x8 = {.width=45,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Back3_45x8}; +const Icon I_DoorLeft_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorLeft_70x55}; +const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; +const Icon I_DoorRight_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorRight_70x55}; +const Icon I_PassportBottom_128x17 = {.width=128,.height=17,.frame_count=1,.frame_rate=0,.frames=_I_PassportBottom_128x17}; +const Icon I_PassportLeft_6x47 = {.width=6,.height=47,.frame_count=1,.frame_rate=0,.frames=_I_PassportLeft_6x47}; +const Icon I_WarningDolphin_45x42 = {.width=45,.height=42,.frame_count=1,.frame_rate=0,.frames=_I_WarningDolphin_45x42}; const Icon I_KeyBackspaceSelected_16x9 = {.width=16,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_KeyBackspaceSelected_16x9}; const Icon I_KeyBackspace_16x9 = {.width=16,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_KeyBackspace_16x9}; const Icon I_KeySaveSelected_24x11 = {.width=24,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_KeySaveSelected_24x11}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index 7c964e1c..e80fdd2f 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -59,24 +59,17 @@ extern const Icon I_ArrowDownEmpty_14x15; extern const Icon I_ArrowDownFilled_14x15; extern const Icon I_ArrowUpEmpty_14x15; extern const Icon I_ArrowUpFilled_14x15; -extern const Icon I_Back3_45x8; -extern const Icon I_DoorLeft_70x55; -extern const Icon I_DoorLocked_10x56; -extern const Icon I_DoorRight_70x55; -extern const Icon I_PassportBottom_128x17; -extern const Icon I_PassportLeft_6x47; -extern const Icon I_WarningDolphin_45x42; extern const Icon I_Back_15x10; extern const Icon I_DolphinReadingSuccess_59x63; extern const Icon I_Down_25x27; extern const Icon I_Down_hvr_25x27; extern const Icon I_Fill_marker_7x7; -extern const Icon I_IrdaArrowDown_4x8; -extern const Icon I_IrdaArrowUp_4x8; -extern const Icon I_IrdaLearnShort_128x31; -extern const Icon I_IrdaLearn_128x64; -extern const Icon I_IrdaSendShort_128x34; -extern const Icon I_IrdaSend_128x64; +extern const Icon I_InfraredArrowDown_4x8; +extern const Icon I_InfraredArrowUp_4x8; +extern const Icon I_InfraredLearnShort_128x31; +extern const Icon I_InfraredLearn_128x64; +extern const Icon I_InfraredSendShort_128x34; +extern const Icon I_InfraredSend_128x64; extern const Icon I_Mute_25x27; extern const Icon I_Mute_hvr_25x27; extern const Icon I_Power_25x27; @@ -87,6 +80,13 @@ extern const Icon I_Vol_down_25x27; extern const Icon I_Vol_down_hvr_25x27; extern const Icon I_Vol_up_25x27; extern const Icon I_Vol_up_hvr_25x27; +extern const Icon I_Back3_45x8; +extern const Icon I_DoorLeft_70x55; +extern const Icon I_DoorLocked_10x56; +extern const Icon I_DoorRight_70x55; +extern const Icon I_PassportBottom_128x17; +extern const Icon I_PassportLeft_6x47; +extern const Icon I_WarningDolphin_45x42; extern const Icon I_KeyBackspaceSelected_16x9; extern const Icon I_KeyBackspace_16x9; extern const Icon I_KeySaveSelected_24x11; diff --git a/assets/icons/Irda/Back_15x10.png b/assets/icons/Infrared/Back_15x10.png similarity index 100% rename from assets/icons/Irda/Back_15x10.png rename to assets/icons/Infrared/Back_15x10.png diff --git a/assets/icons/Irda/DolphinReadingSuccess_59x63.png b/assets/icons/Infrared/DolphinReadingSuccess_59x63.png similarity index 100% rename from assets/icons/Irda/DolphinReadingSuccess_59x63.png rename to assets/icons/Infrared/DolphinReadingSuccess_59x63.png diff --git a/assets/icons/Irda/Down_25x27.png b/assets/icons/Infrared/Down_25x27.png similarity index 100% rename from assets/icons/Irda/Down_25x27.png rename to assets/icons/Infrared/Down_25x27.png diff --git a/assets/icons/Irda/Down_hvr_25x27.png b/assets/icons/Infrared/Down_hvr_25x27.png similarity index 100% rename from assets/icons/Irda/Down_hvr_25x27.png rename to assets/icons/Infrared/Down_hvr_25x27.png diff --git a/assets/icons/Irda/Fill-marker_7x7.png b/assets/icons/Infrared/Fill-marker_7x7.png similarity index 100% rename from assets/icons/Irda/Fill-marker_7x7.png rename to assets/icons/Infrared/Fill-marker_7x7.png diff --git a/assets/icons/Irda/IrdaArrowDown_4x8.png b/assets/icons/Infrared/InfraredArrowDown_4x8.png similarity index 100% rename from assets/icons/Irda/IrdaArrowDown_4x8.png rename to assets/icons/Infrared/InfraredArrowDown_4x8.png diff --git a/assets/icons/Irda/IrdaArrowUp_4x8.png b/assets/icons/Infrared/InfraredArrowUp_4x8.png similarity index 100% rename from assets/icons/Irda/IrdaArrowUp_4x8.png rename to assets/icons/Infrared/InfraredArrowUp_4x8.png diff --git a/assets/icons/Irda/IrdaLearnShort_128x31.png b/assets/icons/Infrared/InfraredLearnShort_128x31.png similarity index 100% rename from assets/icons/Irda/IrdaLearnShort_128x31.png rename to assets/icons/Infrared/InfraredLearnShort_128x31.png diff --git a/assets/icons/Irda/IrdaLearn_128x64.png b/assets/icons/Infrared/InfraredLearn_128x64.png similarity index 100% rename from assets/icons/Irda/IrdaLearn_128x64.png rename to assets/icons/Infrared/InfraredLearn_128x64.png diff --git a/assets/icons/Irda/IrdaSendShort_128x34.png b/assets/icons/Infrared/InfraredSendShort_128x34.png similarity index 100% rename from assets/icons/Irda/IrdaSendShort_128x34.png rename to assets/icons/Infrared/InfraredSendShort_128x34.png diff --git a/assets/icons/Irda/IrdaSend_128x64.png b/assets/icons/Infrared/InfraredSend_128x64.png similarity index 100% rename from assets/icons/Irda/IrdaSend_128x64.png rename to assets/icons/Infrared/InfraredSend_128x64.png diff --git a/assets/icons/Irda/Mute_25x27.png b/assets/icons/Infrared/Mute_25x27.png similarity index 100% rename from assets/icons/Irda/Mute_25x27.png rename to assets/icons/Infrared/Mute_25x27.png diff --git a/assets/icons/Irda/Mute_hvr_25x27.png b/assets/icons/Infrared/Mute_hvr_25x27.png similarity index 100% rename from assets/icons/Irda/Mute_hvr_25x27.png rename to assets/icons/Infrared/Mute_hvr_25x27.png diff --git a/assets/icons/Irda/Power_25x27.png b/assets/icons/Infrared/Power_25x27.png similarity index 100% rename from assets/icons/Irda/Power_25x27.png rename to assets/icons/Infrared/Power_25x27.png diff --git a/assets/icons/Irda/Power_hvr_25x27.png b/assets/icons/Infrared/Power_hvr_25x27.png similarity index 100% rename from assets/icons/Irda/Power_hvr_25x27.png rename to assets/icons/Infrared/Power_hvr_25x27.png diff --git a/assets/icons/Irda/Up_25x27.png b/assets/icons/Infrared/Up_25x27.png similarity index 100% rename from assets/icons/Irda/Up_25x27.png rename to assets/icons/Infrared/Up_25x27.png diff --git a/assets/icons/Irda/Up_hvr_25x27.png b/assets/icons/Infrared/Up_hvr_25x27.png similarity index 100% rename from assets/icons/Irda/Up_hvr_25x27.png rename to assets/icons/Infrared/Up_hvr_25x27.png diff --git a/assets/icons/Irda/Vol_down_25x27.png b/assets/icons/Infrared/Vol_down_25x27.png similarity index 100% rename from assets/icons/Irda/Vol_down_25x27.png rename to assets/icons/Infrared/Vol_down_25x27.png diff --git a/assets/icons/Irda/Vol_down_hvr_25x27.png b/assets/icons/Infrared/Vol_down_hvr_25x27.png similarity index 100% rename from assets/icons/Irda/Vol_down_hvr_25x27.png rename to assets/icons/Infrared/Vol_down_hvr_25x27.png diff --git a/assets/icons/Irda/Vol_up_25x27.png b/assets/icons/Infrared/Vol_up_25x27.png similarity index 100% rename from assets/icons/Irda/Vol_up_25x27.png rename to assets/icons/Infrared/Vol_up_25x27.png diff --git a/assets/icons/Irda/Vol_up_hvr_25x27.png b/assets/icons/Infrared/Vol_up_hvr_25x27.png similarity index 100% rename from assets/icons/Irda/Vol_up_hvr_25x27.png rename to assets/icons/Infrared/Vol_up_hvr_25x27.png diff --git a/assets/resources/irda/assets/tv.ir b/assets/resources/infrared/assets/tv.ir similarity index 100% rename from assets/resources/irda/assets/tv.ir rename to assets/resources/infrared/assets/tv.ir diff --git a/bootloader/targets/f6/furi_hal/furi_hal_resources.c b/bootloader/targets/f6/furi_hal/furi_hal_resources.c index 41e77347..e73b7cbc 100644 --- a/bootloader/targets/f6/furi_hal/furi_hal_resources.c +++ b/bootloader/targets/f6/furi_hal/furi_hal_resources.c @@ -34,8 +34,8 @@ const GpioPin gpio_rfid_pull = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pi const GpioPin gpio_rfid_carrier_out = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin}; const GpioPin gpio_rfid_data_in = {.port = RFID_RF_IN_GPIO_Port, .pin = RFID_RF_IN_Pin}; -const GpioPin gpio_irda_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; -const GpioPin gpio_irda_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; +const GpioPin gpio_infrared_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; +const GpioPin gpio_infrared_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; const GpioPin gpio_usart_tx = {.port = USART1_TX_Port, .pin = USART1_TX_Pin}; const GpioPin gpio_usart_rx = {.port = USART1_RX_Port, .pin = USART1_RX_Pin}; diff --git a/bootloader/targets/f6/furi_hal/furi_hal_resources.h b/bootloader/targets/f6/furi_hal/furi_hal_resources.h index 8f923bd2..925bd5ac 100644 --- a/bootloader/targets/f6/furi_hal/furi_hal_resources.h +++ b/bootloader/targets/f6/furi_hal/furi_hal_resources.h @@ -59,8 +59,8 @@ extern const GpioPin gpio_rfid_pull; extern const GpioPin gpio_rfid_carrier_out; extern const GpioPin gpio_rfid_data_in; -extern const GpioPin gpio_irda_rx; -extern const GpioPin gpio_irda_tx; +extern const GpioPin gpio_infrared_rx; +extern const GpioPin gpio_infrared_tx; extern const GpioPin gpio_usart_tx; extern const GpioPin gpio_usart_rx; diff --git a/bootloader/targets/f7/furi_hal/furi_hal_resources.c b/bootloader/targets/f7/furi_hal/furi_hal_resources.c index 41e77347..e73b7cbc 100644 --- a/bootloader/targets/f7/furi_hal/furi_hal_resources.c +++ b/bootloader/targets/f7/furi_hal/furi_hal_resources.c @@ -34,8 +34,8 @@ const GpioPin gpio_rfid_pull = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pi const GpioPin gpio_rfid_carrier_out = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin}; const GpioPin gpio_rfid_data_in = {.port = RFID_RF_IN_GPIO_Port, .pin = RFID_RF_IN_Pin}; -const GpioPin gpio_irda_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; -const GpioPin gpio_irda_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; +const GpioPin gpio_infrared_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; +const GpioPin gpio_infrared_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; const GpioPin gpio_usart_tx = {.port = USART1_TX_Port, .pin = USART1_TX_Pin}; const GpioPin gpio_usart_rx = {.port = USART1_RX_Port, .pin = USART1_RX_Pin}; diff --git a/bootloader/targets/f7/furi_hal/furi_hal_resources.h b/bootloader/targets/f7/furi_hal/furi_hal_resources.h index 8f923bd2..925bd5ac 100644 --- a/bootloader/targets/f7/furi_hal/furi_hal_resources.h +++ b/bootloader/targets/f7/furi_hal/furi_hal_resources.h @@ -59,8 +59,8 @@ extern const GpioPin gpio_rfid_pull; extern const GpioPin gpio_rfid_carrier_out; extern const GpioPin gpio_rfid_data_in; -extern const GpioPin gpio_irda_rx; -extern const GpioPin gpio_irda_tx; +extern const GpioPin gpio_infrared_rx; +extern const GpioPin gpio_infrared_tx; extern const GpioPin gpio_usart_tx; extern const GpioPin gpio_usart_rx; diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 714f1ae9..6d6bb8aa 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -873,7 +873,7 @@ WARN_LOGFILE = INPUT = applications \ core \ - lib/irda \ + lib/infrared \ lib/subghz \ lib/toolbox \ lib/onewire \ diff --git a/firmware/targets/f6/Inc/main.h b/firmware/targets/f6/Inc/main.h index 3b578816..99b7c0e8 100644 --- a/firmware/targets/f6/Inc/main.h +++ b/firmware/targets/f6/Inc/main.h @@ -131,15 +131,15 @@ extern TIM_HandleTypeDef htim16; #define LFRFID_TIM htim1 #define LFRFID_CH TIM_CHANNEL_1 -#define IRDA_TX_TIM htim1 -#define IRDA_TX_CH TIM_CHANNEL_3 +#define INFRARED_TX_TIM htim1 +#define INFRARED_TX_CH TIM_CHANNEL_3 // only for reference -// IRDA RX timer dont exist in F2 +// INFRARED RX timer dont exist in F2 // and timer need more data to init (NVIC IRQn to set priority) -#define IRDA_RX_TIM htim2 -#define IRDA_RX_FALLING_CH TIM_CHANNEL_1 -#define IRDA_RX_RISING_CH TIM_CHANNEL_2 +#define INFRARED_RX_TIM htim2 +#define INFRARED_RX_FALLING_CH TIM_CHANNEL_1 +#define INFRARED_RX_RISING_CH TIM_CHANNEL_2 #define NFC_IRQ_Pin RFID_PULL_Pin #define NFC_IRQ_GPIO_Port RFID_PULL_GPIO_Port diff --git a/firmware/targets/f6/furi_hal/furi_hal_irda.c b/firmware/targets/f6/furi_hal/furi_hal_infrared.c similarity index 50% rename from firmware/targets/f6/furi_hal/furi_hal_irda.c rename to firmware/targets/f6/furi_hal/furi_hal_infrared.c index 6fe051a9..765885b9 100644 --- a/firmware/targets/f6/furi_hal/furi_hal_irda.c +++ b/firmware/targets/f6/furi_hal/furi_hal_infrared.c @@ -1,4 +1,4 @@ -#include "furi_hal_irda.h" +#include "furi_hal_infrared.h" #include "furi_hal_delay.h" #include "furi/check.h" #include "stm32wbxx_ll_dma.h" @@ -17,26 +17,27 @@ #include #include -#define IRDA_TX_DEBUG 0 +#define INFRARED_TX_DEBUG 0 -#if IRDA_TX_DEBUG == 1 -#define gpio_irda_tx gpio_irda_tx_debug -const GpioPin gpio_irda_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; +#if INFRARED_TX_DEBUG == 1 +#define gpio_infrared_tx gpio_infrared_tx_debug +const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; #endif -#define IRDA_TIM_TX_DMA_BUFFER_SIZE 200 -#define IRDA_POLARITY_SHIFT 1 +#define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200 +#define INFRARED_POLARITY_SHIFT 1 -#define IRDA_TX_CCMR_HIGH (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_PWM2) /* Mark time - enable PWM2 mode */ -#define IRDA_TX_CCMR_LOW \ +#define INFRARED_TX_CCMR_HIGH \ + (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_PWM2) /* Mark time - enable PWM2 mode */ +#define INFRARED_TX_CCMR_LOW \ (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_FORCED_INACTIVE) /* Space time - force low */ typedef struct { - FuriHalIrdaRxCaptureCallback capture_callback; + FuriHalInfraredRxCaptureCallback capture_callback; void* capture_context; - FuriHalIrdaRxTimeoutCallback timeout_callback; + FuriHalInfraredRxTimeoutCallback timeout_callback; void* timeout_context; -} IrdaTimRx; +} InfraredTimRx; typedef struct { uint8_t* polarity; @@ -44,52 +45,52 @@ typedef struct { size_t size; bool packet_end; bool last_packet_end; -} IrdaTxBuf; +} InfraredTxBuf; typedef struct { float cycle_duration; - FuriHalIrdaTxGetDataISRCallback data_callback; - FuriHalIrdaTxSignalSentISRCallback signal_sent_callback; + FuriHalInfraredTxGetDataISRCallback data_callback; + FuriHalInfraredTxSignalSentISRCallback signal_sent_callback; void* data_context; void* signal_sent_context; - IrdaTxBuf buffer[2]; + InfraredTxBuf buffer[2]; osSemaphoreId_t stop_semaphore; uint32_t tx_timing_rest_duration; /** if timing is too long (> 0xFFFF), send it in few iterations */ bool tx_timing_rest_level; - FuriHalIrdaTxGetDataState tx_timing_rest_status; -} IrdaTimTx; + FuriHalInfraredTxGetDataState tx_timing_rest_status; +} InfraredTimTx; typedef enum { - IrdaStateIdle, /** Furi Hal Irda is ready to start RX or TX */ - IrdaStateAsyncRx, /** Async RX started */ - IrdaStateAsyncTx, /** Async TX started, DMA and timer is on */ - IrdaStateAsyncTxStopReq, /** Async TX started, async stop request received */ - IrdaStateAsyncTxStopInProgress, /** Async TX started, stop request is processed and we wait for last data to be sent */ - IrdaStateAsyncTxStopped, /** Async TX complete, cleanup needed */ - IrdaStateMAX, -} IrdaState; + InfraredStateIdle, /** Furi Hal Infrared is ready to start RX or TX */ + InfraredStateAsyncRx, /** Async RX started */ + InfraredStateAsyncTx, /** Async TX started, DMA and timer is on */ + InfraredStateAsyncTxStopReq, /** Async TX started, async stop request received */ + InfraredStateAsyncTxStopInProgress, /** Async TX started, stop request is processed and we wait for last data to be sent */ + InfraredStateAsyncTxStopped, /** Async TX complete, cleanup needed */ + InfraredStateMAX, +} InfraredState; -static volatile IrdaState furi_hal_irda_state = IrdaStateIdle; -static IrdaTimTx irda_tim_tx; -static IrdaTimRx irda_tim_rx; +static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle; +static InfraredTimTx infrared_tim_tx; +static InfraredTimRx infrared_tim_rx; -static void furi_hal_irda_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift); -static void furi_hal_irda_async_tx_free_resources(void); -static void furi_hal_irda_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift); -static void furi_hal_irda_tx_dma_set_buffer(uint8_t buf_num); -static void furi_hal_irda_tx_fill_buffer_last(uint8_t buf_num); -static uint8_t furi_hal_irda_get_current_dma_tx_buffer(void); -static void furi_hal_irda_tx_dma_polarity_isr(); -static void furi_hal_irda_tx_dma_isr(); +static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift); +static void furi_hal_infrared_async_tx_free_resources(void); +static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift); +static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num); +static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num); +static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void); +static void furi_hal_infrared_tx_dma_polarity_isr(); +static void furi_hal_infrared_tx_dma_isr(); -static void furi_hal_irda_tim_rx_isr() { +static void furi_hal_infrared_tim_rx_isr() { static uint32_t previous_captured_ch2 = 0; /* Timeout */ if(LL_TIM_IsActiveFlag_CC3(TIM2)) { LL_TIM_ClearFlag_CC3(TIM2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); /* Timers CNT register starts to counting from 0 to ARR, but it is * reseted when Channel 1 catches interrupt. It is not reseted by @@ -97,22 +98,22 @@ static void furi_hal_irda_tim_rx_isr() { * This can cause false timeout: when time is over, but we started * receiving new signal few microseconds ago, because CNT register * is reseted once per period, not per sample. */ - if(LL_GPIO_IsInputPinSet(gpio_irda_rx.port, gpio_irda_rx.pin) != 0) { - if(irda_tim_rx.timeout_callback) - irda_tim_rx.timeout_callback(irda_tim_rx.timeout_context); + if(LL_GPIO_IsInputPinSet(gpio_infrared_rx.port, gpio_infrared_rx.pin) != 0) { + if(infrared_tim_rx.timeout_callback) + infrared_tim_rx.timeout_callback(infrared_tim_rx.timeout_context); } } /* Rising Edge */ if(LL_TIM_IsActiveFlag_CC1(TIM2)) { LL_TIM_ClearFlag_CC1(TIM2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); if(READ_BIT(TIM2->CCMR1, TIM_CCMR1_CC1S)) { - /* Low pin level is a Mark state of IRDA signal. Invert level for further processing. */ + /* Low pin level is a Mark state of INFRARED signal. Invert level for further processing. */ uint32_t duration = LL_TIM_IC_GetCaptureCH1(TIM2) - previous_captured_ch2; - if(irda_tim_rx.capture_callback) - irda_tim_rx.capture_callback(irda_tim_rx.capture_context, 1, duration); + if(infrared_tim_rx.capture_callback) + infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 1, duration); } else { furi_assert(0); } @@ -121,22 +122,22 @@ static void furi_hal_irda_tim_rx_isr() { /* Falling Edge */ if(LL_TIM_IsActiveFlag_CC2(TIM2)) { LL_TIM_ClearFlag_CC2(TIM2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); if(READ_BIT(TIM2->CCMR1, TIM_CCMR1_CC2S)) { - /* High pin level is a Space state of IRDA signal. Invert level for further processing. */ + /* High pin level is a Space state of INFRARED signal. Invert level for further processing. */ uint32_t duration = LL_TIM_IC_GetCaptureCH2(TIM2); previous_captured_ch2 = duration; - if(irda_tim_rx.capture_callback) - irda_tim_rx.capture_callback(irda_tim_rx.capture_context, 0, duration); + if(infrared_tim_rx.capture_callback) + infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 0, duration); } else { furi_assert(0); } } } -void furi_hal_irda_async_rx_start(void) { - furi_assert(furi_hal_irda_state == IrdaStateIdle); +void furi_hal_infrared_async_rx_start(void) { + furi_assert(furi_hal_infrared_state == InfraredStateIdle); FURI_CRITICAL_ENTER(); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); @@ -144,7 +145,7 @@ void furi_hal_irda_async_rx_start(void) { FURI_CRITICAL_EXIT(); hal_gpio_init_ex( - &gpio_irda_rx, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + &gpio_infrared_rx, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; @@ -171,8 +172,8 @@ void furi_hal_irda_async_rx_start(void) { LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_INDIRECTTI); LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); - furi_hal_interrupt_set_timer_isr(TIM2, furi_hal_irda_tim_rx_isr); - furi_hal_irda_state = IrdaStateAsyncRx; + furi_hal_interrupt_set_timer_isr(TIM2, furi_hal_infrared_tim_rx_isr); + furi_hal_infrared_state = InfraredStateAsyncRx; LL_TIM_EnableIT_CC1(TIM2); LL_TIM_EnableIT_CC2(TIM2); @@ -186,15 +187,15 @@ void furi_hal_irda_async_rx_start(void) { NVIC_EnableIRQ(TIM2_IRQn); } -void furi_hal_irda_async_rx_stop(void) { - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); +void furi_hal_infrared_async_rx_stop(void) { + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); LL_TIM_DeInit(TIM2); furi_hal_interrupt_set_timer_isr(TIM2, NULL); LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_TIM2); - furi_hal_irda_state = IrdaStateIdle; + furi_hal_infrared_state = InfraredStateIdle; } -void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us) { +void furi_hal_infrared_async_rx_set_timeout(uint32_t timeout_us) { furi_assert(LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM2)); LL_TIM_OC_SetCompareCH3(TIM2, timeout_us); @@ -203,46 +204,46 @@ void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us) { LL_TIM_EnableIT_CC3(TIM2); } -bool furi_hal_irda_is_busy(void) { - return furi_hal_irda_state != IrdaStateIdle; +bool furi_hal_infrared_is_busy(void) { + return furi_hal_infrared_state != InfraredStateIdle; } -void furi_hal_irda_async_rx_set_capture_isr_callback( - FuriHalIrdaRxCaptureCallback callback, +void furi_hal_infrared_async_rx_set_capture_isr_callback( + FuriHalInfraredRxCaptureCallback callback, void* ctx) { - irda_tim_rx.capture_callback = callback; - irda_tim_rx.capture_context = ctx; + infrared_tim_rx.capture_callback = callback; + infrared_tim_rx.capture_context = ctx; } -void furi_hal_irda_async_rx_set_timeout_isr_callback( - FuriHalIrdaRxTimeoutCallback callback, +void furi_hal_infrared_async_rx_set_timeout_isr_callback( + FuriHalInfraredRxTimeoutCallback callback, void* ctx) { - irda_tim_rx.timeout_callback = callback; - irda_tim_rx.timeout_context = ctx; + infrared_tim_rx.timeout_callback = callback; + infrared_tim_rx.timeout_context = ctx; } -static void furi_hal_irda_tx_dma_terminate(void) { +static void furi_hal_infrared_tx_dma_terminate(void) { LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncTxStopInProgress); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress); LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); LL_TIM_DisableCounter(TIM1); - osStatus_t status = osSemaphoreRelease(irda_tim_tx.stop_semaphore); + osStatus_t status = osSemaphoreRelease(infrared_tim_tx.stop_semaphore); furi_check(status == osOK); - furi_hal_irda_state = IrdaStateAsyncTxStopped; + furi_hal_infrared_state = InfraredStateAsyncTxStopped; } -static uint8_t furi_hal_irda_get_current_dma_tx_buffer(void) { +static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { uint8_t buf_num = 0; uint32_t buffer_adr = LL_DMA_GetMemoryAddress(DMA1, LL_DMA_CHANNEL_2); - if(buffer_adr == (uint32_t)irda_tim_tx.buffer[0].data) { + if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[0].data) { buf_num = 0; - } else if(buffer_adr == (uint32_t)irda_tim_tx.buffer[1].data) { + } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { buf_num = 1; } else { furi_assert(0); @@ -250,7 +251,7 @@ static uint8_t furi_hal_irda_get_current_dma_tx_buffer(void) { return buf_num; } -static void furi_hal_irda_tx_dma_polarity_isr() { +static void furi_hal_infrared_tx_dma_polarity_isr() { if(LL_DMA_IsActiveFlag_TE1(DMA1)) { LL_DMA_ClearFlag_TE1(DMA1); furi_crash(NULL); @@ -259,33 +260,34 @@ static void furi_hal_irda_tx_dma_polarity_isr() { LL_DMA_ClearFlag_TC1(DMA1); furi_check( - (furi_hal_irda_state == IrdaStateAsyncTx) || - (furi_hal_irda_state == IrdaStateAsyncTxStopReq) || - (furi_hal_irda_state == IrdaStateAsyncTxStopInProgress)); + (furi_hal_infrared_state == InfraredStateAsyncTx) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress)); /* actually TC2 is processed and buffer is next buffer */ - uint8_t next_buf_num = furi_hal_irda_get_current_dma_tx_buffer(); - furi_hal_irda_tx_dma_set_polarity(next_buf_num, 0); + uint8_t next_buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); + furi_hal_infrared_tx_dma_set_polarity(next_buf_num, 0); } } -static void furi_hal_irda_tx_dma_isr() { +static void furi_hal_infrared_tx_dma_isr() { if(LL_DMA_IsActiveFlag_TE2(DMA1)) { LL_DMA_ClearFlag_TE2(DMA1); furi_crash(NULL); } if(LL_DMA_IsActiveFlag_HT2(DMA1) && LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_2)) { LL_DMA_ClearFlag_HT2(DMA1); - uint8_t buf_num = furi_hal_irda_get_current_dma_tx_buffer(); + uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); uint8_t next_buf_num = !buf_num; - if(irda_tim_tx.buffer[buf_num].last_packet_end) { + if(infrared_tim_tx.buffer[buf_num].last_packet_end) { LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); } else if( - !irda_tim_tx.buffer[buf_num].packet_end || (furi_hal_irda_state == IrdaStateAsyncTx)) { - furi_hal_irda_tx_fill_buffer(next_buf_num, 0); - if(irda_tim_tx.buffer[next_buf_num].last_packet_end) { + !infrared_tim_tx.buffer[buf_num].packet_end || + (furi_hal_infrared_state == InfraredStateAsyncTx)) { + furi_hal_infrared_tx_fill_buffer(next_buf_num, 0); + if(infrared_tim_tx.buffer[next_buf_num].last_packet_end) { LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); } - } else if(furi_hal_irda_state == IrdaStateAsyncTxStopReq) { + } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { /* fallthrough */ } else { furi_crash(NULL); @@ -294,33 +296,33 @@ static void furi_hal_irda_tx_dma_isr() { if(LL_DMA_IsActiveFlag_TC2(DMA1) && LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_2)) { LL_DMA_ClearFlag_TC2(DMA1); furi_check( - (furi_hal_irda_state == IrdaStateAsyncTxStopInProgress) || - (furi_hal_irda_state == IrdaStateAsyncTxStopReq) || - (furi_hal_irda_state == IrdaStateAsyncTx)); + (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || + (furi_hal_infrared_state == InfraredStateAsyncTx)); - uint8_t buf_num = furi_hal_irda_get_current_dma_tx_buffer(); + uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); uint8_t next_buf_num = !buf_num; - if(furi_hal_irda_state == IrdaStateAsyncTxStopInProgress) { - furi_hal_irda_tx_dma_terminate(); + if(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) { + furi_hal_infrared_tx_dma_terminate(); } else if( - irda_tim_tx.buffer[buf_num].last_packet_end || - (irda_tim_tx.buffer[buf_num].packet_end && - (furi_hal_irda_state == IrdaStateAsyncTxStopReq))) { - furi_hal_irda_state = IrdaStateAsyncTxStopInProgress; - furi_hal_irda_tx_fill_buffer_last(next_buf_num); - furi_hal_irda_tx_dma_set_buffer(next_buf_num); + infrared_tim_tx.buffer[buf_num].last_packet_end || + (infrared_tim_tx.buffer[buf_num].packet_end && + (furi_hal_infrared_state == InfraredStateAsyncTxStopReq))) { + furi_hal_infrared_state = InfraredStateAsyncTxStopInProgress; + furi_hal_infrared_tx_fill_buffer_last(next_buf_num); + furi_hal_infrared_tx_dma_set_buffer(next_buf_num); } else { /* if it's not end of the packet - continue receiving */ - furi_hal_irda_tx_dma_set_buffer(next_buf_num); + furi_hal_infrared_tx_dma_set_buffer(next_buf_num); } - if(irda_tim_tx.signal_sent_callback && irda_tim_tx.buffer[buf_num].packet_end && - (furi_hal_irda_state != IrdaStateAsyncTxStopped)) { - irda_tim_tx.signal_sent_callback(irda_tim_tx.signal_sent_context); + if(infrared_tim_tx.signal_sent_callback && infrared_tim_tx.buffer[buf_num].packet_end && + (furi_hal_infrared_state != InfraredStateAsyncTxStopped)) { + infrared_tim_tx.signal_sent_callback(infrared_tim_tx.signal_sent_context); } } } -static void furi_hal_irda_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { +static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1); /* LL_DBGMCU_APB2_GRP1_FreezePeriph(LL_DBGMCU_APB2_GRP1_TIM1_STOP); */ @@ -332,7 +334,7 @@ static void furi_hal_irda_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) LL_TIM_EnableARRPreload(TIM1); LL_TIM_SetAutoReload( TIM1, __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(TIM1), freq)); -#if IRDA_TX_DEBUG == 1 +#if INFRARED_TX_DEBUG == 1 LL_TIM_OC_SetCompareCH1(TIM1, ((LL_TIM_GetAutoReload(TIM1) + 1) * (1 - duty_cycle))); LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ @@ -360,11 +362,11 @@ static void furi_hal_irda_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); } -static void furi_hal_irda_configure_tim_cmgr2_dma_tx(void) { +static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_C2_AHB1_GRP1_EnableClock(LL_C2_AHB1_GRP1_PERIPH_DMA1); LL_DMA_InitTypeDef dma_config = {0}; -#if IRDA_TX_DEBUG == 1 +#if INFRARED_TX_DEBUG == 1 dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR1); #else dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR2); @@ -382,7 +384,7 @@ static void furi_hal_irda_configure_tim_cmgr2_dma_tx(void) { dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); furi_hal_interrupt_set_dma_channel_isr( - DMA1, LL_DMA_CHANNEL_1, furi_hal_irda_tx_dma_polarity_isr); + DMA1, LL_DMA_CHANNEL_1, furi_hal_infrared_tx_dma_polarity_isr); LL_DMA_ClearFlag_TE1(DMA1); LL_DMA_ClearFlag_TC1(DMA1); LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1); @@ -392,7 +394,7 @@ static void furi_hal_irda_configure_tim_cmgr2_dma_tx(void) { NVIC_EnableIRQ(DMA1_Channel1_IRQn); } -static void furi_hal_irda_configure_tim_rcr_dma_tx(void) { +static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { LL_C2_AHB1_GRP1_EnableClock(LL_C2_AHB1_GRP1_PERIPH_DMA1); LL_DMA_InitTypeDef dma_config = {0}; @@ -408,7 +410,7 @@ static void furi_hal_irda_configure_tim_rcr_dma_tx(void) { dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); - furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_2, furi_hal_irda_tx_dma_isr); + furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_2, furi_hal_infrared_tx_dma_isr); LL_DMA_ClearFlag_TC2(DMA1); LL_DMA_ClearFlag_HT2(DMA1); LL_DMA_ClearFlag_TE2(DMA1); @@ -420,100 +422,102 @@ static void furi_hal_irda_configure_tim_rcr_dma_tx(void) { NVIC_EnableIRQ(DMA1_Channel2_IRQn); } -static void furi_hal_irda_tx_fill_buffer_last(uint8_t buf_num) { +static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state != IrdaStateAsyncRx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - furi_assert(irda_tim_tx.data_callback); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state != InfraredStateAsyncRx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + furi_assert(infrared_tim_tx.data_callback); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->data != NULL); (void)buffer->data; furi_assert(buffer->polarity != NULL); (void)buffer->polarity; - irda_tim_tx.buffer[buf_num].data[0] = 0; // 1 pulse - irda_tim_tx.buffer[buf_num].polarity[0] = IRDA_TX_CCMR_LOW; - irda_tim_tx.buffer[buf_num].data[1] = 0; // 1 pulse - irda_tim_tx.buffer[buf_num].polarity[1] = IRDA_TX_CCMR_LOW; - irda_tim_tx.buffer[buf_num].size = 2; - irda_tim_tx.buffer[buf_num].last_packet_end = true; - irda_tim_tx.buffer[buf_num].packet_end = true; + infrared_tim_tx.buffer[buf_num].data[0] = 0; // 1 pulse + infrared_tim_tx.buffer[buf_num].polarity[0] = INFRARED_TX_CCMR_LOW; + infrared_tim_tx.buffer[buf_num].data[1] = 0; // 1 pulse + infrared_tim_tx.buffer[buf_num].polarity[1] = INFRARED_TX_CCMR_LOW; + infrared_tim_tx.buffer[buf_num].size = 2; + infrared_tim_tx.buffer[buf_num].last_packet_end = true; + infrared_tim_tx.buffer[buf_num].packet_end = true; } -static void furi_hal_irda_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift) { +static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state != IrdaStateAsyncRx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - furi_assert(irda_tim_tx.data_callback); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state != InfraredStateAsyncRx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + furi_assert(infrared_tim_tx.data_callback); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->data != NULL); furi_assert(buffer->polarity != NULL); - FuriHalIrdaTxGetDataState status = FuriHalIrdaTxGetDataStateOk; + FuriHalInfraredTxGetDataState status = FuriHalInfraredTxGetDataStateOk; uint32_t duration = 0; bool level = 0; size_t* size = &buffer->size; size_t polarity_counter = 0; while(polarity_shift--) { - buffer->polarity[polarity_counter++] = IRDA_TX_CCMR_LOW; + buffer->polarity[polarity_counter++] = INFRARED_TX_CCMR_LOW; } - for(*size = 0; - (*size < IRDA_TIM_TX_DMA_BUFFER_SIZE) && (status == FuriHalIrdaTxGetDataStateOk);) { - if(irda_tim_tx.tx_timing_rest_duration > 0) { - if(irda_tim_tx.tx_timing_rest_duration > 0xFFFF) { + for(*size = 0; (*size < INFRARED_TIM_TX_DMA_BUFFER_SIZE) && + (status == FuriHalInfraredTxGetDataStateOk);) { + if(infrared_tim_tx.tx_timing_rest_duration > 0) { + if(infrared_tim_tx.tx_timing_rest_duration > 0xFFFF) { buffer->data[*size] = 0xFFFF; - status = FuriHalIrdaTxGetDataStateOk; + status = FuriHalInfraredTxGetDataStateOk; } else { - buffer->data[*size] = irda_tim_tx.tx_timing_rest_duration; - status = irda_tim_tx.tx_timing_rest_status; + buffer->data[*size] = infrared_tim_tx.tx_timing_rest_duration; + status = infrared_tim_tx.tx_timing_rest_status; } - irda_tim_tx.tx_timing_rest_duration -= buffer->data[*size]; - buffer->polarity[polarity_counter] = - irda_tim_tx.tx_timing_rest_level ? IRDA_TX_CCMR_HIGH : IRDA_TX_CCMR_LOW; + infrared_tim_tx.tx_timing_rest_duration -= buffer->data[*size]; + buffer->polarity[polarity_counter] = infrared_tim_tx.tx_timing_rest_level ? + INFRARED_TX_CCMR_HIGH : + INFRARED_TX_CCMR_LOW; ++(*size); ++polarity_counter; continue; } - status = irda_tim_tx.data_callback(irda_tim_tx.data_context, &duration, &level); + status = infrared_tim_tx.data_callback(infrared_tim_tx.data_context, &duration, &level); - uint32_t num_of_impulses = roundf(duration / irda_tim_tx.cycle_duration); + uint32_t num_of_impulses = roundf(duration / infrared_tim_tx.cycle_duration); if(num_of_impulses == 0) { - if((*size == 0) && (status == FuriHalIrdaTxGetDataStateDone)) { + if((*size == 0) && (status == FuriHalInfraredTxGetDataStateDone)) { /* if this is one sample in current buffer, but we * have more to send - continue */ - status = FuriHalIrdaTxGetDataStateOk; + status = FuriHalInfraredTxGetDataStateOk; } } else if((num_of_impulses - 1) > 0xFFFF) { - irda_tim_tx.tx_timing_rest_duration = num_of_impulses - 1; - irda_tim_tx.tx_timing_rest_status = status; - irda_tim_tx.tx_timing_rest_level = level; - status = FuriHalIrdaTxGetDataStateOk; + infrared_tim_tx.tx_timing_rest_duration = num_of_impulses - 1; + infrared_tim_tx.tx_timing_rest_status = status; + infrared_tim_tx.tx_timing_rest_level = level; + status = FuriHalInfraredTxGetDataStateOk; } else { - buffer->polarity[polarity_counter] = level ? IRDA_TX_CCMR_HIGH : IRDA_TX_CCMR_LOW; + buffer->polarity[polarity_counter] = level ? INFRARED_TX_CCMR_HIGH : + INFRARED_TX_CCMR_LOW; buffer->data[*size] = num_of_impulses - 1; ++(*size); ++polarity_counter; } } - buffer->last_packet_end = (status == FuriHalIrdaTxGetDataStateLastDone); - buffer->packet_end = buffer->last_packet_end || (status == FuriHalIrdaTxGetDataStateDone); + buffer->last_packet_end = (status == FuriHalInfraredTxGetDataStateLastDone); + buffer->packet_end = buffer->last_packet_end || (status == FuriHalInfraredTxGetDataStateDone); if(*size == 0) { buffer->data[0] = 0; // 1 pulse - buffer->polarity[0] = IRDA_TX_CCMR_LOW; + buffer->polarity[0] = INFRARED_TX_CCMR_LOW; buffer->size = 1; } } -static void furi_hal_irda_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift) { +static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->polarity != NULL); FURI_CRITICAL_ENTER(); @@ -529,10 +533,10 @@ static void furi_hal_irda_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_ FURI_CRITICAL_EXIT(); } -static void furi_hal_irda_tx_dma_set_buffer(uint8_t buf_num) { +static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->data != NULL); /* non-circular mode requires disabled channel before setup */ @@ -549,66 +553,66 @@ static void furi_hal_irda_tx_dma_set_buffer(uint8_t buf_num) { FURI_CRITICAL_EXIT(); } -static void furi_hal_irda_async_tx_free_resources(void) { +static void furi_hal_infrared_async_tx_free_resources(void) { furi_assert( - (furi_hal_irda_state == IrdaStateIdle) || - (furi_hal_irda_state == IrdaStateAsyncTxStopped)); + (furi_hal_infrared_state == InfraredStateIdle) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); osStatus_t status; - hal_gpio_init(&gpio_irda_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); + hal_gpio_init(&gpio_infrared_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_1, NULL); furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_2, NULL); LL_TIM_DeInit(TIM1); LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_TIM1); LL_C2_AHB1_GRP1_DisableClock(LL_C2_AHB1_GRP1_PERIPH_DMA1); - status = osSemaphoreDelete(irda_tim_tx.stop_semaphore); + status = osSemaphoreDelete(infrared_tim_tx.stop_semaphore); furi_check(status == osOK); - free(irda_tim_tx.buffer[0].data); - free(irda_tim_tx.buffer[1].data); - free(irda_tim_tx.buffer[0].polarity); - free(irda_tim_tx.buffer[1].polarity); + free(infrared_tim_tx.buffer[0].data); + free(infrared_tim_tx.buffer[1].data); + free(infrared_tim_tx.buffer[0].polarity); + free(infrared_tim_tx.buffer[1].polarity); - irda_tim_tx.buffer[0].data = NULL; - irda_tim_tx.buffer[1].data = NULL; - irda_tim_tx.buffer[0].polarity = NULL; - irda_tim_tx.buffer[1].polarity = NULL; + infrared_tim_tx.buffer[0].data = NULL; + infrared_tim_tx.buffer[1].data = NULL; + infrared_tim_tx.buffer[0].polarity = NULL; + infrared_tim_tx.buffer[1].polarity = NULL; } -void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle) { - if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > IRDA_MAX_FREQUENCY) || - (freq < IRDA_MIN_FREQUENCY) || (irda_tim_tx.data_callback == NULL)) { +void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { + if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || + (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { furi_crash(NULL); } - furi_assert(furi_hal_irda_state == IrdaStateIdle); - furi_assert(irda_tim_tx.buffer[0].data == NULL); - furi_assert(irda_tim_tx.buffer[1].data == NULL); - furi_assert(irda_tim_tx.buffer[0].polarity == NULL); - furi_assert(irda_tim_tx.buffer[1].polarity == NULL); + furi_assert(furi_hal_infrared_state == InfraredStateIdle); + furi_assert(infrared_tim_tx.buffer[0].data == NULL); + furi_assert(infrared_tim_tx.buffer[1].data == NULL); + furi_assert(infrared_tim_tx.buffer[0].polarity == NULL); + furi_assert(infrared_tim_tx.buffer[1].polarity == NULL); - size_t alloc_size_data = IRDA_TIM_TX_DMA_BUFFER_SIZE * sizeof(uint16_t); - irda_tim_tx.buffer[0].data = malloc(alloc_size_data); - irda_tim_tx.buffer[1].data = malloc(alloc_size_data); + size_t alloc_size_data = INFRARED_TIM_TX_DMA_BUFFER_SIZE * sizeof(uint16_t); + infrared_tim_tx.buffer[0].data = malloc(alloc_size_data); + infrared_tim_tx.buffer[1].data = malloc(alloc_size_data); size_t alloc_size_polarity = - (IRDA_TIM_TX_DMA_BUFFER_SIZE + IRDA_POLARITY_SHIFT) * sizeof(uint8_t); - irda_tim_tx.buffer[0].polarity = malloc(alloc_size_polarity); - irda_tim_tx.buffer[1].polarity = malloc(alloc_size_polarity); + (INFRARED_TIM_TX_DMA_BUFFER_SIZE + INFRARED_POLARITY_SHIFT) * sizeof(uint8_t); + infrared_tim_tx.buffer[0].polarity = malloc(alloc_size_polarity); + infrared_tim_tx.buffer[1].polarity = malloc(alloc_size_polarity); - irda_tim_tx.stop_semaphore = osSemaphoreNew(1, 0, NULL); - irda_tim_tx.cycle_duration = 1000000.0 / freq; - irda_tim_tx.tx_timing_rest_duration = 0; + infrared_tim_tx.stop_semaphore = osSemaphoreNew(1, 0, NULL); + infrared_tim_tx.cycle_duration = 1000000.0 / freq; + infrared_tim_tx.tx_timing_rest_duration = 0; - furi_hal_irda_tx_fill_buffer(0, IRDA_POLARITY_SHIFT); + furi_hal_infrared_tx_fill_buffer(0, INFRARED_POLARITY_SHIFT); - furi_hal_irda_configure_tim_pwm_tx(freq, duty_cycle); - furi_hal_irda_configure_tim_cmgr2_dma_tx(); - furi_hal_irda_configure_tim_rcr_dma_tx(); - furi_hal_irda_tx_dma_set_polarity(0, IRDA_POLARITY_SHIFT); - furi_hal_irda_tx_dma_set_buffer(0); + furi_hal_infrared_configure_tim_pwm_tx(freq, duty_cycle); + furi_hal_infrared_configure_tim_cmgr2_dma_tx(); + furi_hal_infrared_configure_tim_rcr_dma_tx(); + furi_hal_infrared_tx_dma_set_polarity(0, INFRARED_POLARITY_SHIFT); + furi_hal_infrared_tx_dma_set_buffer(0); - furi_hal_irda_state = IrdaStateAsyncTx; + furi_hal_infrared_state = InfraredStateAsyncTx; LL_TIM_ClearFlag_UPDATE(TIM1); LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); @@ -617,9 +621,9 @@ void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle) { LL_TIM_GenerateEvent_UPDATE(TIM1); /* DMA -> TIMx_RCR */ delay_us(5); LL_GPIO_ResetOutputPin( - gpio_irda_tx.port, gpio_irda_tx.pin); /* when disable it prevents false pulse */ + gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */ hal_gpio_init_ex( - &gpio_irda_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); + &gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); FURI_CRITICAL_ENTER(); LL_TIM_GenerateEvent_UPDATE(TIM1); /* TIMx_RCR -> Repetition counter */ @@ -627,39 +631,40 @@ void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle) { FURI_CRITICAL_EXIT(); } -void furi_hal_irda_async_tx_wait_termination(void) { - furi_assert(furi_hal_irda_state >= IrdaStateAsyncTx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); +void furi_hal_infrared_async_tx_wait_termination(void) { + furi_assert(furi_hal_infrared_state >= InfraredStateAsyncTx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); osStatus_t status; - status = osSemaphoreAcquire(irda_tim_tx.stop_semaphore, osWaitForever); + status = osSemaphoreAcquire(infrared_tim_tx.stop_semaphore, osWaitForever); furi_check(status == osOK); - furi_hal_irda_async_tx_free_resources(); - furi_hal_irda_state = IrdaStateIdle; + furi_hal_infrared_async_tx_free_resources(); + furi_hal_infrared_state = InfraredStateIdle; } -void furi_hal_irda_async_tx_stop(void) { - furi_assert(furi_hal_irda_state >= IrdaStateAsyncTx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); +void furi_hal_infrared_async_tx_stop(void) { + furi_assert(furi_hal_infrared_state >= InfraredStateAsyncTx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); FURI_CRITICAL_ENTER(); - if(furi_hal_irda_state == IrdaStateAsyncTx) furi_hal_irda_state = IrdaStateAsyncTxStopReq; + if(furi_hal_infrared_state == InfraredStateAsyncTx) + furi_hal_infrared_state = InfraredStateAsyncTxStopReq; FURI_CRITICAL_EXIT(); - furi_hal_irda_async_tx_wait_termination(); + furi_hal_infrared_async_tx_wait_termination(); } -void furi_hal_irda_async_tx_set_data_isr_callback( - FuriHalIrdaTxGetDataISRCallback callback, +void furi_hal_infrared_async_tx_set_data_isr_callback( + FuriHalInfraredTxGetDataISRCallback callback, void* context) { - furi_assert(furi_hal_irda_state == IrdaStateIdle); - irda_tim_tx.data_callback = callback; - irda_tim_tx.data_context = context; + furi_assert(furi_hal_infrared_state == InfraredStateIdle); + infrared_tim_tx.data_callback = callback; + infrared_tim_tx.data_context = context; } -void furi_hal_irda_async_tx_set_signal_sent_isr_callback( - FuriHalIrdaTxSignalSentISRCallback callback, +void furi_hal_infrared_async_tx_set_signal_sent_isr_callback( + FuriHalInfraredTxSignalSentISRCallback callback, void* context) { - irda_tim_tx.signal_sent_callback = callback; - irda_tim_tx.signal_sent_context = context; + infrared_tim_tx.signal_sent_callback = callback; + infrared_tim_tx.signal_sent_context = context; } diff --git a/firmware/targets/f6/furi_hal/furi_hal_resources.c b/firmware/targets/f6/furi_hal/furi_hal_resources.c index 670fd555..dbf9fc68 100644 --- a/firmware/targets/f6/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f6/furi_hal/furi_hal_resources.c @@ -70,8 +70,8 @@ const GpioPin gpio_rfid_pull = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pi const GpioPin gpio_rfid_carrier_out = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin}; const GpioPin gpio_rfid_data_in = {.port = RFID_RF_IN_GPIO_Port, .pin = RFID_RF_IN_Pin}; -const GpioPin gpio_irda_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; -const GpioPin gpio_irda_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; +const GpioPin gpio_infrared_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; +const GpioPin gpio_infrared_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; const GpioPin gpio_usart_tx = {.port = USART1_TX_Port, .pin = USART1_TX_Pin}; const GpioPin gpio_usart_rx = {.port = USART1_RX_Port, .pin = USART1_RX_Pin}; diff --git a/firmware/targets/f6/furi_hal/furi_hal_resources.h b/firmware/targets/f6/furi_hal/furi_hal_resources.h index f55cf44e..2a090b45 100644 --- a/firmware/targets/f6/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f6/furi_hal/furi_hal_resources.h @@ -75,8 +75,8 @@ extern const GpioPin gpio_rfid_pull; extern const GpioPin gpio_rfid_carrier_out; extern const GpioPin gpio_rfid_data_in; -extern const GpioPin gpio_irda_rx; -extern const GpioPin gpio_irda_tx; +extern const GpioPin gpio_infrared_rx; +extern const GpioPin gpio_infrared_tx; extern const GpioPin gpio_usart_tx; extern const GpioPin gpio_usart_rx; diff --git a/firmware/targets/f7/Inc/main.h b/firmware/targets/f7/Inc/main.h index 8c53e34c..88325611 100644 --- a/firmware/targets/f7/Inc/main.h +++ b/firmware/targets/f7/Inc/main.h @@ -131,15 +131,15 @@ extern TIM_HandleTypeDef htim16; #define LFRFID_TIM htim1 #define LFRFID_CH TIM_CHANNEL_1 -#define IRDA_TX_TIM htim1 -#define IRDA_TX_CH TIM_CHANNEL_3 +#define INFRARED_TX_TIM htim1 +#define INFRARED_TX_CH TIM_CHANNEL_3 // only for reference -// IRDA RX timer dont exist in F2 +// INFRARED RX timer dont exist in F2 // and timer need more data to init (NVIC IRQn to set priority) -#define IRDA_RX_TIM htim2 -#define IRDA_RX_FALLING_CH TIM_CHANNEL_1 -#define IRDA_RX_RISING_CH TIM_CHANNEL_2 +#define INFRARED_RX_TIM htim2 +#define INFRARED_RX_FALLING_CH TIM_CHANNEL_1 +#define INFRARED_RX_RISING_CH TIM_CHANNEL_2 #define NFC_IRQ_Pin RFID_PULL_Pin #define NFC_IRQ_GPIO_Port RFID_PULL_GPIO_Port diff --git a/firmware/targets/f7/furi_hal/furi_hal_irda.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c similarity index 50% rename from firmware/targets/f7/furi_hal/furi_hal_irda.c rename to firmware/targets/f7/furi_hal/furi_hal_infrared.c index 6fe051a9..765885b9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_irda.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -1,4 +1,4 @@ -#include "furi_hal_irda.h" +#include "furi_hal_infrared.h" #include "furi_hal_delay.h" #include "furi/check.h" #include "stm32wbxx_ll_dma.h" @@ -17,26 +17,27 @@ #include #include -#define IRDA_TX_DEBUG 0 +#define INFRARED_TX_DEBUG 0 -#if IRDA_TX_DEBUG == 1 -#define gpio_irda_tx gpio_irda_tx_debug -const GpioPin gpio_irda_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; +#if INFRARED_TX_DEBUG == 1 +#define gpio_infrared_tx gpio_infrared_tx_debug +const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; #endif -#define IRDA_TIM_TX_DMA_BUFFER_SIZE 200 -#define IRDA_POLARITY_SHIFT 1 +#define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200 +#define INFRARED_POLARITY_SHIFT 1 -#define IRDA_TX_CCMR_HIGH (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_PWM2) /* Mark time - enable PWM2 mode */ -#define IRDA_TX_CCMR_LOW \ +#define INFRARED_TX_CCMR_HIGH \ + (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_PWM2) /* Mark time - enable PWM2 mode */ +#define INFRARED_TX_CCMR_LOW \ (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_FORCED_INACTIVE) /* Space time - force low */ typedef struct { - FuriHalIrdaRxCaptureCallback capture_callback; + FuriHalInfraredRxCaptureCallback capture_callback; void* capture_context; - FuriHalIrdaRxTimeoutCallback timeout_callback; + FuriHalInfraredRxTimeoutCallback timeout_callback; void* timeout_context; -} IrdaTimRx; +} InfraredTimRx; typedef struct { uint8_t* polarity; @@ -44,52 +45,52 @@ typedef struct { size_t size; bool packet_end; bool last_packet_end; -} IrdaTxBuf; +} InfraredTxBuf; typedef struct { float cycle_duration; - FuriHalIrdaTxGetDataISRCallback data_callback; - FuriHalIrdaTxSignalSentISRCallback signal_sent_callback; + FuriHalInfraredTxGetDataISRCallback data_callback; + FuriHalInfraredTxSignalSentISRCallback signal_sent_callback; void* data_context; void* signal_sent_context; - IrdaTxBuf buffer[2]; + InfraredTxBuf buffer[2]; osSemaphoreId_t stop_semaphore; uint32_t tx_timing_rest_duration; /** if timing is too long (> 0xFFFF), send it in few iterations */ bool tx_timing_rest_level; - FuriHalIrdaTxGetDataState tx_timing_rest_status; -} IrdaTimTx; + FuriHalInfraredTxGetDataState tx_timing_rest_status; +} InfraredTimTx; typedef enum { - IrdaStateIdle, /** Furi Hal Irda is ready to start RX or TX */ - IrdaStateAsyncRx, /** Async RX started */ - IrdaStateAsyncTx, /** Async TX started, DMA and timer is on */ - IrdaStateAsyncTxStopReq, /** Async TX started, async stop request received */ - IrdaStateAsyncTxStopInProgress, /** Async TX started, stop request is processed and we wait for last data to be sent */ - IrdaStateAsyncTxStopped, /** Async TX complete, cleanup needed */ - IrdaStateMAX, -} IrdaState; + InfraredStateIdle, /** Furi Hal Infrared is ready to start RX or TX */ + InfraredStateAsyncRx, /** Async RX started */ + InfraredStateAsyncTx, /** Async TX started, DMA and timer is on */ + InfraredStateAsyncTxStopReq, /** Async TX started, async stop request received */ + InfraredStateAsyncTxStopInProgress, /** Async TX started, stop request is processed and we wait for last data to be sent */ + InfraredStateAsyncTxStopped, /** Async TX complete, cleanup needed */ + InfraredStateMAX, +} InfraredState; -static volatile IrdaState furi_hal_irda_state = IrdaStateIdle; -static IrdaTimTx irda_tim_tx; -static IrdaTimRx irda_tim_rx; +static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle; +static InfraredTimTx infrared_tim_tx; +static InfraredTimRx infrared_tim_rx; -static void furi_hal_irda_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift); -static void furi_hal_irda_async_tx_free_resources(void); -static void furi_hal_irda_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift); -static void furi_hal_irda_tx_dma_set_buffer(uint8_t buf_num); -static void furi_hal_irda_tx_fill_buffer_last(uint8_t buf_num); -static uint8_t furi_hal_irda_get_current_dma_tx_buffer(void); -static void furi_hal_irda_tx_dma_polarity_isr(); -static void furi_hal_irda_tx_dma_isr(); +static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift); +static void furi_hal_infrared_async_tx_free_resources(void); +static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift); +static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num); +static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num); +static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void); +static void furi_hal_infrared_tx_dma_polarity_isr(); +static void furi_hal_infrared_tx_dma_isr(); -static void furi_hal_irda_tim_rx_isr() { +static void furi_hal_infrared_tim_rx_isr() { static uint32_t previous_captured_ch2 = 0; /* Timeout */ if(LL_TIM_IsActiveFlag_CC3(TIM2)) { LL_TIM_ClearFlag_CC3(TIM2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); /* Timers CNT register starts to counting from 0 to ARR, but it is * reseted when Channel 1 catches interrupt. It is not reseted by @@ -97,22 +98,22 @@ static void furi_hal_irda_tim_rx_isr() { * This can cause false timeout: when time is over, but we started * receiving new signal few microseconds ago, because CNT register * is reseted once per period, not per sample. */ - if(LL_GPIO_IsInputPinSet(gpio_irda_rx.port, gpio_irda_rx.pin) != 0) { - if(irda_tim_rx.timeout_callback) - irda_tim_rx.timeout_callback(irda_tim_rx.timeout_context); + if(LL_GPIO_IsInputPinSet(gpio_infrared_rx.port, gpio_infrared_rx.pin) != 0) { + if(infrared_tim_rx.timeout_callback) + infrared_tim_rx.timeout_callback(infrared_tim_rx.timeout_context); } } /* Rising Edge */ if(LL_TIM_IsActiveFlag_CC1(TIM2)) { LL_TIM_ClearFlag_CC1(TIM2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); if(READ_BIT(TIM2->CCMR1, TIM_CCMR1_CC1S)) { - /* Low pin level is a Mark state of IRDA signal. Invert level for further processing. */ + /* Low pin level is a Mark state of INFRARED signal. Invert level for further processing. */ uint32_t duration = LL_TIM_IC_GetCaptureCH1(TIM2) - previous_captured_ch2; - if(irda_tim_rx.capture_callback) - irda_tim_rx.capture_callback(irda_tim_rx.capture_context, 1, duration); + if(infrared_tim_rx.capture_callback) + infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 1, duration); } else { furi_assert(0); } @@ -121,22 +122,22 @@ static void furi_hal_irda_tim_rx_isr() { /* Falling Edge */ if(LL_TIM_IsActiveFlag_CC2(TIM2)) { LL_TIM_ClearFlag_CC2(TIM2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); if(READ_BIT(TIM2->CCMR1, TIM_CCMR1_CC2S)) { - /* High pin level is a Space state of IRDA signal. Invert level for further processing. */ + /* High pin level is a Space state of INFRARED signal. Invert level for further processing. */ uint32_t duration = LL_TIM_IC_GetCaptureCH2(TIM2); previous_captured_ch2 = duration; - if(irda_tim_rx.capture_callback) - irda_tim_rx.capture_callback(irda_tim_rx.capture_context, 0, duration); + if(infrared_tim_rx.capture_callback) + infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 0, duration); } else { furi_assert(0); } } } -void furi_hal_irda_async_rx_start(void) { - furi_assert(furi_hal_irda_state == IrdaStateIdle); +void furi_hal_infrared_async_rx_start(void) { + furi_assert(furi_hal_infrared_state == InfraredStateIdle); FURI_CRITICAL_ENTER(); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); @@ -144,7 +145,7 @@ void furi_hal_irda_async_rx_start(void) { FURI_CRITICAL_EXIT(); hal_gpio_init_ex( - &gpio_irda_rx, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + &gpio_infrared_rx, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; @@ -171,8 +172,8 @@ void furi_hal_irda_async_rx_start(void) { LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_INDIRECTTI); LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); - furi_hal_interrupt_set_timer_isr(TIM2, furi_hal_irda_tim_rx_isr); - furi_hal_irda_state = IrdaStateAsyncRx; + furi_hal_interrupt_set_timer_isr(TIM2, furi_hal_infrared_tim_rx_isr); + furi_hal_infrared_state = InfraredStateAsyncRx; LL_TIM_EnableIT_CC1(TIM2); LL_TIM_EnableIT_CC2(TIM2); @@ -186,15 +187,15 @@ void furi_hal_irda_async_rx_start(void) { NVIC_EnableIRQ(TIM2_IRQn); } -void furi_hal_irda_async_rx_stop(void) { - furi_assert(furi_hal_irda_state == IrdaStateAsyncRx); +void furi_hal_infrared_async_rx_stop(void) { + furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); LL_TIM_DeInit(TIM2); furi_hal_interrupt_set_timer_isr(TIM2, NULL); LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_TIM2); - furi_hal_irda_state = IrdaStateIdle; + furi_hal_infrared_state = InfraredStateIdle; } -void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us) { +void furi_hal_infrared_async_rx_set_timeout(uint32_t timeout_us) { furi_assert(LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM2)); LL_TIM_OC_SetCompareCH3(TIM2, timeout_us); @@ -203,46 +204,46 @@ void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us) { LL_TIM_EnableIT_CC3(TIM2); } -bool furi_hal_irda_is_busy(void) { - return furi_hal_irda_state != IrdaStateIdle; +bool furi_hal_infrared_is_busy(void) { + return furi_hal_infrared_state != InfraredStateIdle; } -void furi_hal_irda_async_rx_set_capture_isr_callback( - FuriHalIrdaRxCaptureCallback callback, +void furi_hal_infrared_async_rx_set_capture_isr_callback( + FuriHalInfraredRxCaptureCallback callback, void* ctx) { - irda_tim_rx.capture_callback = callback; - irda_tim_rx.capture_context = ctx; + infrared_tim_rx.capture_callback = callback; + infrared_tim_rx.capture_context = ctx; } -void furi_hal_irda_async_rx_set_timeout_isr_callback( - FuriHalIrdaRxTimeoutCallback callback, +void furi_hal_infrared_async_rx_set_timeout_isr_callback( + FuriHalInfraredRxTimeoutCallback callback, void* ctx) { - irda_tim_rx.timeout_callback = callback; - irda_tim_rx.timeout_context = ctx; + infrared_tim_rx.timeout_callback = callback; + infrared_tim_rx.timeout_context = ctx; } -static void furi_hal_irda_tx_dma_terminate(void) { +static void furi_hal_infrared_tx_dma_terminate(void) { LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_2); - furi_assert(furi_hal_irda_state == IrdaStateAsyncTxStopInProgress); + furi_assert(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress); LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); LL_TIM_DisableCounter(TIM1); - osStatus_t status = osSemaphoreRelease(irda_tim_tx.stop_semaphore); + osStatus_t status = osSemaphoreRelease(infrared_tim_tx.stop_semaphore); furi_check(status == osOK); - furi_hal_irda_state = IrdaStateAsyncTxStopped; + furi_hal_infrared_state = InfraredStateAsyncTxStopped; } -static uint8_t furi_hal_irda_get_current_dma_tx_buffer(void) { +static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { uint8_t buf_num = 0; uint32_t buffer_adr = LL_DMA_GetMemoryAddress(DMA1, LL_DMA_CHANNEL_2); - if(buffer_adr == (uint32_t)irda_tim_tx.buffer[0].data) { + if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[0].data) { buf_num = 0; - } else if(buffer_adr == (uint32_t)irda_tim_tx.buffer[1].data) { + } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { buf_num = 1; } else { furi_assert(0); @@ -250,7 +251,7 @@ static uint8_t furi_hal_irda_get_current_dma_tx_buffer(void) { return buf_num; } -static void furi_hal_irda_tx_dma_polarity_isr() { +static void furi_hal_infrared_tx_dma_polarity_isr() { if(LL_DMA_IsActiveFlag_TE1(DMA1)) { LL_DMA_ClearFlag_TE1(DMA1); furi_crash(NULL); @@ -259,33 +260,34 @@ static void furi_hal_irda_tx_dma_polarity_isr() { LL_DMA_ClearFlag_TC1(DMA1); furi_check( - (furi_hal_irda_state == IrdaStateAsyncTx) || - (furi_hal_irda_state == IrdaStateAsyncTxStopReq) || - (furi_hal_irda_state == IrdaStateAsyncTxStopInProgress)); + (furi_hal_infrared_state == InfraredStateAsyncTx) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress)); /* actually TC2 is processed and buffer is next buffer */ - uint8_t next_buf_num = furi_hal_irda_get_current_dma_tx_buffer(); - furi_hal_irda_tx_dma_set_polarity(next_buf_num, 0); + uint8_t next_buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); + furi_hal_infrared_tx_dma_set_polarity(next_buf_num, 0); } } -static void furi_hal_irda_tx_dma_isr() { +static void furi_hal_infrared_tx_dma_isr() { if(LL_DMA_IsActiveFlag_TE2(DMA1)) { LL_DMA_ClearFlag_TE2(DMA1); furi_crash(NULL); } if(LL_DMA_IsActiveFlag_HT2(DMA1) && LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_2)) { LL_DMA_ClearFlag_HT2(DMA1); - uint8_t buf_num = furi_hal_irda_get_current_dma_tx_buffer(); + uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); uint8_t next_buf_num = !buf_num; - if(irda_tim_tx.buffer[buf_num].last_packet_end) { + if(infrared_tim_tx.buffer[buf_num].last_packet_end) { LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); } else if( - !irda_tim_tx.buffer[buf_num].packet_end || (furi_hal_irda_state == IrdaStateAsyncTx)) { - furi_hal_irda_tx_fill_buffer(next_buf_num, 0); - if(irda_tim_tx.buffer[next_buf_num].last_packet_end) { + !infrared_tim_tx.buffer[buf_num].packet_end || + (furi_hal_infrared_state == InfraredStateAsyncTx)) { + furi_hal_infrared_tx_fill_buffer(next_buf_num, 0); + if(infrared_tim_tx.buffer[next_buf_num].last_packet_end) { LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); } - } else if(furi_hal_irda_state == IrdaStateAsyncTxStopReq) { + } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { /* fallthrough */ } else { furi_crash(NULL); @@ -294,33 +296,33 @@ static void furi_hal_irda_tx_dma_isr() { if(LL_DMA_IsActiveFlag_TC2(DMA1) && LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_2)) { LL_DMA_ClearFlag_TC2(DMA1); furi_check( - (furi_hal_irda_state == IrdaStateAsyncTxStopInProgress) || - (furi_hal_irda_state == IrdaStateAsyncTxStopReq) || - (furi_hal_irda_state == IrdaStateAsyncTx)); + (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || + (furi_hal_infrared_state == InfraredStateAsyncTx)); - uint8_t buf_num = furi_hal_irda_get_current_dma_tx_buffer(); + uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); uint8_t next_buf_num = !buf_num; - if(furi_hal_irda_state == IrdaStateAsyncTxStopInProgress) { - furi_hal_irda_tx_dma_terminate(); + if(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) { + furi_hal_infrared_tx_dma_terminate(); } else if( - irda_tim_tx.buffer[buf_num].last_packet_end || - (irda_tim_tx.buffer[buf_num].packet_end && - (furi_hal_irda_state == IrdaStateAsyncTxStopReq))) { - furi_hal_irda_state = IrdaStateAsyncTxStopInProgress; - furi_hal_irda_tx_fill_buffer_last(next_buf_num); - furi_hal_irda_tx_dma_set_buffer(next_buf_num); + infrared_tim_tx.buffer[buf_num].last_packet_end || + (infrared_tim_tx.buffer[buf_num].packet_end && + (furi_hal_infrared_state == InfraredStateAsyncTxStopReq))) { + furi_hal_infrared_state = InfraredStateAsyncTxStopInProgress; + furi_hal_infrared_tx_fill_buffer_last(next_buf_num); + furi_hal_infrared_tx_dma_set_buffer(next_buf_num); } else { /* if it's not end of the packet - continue receiving */ - furi_hal_irda_tx_dma_set_buffer(next_buf_num); + furi_hal_infrared_tx_dma_set_buffer(next_buf_num); } - if(irda_tim_tx.signal_sent_callback && irda_tim_tx.buffer[buf_num].packet_end && - (furi_hal_irda_state != IrdaStateAsyncTxStopped)) { - irda_tim_tx.signal_sent_callback(irda_tim_tx.signal_sent_context); + if(infrared_tim_tx.signal_sent_callback && infrared_tim_tx.buffer[buf_num].packet_end && + (furi_hal_infrared_state != InfraredStateAsyncTxStopped)) { + infrared_tim_tx.signal_sent_callback(infrared_tim_tx.signal_sent_context); } } } -static void furi_hal_irda_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { +static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1); /* LL_DBGMCU_APB2_GRP1_FreezePeriph(LL_DBGMCU_APB2_GRP1_TIM1_STOP); */ @@ -332,7 +334,7 @@ static void furi_hal_irda_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) LL_TIM_EnableARRPreload(TIM1); LL_TIM_SetAutoReload( TIM1, __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(TIM1), freq)); -#if IRDA_TX_DEBUG == 1 +#if INFRARED_TX_DEBUG == 1 LL_TIM_OC_SetCompareCH1(TIM1, ((LL_TIM_GetAutoReload(TIM1) + 1) * (1 - duty_cycle))); LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ @@ -360,11 +362,11 @@ static void furi_hal_irda_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); } -static void furi_hal_irda_configure_tim_cmgr2_dma_tx(void) { +static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_C2_AHB1_GRP1_EnableClock(LL_C2_AHB1_GRP1_PERIPH_DMA1); LL_DMA_InitTypeDef dma_config = {0}; -#if IRDA_TX_DEBUG == 1 +#if INFRARED_TX_DEBUG == 1 dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR1); #else dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR2); @@ -382,7 +384,7 @@ static void furi_hal_irda_configure_tim_cmgr2_dma_tx(void) { dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); furi_hal_interrupt_set_dma_channel_isr( - DMA1, LL_DMA_CHANNEL_1, furi_hal_irda_tx_dma_polarity_isr); + DMA1, LL_DMA_CHANNEL_1, furi_hal_infrared_tx_dma_polarity_isr); LL_DMA_ClearFlag_TE1(DMA1); LL_DMA_ClearFlag_TC1(DMA1); LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1); @@ -392,7 +394,7 @@ static void furi_hal_irda_configure_tim_cmgr2_dma_tx(void) { NVIC_EnableIRQ(DMA1_Channel1_IRQn); } -static void furi_hal_irda_configure_tim_rcr_dma_tx(void) { +static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { LL_C2_AHB1_GRP1_EnableClock(LL_C2_AHB1_GRP1_PERIPH_DMA1); LL_DMA_InitTypeDef dma_config = {0}; @@ -408,7 +410,7 @@ static void furi_hal_irda_configure_tim_rcr_dma_tx(void) { dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); - furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_2, furi_hal_irda_tx_dma_isr); + furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_2, furi_hal_infrared_tx_dma_isr); LL_DMA_ClearFlag_TC2(DMA1); LL_DMA_ClearFlag_HT2(DMA1); LL_DMA_ClearFlag_TE2(DMA1); @@ -420,100 +422,102 @@ static void furi_hal_irda_configure_tim_rcr_dma_tx(void) { NVIC_EnableIRQ(DMA1_Channel2_IRQn); } -static void furi_hal_irda_tx_fill_buffer_last(uint8_t buf_num) { +static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state != IrdaStateAsyncRx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - furi_assert(irda_tim_tx.data_callback); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state != InfraredStateAsyncRx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + furi_assert(infrared_tim_tx.data_callback); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->data != NULL); (void)buffer->data; furi_assert(buffer->polarity != NULL); (void)buffer->polarity; - irda_tim_tx.buffer[buf_num].data[0] = 0; // 1 pulse - irda_tim_tx.buffer[buf_num].polarity[0] = IRDA_TX_CCMR_LOW; - irda_tim_tx.buffer[buf_num].data[1] = 0; // 1 pulse - irda_tim_tx.buffer[buf_num].polarity[1] = IRDA_TX_CCMR_LOW; - irda_tim_tx.buffer[buf_num].size = 2; - irda_tim_tx.buffer[buf_num].last_packet_end = true; - irda_tim_tx.buffer[buf_num].packet_end = true; + infrared_tim_tx.buffer[buf_num].data[0] = 0; // 1 pulse + infrared_tim_tx.buffer[buf_num].polarity[0] = INFRARED_TX_CCMR_LOW; + infrared_tim_tx.buffer[buf_num].data[1] = 0; // 1 pulse + infrared_tim_tx.buffer[buf_num].polarity[1] = INFRARED_TX_CCMR_LOW; + infrared_tim_tx.buffer[buf_num].size = 2; + infrared_tim_tx.buffer[buf_num].last_packet_end = true; + infrared_tim_tx.buffer[buf_num].packet_end = true; } -static void furi_hal_irda_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift) { +static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state != IrdaStateAsyncRx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - furi_assert(irda_tim_tx.data_callback); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state != InfraredStateAsyncRx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + furi_assert(infrared_tim_tx.data_callback); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->data != NULL); furi_assert(buffer->polarity != NULL); - FuriHalIrdaTxGetDataState status = FuriHalIrdaTxGetDataStateOk; + FuriHalInfraredTxGetDataState status = FuriHalInfraredTxGetDataStateOk; uint32_t duration = 0; bool level = 0; size_t* size = &buffer->size; size_t polarity_counter = 0; while(polarity_shift--) { - buffer->polarity[polarity_counter++] = IRDA_TX_CCMR_LOW; + buffer->polarity[polarity_counter++] = INFRARED_TX_CCMR_LOW; } - for(*size = 0; - (*size < IRDA_TIM_TX_DMA_BUFFER_SIZE) && (status == FuriHalIrdaTxGetDataStateOk);) { - if(irda_tim_tx.tx_timing_rest_duration > 0) { - if(irda_tim_tx.tx_timing_rest_duration > 0xFFFF) { + for(*size = 0; (*size < INFRARED_TIM_TX_DMA_BUFFER_SIZE) && + (status == FuriHalInfraredTxGetDataStateOk);) { + if(infrared_tim_tx.tx_timing_rest_duration > 0) { + if(infrared_tim_tx.tx_timing_rest_duration > 0xFFFF) { buffer->data[*size] = 0xFFFF; - status = FuriHalIrdaTxGetDataStateOk; + status = FuriHalInfraredTxGetDataStateOk; } else { - buffer->data[*size] = irda_tim_tx.tx_timing_rest_duration; - status = irda_tim_tx.tx_timing_rest_status; + buffer->data[*size] = infrared_tim_tx.tx_timing_rest_duration; + status = infrared_tim_tx.tx_timing_rest_status; } - irda_tim_tx.tx_timing_rest_duration -= buffer->data[*size]; - buffer->polarity[polarity_counter] = - irda_tim_tx.tx_timing_rest_level ? IRDA_TX_CCMR_HIGH : IRDA_TX_CCMR_LOW; + infrared_tim_tx.tx_timing_rest_duration -= buffer->data[*size]; + buffer->polarity[polarity_counter] = infrared_tim_tx.tx_timing_rest_level ? + INFRARED_TX_CCMR_HIGH : + INFRARED_TX_CCMR_LOW; ++(*size); ++polarity_counter; continue; } - status = irda_tim_tx.data_callback(irda_tim_tx.data_context, &duration, &level); + status = infrared_tim_tx.data_callback(infrared_tim_tx.data_context, &duration, &level); - uint32_t num_of_impulses = roundf(duration / irda_tim_tx.cycle_duration); + uint32_t num_of_impulses = roundf(duration / infrared_tim_tx.cycle_duration); if(num_of_impulses == 0) { - if((*size == 0) && (status == FuriHalIrdaTxGetDataStateDone)) { + if((*size == 0) && (status == FuriHalInfraredTxGetDataStateDone)) { /* if this is one sample in current buffer, but we * have more to send - continue */ - status = FuriHalIrdaTxGetDataStateOk; + status = FuriHalInfraredTxGetDataStateOk; } } else if((num_of_impulses - 1) > 0xFFFF) { - irda_tim_tx.tx_timing_rest_duration = num_of_impulses - 1; - irda_tim_tx.tx_timing_rest_status = status; - irda_tim_tx.tx_timing_rest_level = level; - status = FuriHalIrdaTxGetDataStateOk; + infrared_tim_tx.tx_timing_rest_duration = num_of_impulses - 1; + infrared_tim_tx.tx_timing_rest_status = status; + infrared_tim_tx.tx_timing_rest_level = level; + status = FuriHalInfraredTxGetDataStateOk; } else { - buffer->polarity[polarity_counter] = level ? IRDA_TX_CCMR_HIGH : IRDA_TX_CCMR_LOW; + buffer->polarity[polarity_counter] = level ? INFRARED_TX_CCMR_HIGH : + INFRARED_TX_CCMR_LOW; buffer->data[*size] = num_of_impulses - 1; ++(*size); ++polarity_counter; } } - buffer->last_packet_end = (status == FuriHalIrdaTxGetDataStateLastDone); - buffer->packet_end = buffer->last_packet_end || (status == FuriHalIrdaTxGetDataStateDone); + buffer->last_packet_end = (status == FuriHalInfraredTxGetDataStateLastDone); + buffer->packet_end = buffer->last_packet_end || (status == FuriHalInfraredTxGetDataStateDone); if(*size == 0) { buffer->data[0] = 0; // 1 pulse - buffer->polarity[0] = IRDA_TX_CCMR_LOW; + buffer->polarity[0] = INFRARED_TX_CCMR_LOW; buffer->size = 1; } } -static void furi_hal_irda_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift) { +static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->polarity != NULL); FURI_CRITICAL_ENTER(); @@ -529,10 +533,10 @@ static void furi_hal_irda_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_ FURI_CRITICAL_EXIT(); } -static void furi_hal_irda_tx_dma_set_buffer(uint8_t buf_num) { +static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num) { furi_assert(buf_num < 2); - furi_assert(furi_hal_irda_state < IrdaStateMAX); - IrdaTxBuf* buffer = &irda_tim_tx.buffer[buf_num]; + furi_assert(furi_hal_infrared_state < InfraredStateMAX); + InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num]; furi_assert(buffer->data != NULL); /* non-circular mode requires disabled channel before setup */ @@ -549,66 +553,66 @@ static void furi_hal_irda_tx_dma_set_buffer(uint8_t buf_num) { FURI_CRITICAL_EXIT(); } -static void furi_hal_irda_async_tx_free_resources(void) { +static void furi_hal_infrared_async_tx_free_resources(void) { furi_assert( - (furi_hal_irda_state == IrdaStateIdle) || - (furi_hal_irda_state == IrdaStateAsyncTxStopped)); + (furi_hal_infrared_state == InfraredStateIdle) || + (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); osStatus_t status; - hal_gpio_init(&gpio_irda_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); + hal_gpio_init(&gpio_infrared_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_1, NULL); furi_hal_interrupt_set_dma_channel_isr(DMA1, LL_DMA_CHANNEL_2, NULL); LL_TIM_DeInit(TIM1); LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_TIM1); LL_C2_AHB1_GRP1_DisableClock(LL_C2_AHB1_GRP1_PERIPH_DMA1); - status = osSemaphoreDelete(irda_tim_tx.stop_semaphore); + status = osSemaphoreDelete(infrared_tim_tx.stop_semaphore); furi_check(status == osOK); - free(irda_tim_tx.buffer[0].data); - free(irda_tim_tx.buffer[1].data); - free(irda_tim_tx.buffer[0].polarity); - free(irda_tim_tx.buffer[1].polarity); + free(infrared_tim_tx.buffer[0].data); + free(infrared_tim_tx.buffer[1].data); + free(infrared_tim_tx.buffer[0].polarity); + free(infrared_tim_tx.buffer[1].polarity); - irda_tim_tx.buffer[0].data = NULL; - irda_tim_tx.buffer[1].data = NULL; - irda_tim_tx.buffer[0].polarity = NULL; - irda_tim_tx.buffer[1].polarity = NULL; + infrared_tim_tx.buffer[0].data = NULL; + infrared_tim_tx.buffer[1].data = NULL; + infrared_tim_tx.buffer[0].polarity = NULL; + infrared_tim_tx.buffer[1].polarity = NULL; } -void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle) { - if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > IRDA_MAX_FREQUENCY) || - (freq < IRDA_MIN_FREQUENCY) || (irda_tim_tx.data_callback == NULL)) { +void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { + if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || + (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { furi_crash(NULL); } - furi_assert(furi_hal_irda_state == IrdaStateIdle); - furi_assert(irda_tim_tx.buffer[0].data == NULL); - furi_assert(irda_tim_tx.buffer[1].data == NULL); - furi_assert(irda_tim_tx.buffer[0].polarity == NULL); - furi_assert(irda_tim_tx.buffer[1].polarity == NULL); + furi_assert(furi_hal_infrared_state == InfraredStateIdle); + furi_assert(infrared_tim_tx.buffer[0].data == NULL); + furi_assert(infrared_tim_tx.buffer[1].data == NULL); + furi_assert(infrared_tim_tx.buffer[0].polarity == NULL); + furi_assert(infrared_tim_tx.buffer[1].polarity == NULL); - size_t alloc_size_data = IRDA_TIM_TX_DMA_BUFFER_SIZE * sizeof(uint16_t); - irda_tim_tx.buffer[0].data = malloc(alloc_size_data); - irda_tim_tx.buffer[1].data = malloc(alloc_size_data); + size_t alloc_size_data = INFRARED_TIM_TX_DMA_BUFFER_SIZE * sizeof(uint16_t); + infrared_tim_tx.buffer[0].data = malloc(alloc_size_data); + infrared_tim_tx.buffer[1].data = malloc(alloc_size_data); size_t alloc_size_polarity = - (IRDA_TIM_TX_DMA_BUFFER_SIZE + IRDA_POLARITY_SHIFT) * sizeof(uint8_t); - irda_tim_tx.buffer[0].polarity = malloc(alloc_size_polarity); - irda_tim_tx.buffer[1].polarity = malloc(alloc_size_polarity); + (INFRARED_TIM_TX_DMA_BUFFER_SIZE + INFRARED_POLARITY_SHIFT) * sizeof(uint8_t); + infrared_tim_tx.buffer[0].polarity = malloc(alloc_size_polarity); + infrared_tim_tx.buffer[1].polarity = malloc(alloc_size_polarity); - irda_tim_tx.stop_semaphore = osSemaphoreNew(1, 0, NULL); - irda_tim_tx.cycle_duration = 1000000.0 / freq; - irda_tim_tx.tx_timing_rest_duration = 0; + infrared_tim_tx.stop_semaphore = osSemaphoreNew(1, 0, NULL); + infrared_tim_tx.cycle_duration = 1000000.0 / freq; + infrared_tim_tx.tx_timing_rest_duration = 0; - furi_hal_irda_tx_fill_buffer(0, IRDA_POLARITY_SHIFT); + furi_hal_infrared_tx_fill_buffer(0, INFRARED_POLARITY_SHIFT); - furi_hal_irda_configure_tim_pwm_tx(freq, duty_cycle); - furi_hal_irda_configure_tim_cmgr2_dma_tx(); - furi_hal_irda_configure_tim_rcr_dma_tx(); - furi_hal_irda_tx_dma_set_polarity(0, IRDA_POLARITY_SHIFT); - furi_hal_irda_tx_dma_set_buffer(0); + furi_hal_infrared_configure_tim_pwm_tx(freq, duty_cycle); + furi_hal_infrared_configure_tim_cmgr2_dma_tx(); + furi_hal_infrared_configure_tim_rcr_dma_tx(); + furi_hal_infrared_tx_dma_set_polarity(0, INFRARED_POLARITY_SHIFT); + furi_hal_infrared_tx_dma_set_buffer(0); - furi_hal_irda_state = IrdaStateAsyncTx; + furi_hal_infrared_state = InfraredStateAsyncTx; LL_TIM_ClearFlag_UPDATE(TIM1); LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); @@ -617,9 +621,9 @@ void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle) { LL_TIM_GenerateEvent_UPDATE(TIM1); /* DMA -> TIMx_RCR */ delay_us(5); LL_GPIO_ResetOutputPin( - gpio_irda_tx.port, gpio_irda_tx.pin); /* when disable it prevents false pulse */ + gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */ hal_gpio_init_ex( - &gpio_irda_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); + &gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); FURI_CRITICAL_ENTER(); LL_TIM_GenerateEvent_UPDATE(TIM1); /* TIMx_RCR -> Repetition counter */ @@ -627,39 +631,40 @@ void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle) { FURI_CRITICAL_EXIT(); } -void furi_hal_irda_async_tx_wait_termination(void) { - furi_assert(furi_hal_irda_state >= IrdaStateAsyncTx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); +void furi_hal_infrared_async_tx_wait_termination(void) { + furi_assert(furi_hal_infrared_state >= InfraredStateAsyncTx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); osStatus_t status; - status = osSemaphoreAcquire(irda_tim_tx.stop_semaphore, osWaitForever); + status = osSemaphoreAcquire(infrared_tim_tx.stop_semaphore, osWaitForever); furi_check(status == osOK); - furi_hal_irda_async_tx_free_resources(); - furi_hal_irda_state = IrdaStateIdle; + furi_hal_infrared_async_tx_free_resources(); + furi_hal_infrared_state = InfraredStateIdle; } -void furi_hal_irda_async_tx_stop(void) { - furi_assert(furi_hal_irda_state >= IrdaStateAsyncTx); - furi_assert(furi_hal_irda_state < IrdaStateMAX); +void furi_hal_infrared_async_tx_stop(void) { + furi_assert(furi_hal_infrared_state >= InfraredStateAsyncTx); + furi_assert(furi_hal_infrared_state < InfraredStateMAX); FURI_CRITICAL_ENTER(); - if(furi_hal_irda_state == IrdaStateAsyncTx) furi_hal_irda_state = IrdaStateAsyncTxStopReq; + if(furi_hal_infrared_state == InfraredStateAsyncTx) + furi_hal_infrared_state = InfraredStateAsyncTxStopReq; FURI_CRITICAL_EXIT(); - furi_hal_irda_async_tx_wait_termination(); + furi_hal_infrared_async_tx_wait_termination(); } -void furi_hal_irda_async_tx_set_data_isr_callback( - FuriHalIrdaTxGetDataISRCallback callback, +void furi_hal_infrared_async_tx_set_data_isr_callback( + FuriHalInfraredTxGetDataISRCallback callback, void* context) { - furi_assert(furi_hal_irda_state == IrdaStateIdle); - irda_tim_tx.data_callback = callback; - irda_tim_tx.data_context = context; + furi_assert(furi_hal_infrared_state == InfraredStateIdle); + infrared_tim_tx.data_callback = callback; + infrared_tim_tx.data_context = context; } -void furi_hal_irda_async_tx_set_signal_sent_isr_callback( - FuriHalIrdaTxSignalSentISRCallback callback, +void furi_hal_infrared_async_tx_set_signal_sent_isr_callback( + FuriHalInfraredTxSignalSentISRCallback callback, void* context) { - irda_tim_tx.signal_sent_callback = callback; - irda_tim_tx.signal_sent_context = context; + infrared_tim_tx.signal_sent_callback = callback; + infrared_tim_tx.signal_sent_context = context; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index d8955096..a5e2907f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -71,8 +71,8 @@ const GpioPin gpio_rfid_carrier_out = {.port = RFID_OUT_GPIO_Port, .pin = RFID_O const GpioPin gpio_rfid_data_in = {.port = RFID_RF_IN_GPIO_Port, .pin = RFID_RF_IN_Pin}; const GpioPin gpio_rfid_carrier = {.port = RFID_CARRIER_GPIO_Port, .pin = RFID_CARRIER_Pin}; -const GpioPin gpio_irda_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; -const GpioPin gpio_irda_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; +const GpioPin gpio_infrared_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; +const GpioPin gpio_infrared_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; const GpioPin gpio_usart_tx = {.port = USART1_TX_Port, .pin = USART1_TX_Pin}; const GpioPin gpio_usart_rx = {.port = USART1_RX_Port, .pin = USART1_RX_Pin}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 0851f1b1..d4bb89e5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -76,8 +76,8 @@ extern const GpioPin gpio_rfid_carrier_out; extern const GpioPin gpio_rfid_data_in; extern const GpioPin gpio_rfid_carrier; -extern const GpioPin gpio_irda_rx; -extern const GpioPin gpio_irda_tx; +extern const GpioPin gpio_infrared_rx; +extern const GpioPin gpio_infrared_tx; extern const GpioPin gpio_usart_tx; extern const GpioPin gpio_usart_rx; diff --git a/firmware/targets/furi_hal_include/furi_hal_infrared.h b/firmware/targets/furi_hal_include/furi_hal_infrared.h new file mode 100644 index 00000000..5fcea066 --- /dev/null +++ b/firmware/targets/furi_hal_include/furi_hal_infrared.h @@ -0,0 +1,148 @@ +/** + * @file furi_hal_infrared.h + * INFRARED HAL API + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define INFRARED_MAX_FREQUENCY 56000 +#define INFRARED_MIN_FREQUENCY 10000 + +typedef enum { + FuriHalInfraredTxGetDataStateOk, /**< New data obtained */ + FuriHalInfraredTxGetDataStateDone, /**< New data obtained, and this is end of package */ + FuriHalInfraredTxGetDataStateLastDone, /**< New data obtained, and this is end of package and no more data available */ +} FuriHalInfraredTxGetDataState; + +/** Callback type for providing data to INFRARED DMA TX system. It is called every tim */ +typedef FuriHalInfraredTxGetDataState ( + *FuriHalInfraredTxGetDataISRCallback)(void* context, uint32_t* duration, bool* level); + +/** Callback type called every time signal is sent by DMA to Timer. + * + * Actually, it means there are 2 timings left to send for this signal, which is + * almost end. Don't use this callback to stop transmission, as far as there are + * next signal is charged for transmission by DMA. + */ +typedef void (*FuriHalInfraredTxSignalSentISRCallback)(void* context); + +/** Signature of callback function for receiving continuous INFRARED rx signal. + * + * @param ctx[in] context to pass to callback + * @param level[in] level of input INFRARED rx signal + * @param duration[in] duration of continuous rx signal level in us + */ +typedef void (*FuriHalInfraredRxCaptureCallback)(void* ctx, bool level, uint32_t duration); + +/** Signature of callback function for reaching silence timeout on INFRARED port. + * + * @param ctx[in] context to pass to callback + */ +typedef void (*FuriHalInfraredRxTimeoutCallback)(void* ctx); + +/** Initialize INFRARED RX timer to receive interrupts. + * + * It provides interrupts for every RX-signal edge changing with its duration. + */ +void furi_hal_infrared_async_rx_start(void); + +/** Deinitialize INFRARED RX interrupt. + */ +void furi_hal_infrared_async_rx_stop(void); + +/** Setup hal for receiving silence timeout. + * + * Should be used with 'furi_hal_infrared_timeout_irq_set_callback()'. + * + * @param[in] timeout_us time to wait for silence on INFRARED port before + * generating IRQ. + */ +void furi_hal_infrared_async_rx_set_timeout(uint32_t timeout_us); + +/** Setup callback for previously initialized INFRARED RX interrupt. + * + * @param[in] callback callback to call when RX signal edge changing occurs + * @param[in] ctx context for callback + */ +void furi_hal_infrared_async_rx_set_capture_isr_callback( + FuriHalInfraredRxCaptureCallback callback, + void* ctx); + +/** Setup callback for reaching silence timeout on INFRARED port. + * + * Should setup hal with 'furi_hal_infrared_setup_rx_timeout_irq()' first. + * + * @param[in] callback callback for silence timeout + * @param[in] ctx context to pass to callback + */ +void furi_hal_infrared_async_rx_set_timeout_isr_callback( + FuriHalInfraredRxTimeoutCallback callback, + void* ctx); + +/** Check if INFRARED is in use now. + * + * @return true if INFRARED is busy, false otherwise. + */ +bool furi_hal_infrared_is_busy(void); + +/** Set callback providing new data. + * + * This function has to be called before furi_hal_infrared_async_tx_start(). + * + * @param[in] callback function to provide new data + * @param[in] context context for callback + */ +void furi_hal_infrared_async_tx_set_data_isr_callback( + FuriHalInfraredTxGetDataISRCallback callback, + void* context); + +/** Start IR asynchronous transmission. + * + * It can be stopped by 2 reasons: + * 1. implicit call for furi_hal_infrared_async_tx_stop() + * 2. callback can provide FuriHalInfraredTxGetDataStateLastDone response which + * means no more data available for transmission. + * + * Any func (furi_hal_infrared_async_tx_stop() or + * furi_hal_infrared_async_tx_wait_termination()) has to be called to wait end of + * transmission and free resources. + * + * @param[in] freq frequency for PWM + * @param[in] duty_cycle duty cycle for PWM + */ +void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle); + +/** Stop IR asynchronous transmission and free resources. + * + * Transmission will stop as soon as transmission reaches end of package + * (FuriHalInfraredTxGetDataStateDone or FuriHalInfraredTxGetDataStateLastDone). + */ +void furi_hal_infrared_async_tx_stop(void); + +/** Wait for end of IR asynchronous transmission and free resources. + * + * Transmission will stop as soon as transmission reaches end of transmission + * (FuriHalInfraredTxGetDataStateLastDone). + */ +void furi_hal_infrared_async_tx_wait_termination(void); + +/** Set callback for end of signal transmission + * + * @param[in] callback function to call when signal is sent + * @param[in] context context for callback + */ +void furi_hal_infrared_async_tx_set_signal_sent_isr_callback( + FuriHalInfraredTxSignalSentISRCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_irda.h b/firmware/targets/furi_hal_include/furi_hal_irda.h deleted file mode 100644 index 551210b2..00000000 --- a/firmware/targets/furi_hal_include/furi_hal_irda.h +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @file furi_hal_irda.h - * IRDA HAL API - */ - -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define IRDA_MAX_FREQUENCY 56000 -#define IRDA_MIN_FREQUENCY 10000 - -typedef enum { - FuriHalIrdaTxGetDataStateOk, /**< New data obtained */ - FuriHalIrdaTxGetDataStateDone, /**< New data obtained, and this is end of package */ - FuriHalIrdaTxGetDataStateLastDone, /**< New data obtained, and this is end of package and no more data available */ -} FuriHalIrdaTxGetDataState; - -/** Callback type for providing data to IRDA DMA TX system. It is called every tim */ -typedef FuriHalIrdaTxGetDataState ( - *FuriHalIrdaTxGetDataISRCallback)(void* context, uint32_t* duration, bool* level); - -/** Callback type called every time signal is sent by DMA to Timer. - * - * Actually, it means there are 2 timings left to send for this signal, which is - * almost end. Don't use this callback to stop transmission, as far as there are - * next signal is charged for transmission by DMA. - */ -typedef void (*FuriHalIrdaTxSignalSentISRCallback)(void* context); - -/** Signature of callback function for receiving continuous IRDA rx signal. - * - * @param ctx[in] context to pass to callback - * @param level[in] level of input IRDA rx signal - * @param duration[in] duration of continuous rx signal level in us - */ -typedef void (*FuriHalIrdaRxCaptureCallback)(void* ctx, bool level, uint32_t duration); - -/** Signature of callback function for reaching silence timeout on IRDA port. - * - * @param ctx[in] context to pass to callback - */ -typedef void (*FuriHalIrdaRxTimeoutCallback)(void* ctx); - -/** Initialize IRDA RX timer to receive interrupts. - * - * It provides interrupts for every RX-signal edge changing with its duration. - */ -void furi_hal_irda_async_rx_start(void); - -/** Deinitialize IRDA RX interrupt. - */ -void furi_hal_irda_async_rx_stop(void); - -/** Setup hal for receiving silence timeout. - * - * Should be used with 'furi_hal_irda_timeout_irq_set_callback()'. - * - * @param[in] timeout_us time to wait for silence on IRDA port before - * generating IRQ. - */ -void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us); - -/** Setup callback for previously initialized IRDA RX interrupt. - * - * @param[in] callback callback to call when RX signal edge changing occurs - * @param[in] ctx context for callback - */ -void furi_hal_irda_async_rx_set_capture_isr_callback( - FuriHalIrdaRxCaptureCallback callback, - void* ctx); - -/** Setup callback for reaching silence timeout on IRDA port. - * - * Should setup hal with 'furi_hal_irda_setup_rx_timeout_irq()' first. - * - * @param[in] callback callback for silence timeout - * @param[in] ctx context to pass to callback - */ -void furi_hal_irda_async_rx_set_timeout_isr_callback( - FuriHalIrdaRxTimeoutCallback callback, - void* ctx); - -/** Check if IRDA is in use now. - * - * @return true if IRDA is busy, false otherwise. - */ -bool furi_hal_irda_is_busy(void); - -/** Set callback providing new data. - * - * This function has to be called before furi_hal_irda_async_tx_start(). - * - * @param[in] callback function to provide new data - * @param[in] context context for callback - */ -void furi_hal_irda_async_tx_set_data_isr_callback( - FuriHalIrdaTxGetDataISRCallback callback, - void* context); - -/** Start IR asynchronous transmission. - * - * It can be stopped by 2 reasons: - * 1. implicit call for furi_hal_irda_async_tx_stop() - * 2. callback can provide FuriHalIrdaTxGetDataStateLastDone response which - * means no more data available for transmission. - * - * Any func (furi_hal_irda_async_tx_stop() or - * furi_hal_irda_async_tx_wait_termination()) has to be called to wait end of - * transmission and free resources. - * - * @param[in] freq frequency for PWM - * @param[in] duty_cycle duty cycle for PWM - */ -void furi_hal_irda_async_tx_start(uint32_t freq, float duty_cycle); - -/** Stop IR asynchronous transmission and free resources. - * - * Transmission will stop as soon as transmission reaches end of package - * (FuriHalIrdaTxGetDataStateDone or FuriHalIrdaTxGetDataStateLastDone). - */ -void furi_hal_irda_async_tx_stop(void); - -/** Wait for end of IR asynchronous transmission and free resources. - * - * Transmission will stop as soon as transmission reaches end of transmission - * (FuriHalIrdaTxGetDataStateLastDone). - */ -void furi_hal_irda_async_tx_wait_termination(void); - -/** Set callback for end of signal transmission - * - * @param[in] callback function to call when signal is sent - * @param[in] context context for callback - */ -void furi_hal_irda_async_tx_set_signal_sent_isr_callback( - FuriHalIrdaTxSignalSentISRCallback callback, - void* context); - -#ifdef __cplusplus -} -#endif diff --git a/lib/ReadMe.md b/lib/ReadMe.md index ec36350a..f29f028b 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -9,7 +9,7 @@ - `fatfs` - External storage file system - `flipper_file` - Flipper File Format library - `fnv1a-hash` - Fnv1a hash library -- `irda` - Irda library +- `infrared` - Infrared library - `libusb_stm32` - STM32 USB library - `littlefs` - Internal storage file system - `micro-ecc` - Elyptic Curve Crpytography library diff --git a/lib/irda/encoder_decoder/common/irda_common_decoder.c b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c similarity index 70% rename from lib/irda/encoder_decoder/common/irda_common_decoder.c rename to lib/infrared/encoder_decoder/common/infrared_common_decoder.c index 9e597925..c39de09e 100644 --- a/lib/irda/encoder_decoder/common/irda_common_decoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c @@ -1,13 +1,13 @@ #include "furi/check.h" #include "furi/common_defines.h" -#include "irda.h" -#include "irda_common_i.h" +#include "infrared.h" +#include "infrared_common_i.h" #include #include -#include "irda_i.h" +#include "infrared_i.h" #include -static void irda_common_decoder_reset_state(IrdaCommonDecoder* decoder); +static void infrared_common_decoder_reset_state(InfraredCommonDecoder* decoder); static inline size_t consume_samples(uint32_t* array, size_t len, size_t shift) { furi_assert(len >= shift); @@ -17,7 +17,7 @@ static inline size_t consume_samples(uint32_t* array, size_t len, size_t shift) return len; } -static inline void accumulate_lsb(IrdaCommonDecoder* decoder, bool bit) { +static inline void accumulate_lsb(InfraredCommonDecoder* decoder, bool bit) { uint16_t index = decoder->databit_cnt / 8; uint8_t shift = decoder->databit_cnt % 8; // LSB first @@ -32,7 +32,7 @@ static inline void accumulate_lsb(IrdaCommonDecoder* decoder, bool bit) { ++decoder->databit_cnt; } -static bool irda_check_preamble(IrdaCommonDecoder* decoder) { +static bool infrared_check_preamble(InfraredCommonDecoder* decoder) { furi_assert(decoder); bool result = false; @@ -70,13 +70,13 @@ static bool irda_check_preamble(IrdaCommonDecoder* decoder) { * decoder->protocol->databit_len[1...] contains lesser values, but which can be decoded * for some protocol modifications. */ -static IrdaStatus irda_common_decode_bits(IrdaCommonDecoder* decoder) { +static InfraredStatus infrared_common_decode_bits(InfraredCommonDecoder* decoder) { furi_assert(decoder); - IrdaStatus status = IrdaStatusOk; - const IrdaTimings* timings = &decoder->protocol->timings; + InfraredStatus status = InfraredStatusOk; + const InfraredTimings* timings = &decoder->protocol->timings; - while(decoder->timings_cnt && (status == IrdaStatusOk)) { + while(decoder->timings_cnt && (status == InfraredStatusOk)) { bool level = (decoder->level + decoder->timings_cnt + 1) % 2; uint32_t timing = decoder->timings[0]; @@ -87,19 +87,19 @@ static IrdaStatus irda_common_decode_bits(IrdaCommonDecoder* decoder) { (i < COUNT_OF(decoder->protocol->databit_len)); ++i) { if(decoder->protocol->databit_len[i] == decoder->databit_cnt) { - return IrdaStatusReady; + return InfraredStatusReady; } } } else if(decoder->protocol->databit_len[0] == decoder->databit_cnt) { /* short low timing for longest protocol - this is signal is longer than we expected */ - return IrdaStatusError; + return InfraredStatusError; } } status = decoder->protocol->decode(decoder, level, timing); furi_check(decoder->databit_cnt <= decoder->protocol->databit_len[0]); - furi_assert(status == IrdaStatusError || status == IrdaStatusOk); - if(status == IrdaStatusError) { + furi_assert(status == InfraredStatusError || status == InfraredStatusOk); + if(status == InfraredStatusError) { break; } decoder->timings_cnt = consume_samples(decoder->timings, decoder->timings_cnt, 1); @@ -107,7 +107,7 @@ static IrdaStatus irda_common_decode_bits(IrdaCommonDecoder* decoder) { /* check if largest protocol version can be decoded */ if(level && (decoder->protocol->databit_len[0] == decoder->databit_cnt) && !timings->min_split_time) { - status = IrdaStatusReady; + status = InfraredStatusReady; break; } } @@ -116,10 +116,11 @@ static IrdaStatus irda_common_decode_bits(IrdaCommonDecoder* decoder) { } /* Pulse Distance-Width Modulation */ -IrdaStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder, bool level, uint32_t timing) { +InfraredStatus + infrared_common_decode_pdwm(InfraredCommonDecoder* decoder, bool level, uint32_t timing) { furi_assert(decoder); - IrdaStatus status = IrdaStatusOk; + InfraredStatus status = InfraredStatusOk; uint32_t bit_tolerance = decoder->protocol->timings.bit_tolerance; uint16_t bit1_mark = decoder->protocol->timings.bit1_mark; uint16_t bit1_space = decoder->protocol->timings.bit1_space; @@ -137,11 +138,11 @@ IrdaStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder, bool level, uint3 } else if(MATCH_TIMING(timing, bit0, bit_tolerance)) { accumulate_lsb(decoder, 0); } else { - status = IrdaStatusError; + status = InfraredStatusError; } } else { if(!MATCH_TIMING(timing, no_info_timing, bit_tolerance)) { - status = IrdaStatusError; + status = InfraredStatusError; } } @@ -149,7 +150,8 @@ IrdaStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder, bool level, uint3 } /* level switch detection goes in middle of time-quant */ -IrdaStatus irda_common_decode_manchester(IrdaCommonDecoder* decoder, bool level, uint32_t timing) { +InfraredStatus + infrared_common_decode_manchester(InfraredCommonDecoder* decoder, bool level, uint32_t timing) { furi_assert(decoder); uint16_t bit = decoder->protocol->timings.bit1_mark; uint16_t tolerance = decoder->protocol->timings.bit_tolerance; @@ -161,7 +163,7 @@ IrdaStatus irda_common_decode_manchester(IrdaCommonDecoder* decoder, bool level, bool double_timing = MATCH_TIMING(timing, 2 * bit, tolerance); if(!single_timing && !double_timing) { - return IrdaStatusError; + return InfraredStatusError; } if(decoder->protocol->manchester_start_from_space && (decoder->databit_cnt == 0)) { @@ -171,7 +173,7 @@ IrdaStatus irda_common_decode_manchester(IrdaCommonDecoder* decoder, bool level, if(*switch_detect == 0) { if(double_timing) { - return IrdaStatusError; + return InfraredStatusError; } /* only single timing - level switch required in the middle of time-quant */ *switch_detect = 1; @@ -182,16 +184,16 @@ IrdaStatus irda_common_decode_manchester(IrdaCommonDecoder* decoder, bool level, if(*switch_detect) { if(decoder->protocol->databit_len[0] == decoder->databit_cnt) { - return IrdaStatusError; + return InfraredStatusError; } accumulate_lsb(decoder, level); } - return IrdaStatusOk; + return InfraredStatusOk; } -IrdaMessage* irda_common_decoder_check_ready(IrdaCommonDecoder* decoder) { - IrdaMessage* message = NULL; +InfraredMessage* infrared_common_decoder_check_ready(InfraredCommonDecoder* decoder) { + InfraredMessage* message = NULL; bool found_length = false; for(int i = 0; @@ -207,23 +209,24 @@ IrdaMessage* irda_common_decoder_check_ready(IrdaCommonDecoder* decoder) { decoder->databit_cnt = 0; message = &decoder->message; if(decoder->protocol->decode_repeat) { - decoder->state = IrdaCommonDecoderStateProcessRepeat; + decoder->state = InfraredCommonDecoderStateProcessRepeat; } else { - decoder->state = IrdaCommonDecoderStateWaitPreamble; + decoder->state = InfraredCommonDecoderStateWaitPreamble; } } return message; } -IrdaMessage* irda_common_decode(IrdaCommonDecoder* decoder, bool level, uint32_t duration) { +InfraredMessage* + infrared_common_decode(InfraredCommonDecoder* decoder, bool level, uint32_t duration) { furi_assert(decoder); - IrdaMessage* message = 0; - IrdaStatus status = IrdaStatusError; + InfraredMessage* message = 0; + InfraredStatus status = InfraredStatusError; if(decoder->level == level) { - irda_common_decoder_reset(decoder); + infrared_common_decoder_reset(decoder); } decoder->level = level; // start with low level (Space timing) @@ -233,35 +236,35 @@ IrdaMessage* irda_common_decode(IrdaCommonDecoder* decoder, bool level, uint32_t while(1) { switch(decoder->state) { - case IrdaCommonDecoderStateWaitPreamble: - if(irda_check_preamble(decoder)) { - decoder->state = IrdaCommonDecoderStateDecode; + case InfraredCommonDecoderStateWaitPreamble: + if(infrared_check_preamble(decoder)) { + decoder->state = InfraredCommonDecoderStateDecode; decoder->databit_cnt = 0; decoder->switch_detect = false; continue; } break; - case IrdaCommonDecoderStateDecode: - status = irda_common_decode_bits(decoder); - if(status == IrdaStatusReady) { - message = irda_common_decoder_check_ready(decoder); + case InfraredCommonDecoderStateDecode: + status = infrared_common_decode_bits(decoder); + if(status == InfraredStatusReady) { + message = infrared_common_decoder_check_ready(decoder); if(message) { continue; } else if(decoder->protocol->databit_len[0] == decoder->databit_cnt) { /* error: can't decode largest protocol - begin decoding from start */ - decoder->state = IrdaCommonDecoderStateWaitPreamble; + decoder->state = InfraredCommonDecoderStateWaitPreamble; } - } else if(status == IrdaStatusError) { - irda_common_decoder_reset_state(decoder); + } else if(status == InfraredStatusError) { + infrared_common_decoder_reset_state(decoder); continue; } break; - case IrdaCommonDecoderStateProcessRepeat: + case InfraredCommonDecoderStateProcessRepeat: status = decoder->protocol->decode_repeat(decoder); - if(status == IrdaStatusError) { - irda_common_decoder_reset_state(decoder); + if(status == InfraredStatusError) { + infrared_common_decoder_reset_state(decoder); continue; - } else if(status == IrdaStatusReady) { + } else if(status == InfraredStatusReady) { decoder->message.repeat = true; message = &decoder->message; } @@ -273,7 +276,7 @@ IrdaMessage* irda_common_decode(IrdaCommonDecoder* decoder, bool level, uint32_t return message; } -void* irda_common_decoder_alloc(const IrdaCommonProtocolSpec* protocol) { +void* infrared_common_decoder_alloc(const InfraredCommonProtocolSpec* protocol) { furi_assert(protocol); /* protocol->databit_len[0] has to contain biggest value of bits that can be decoded */ @@ -281,24 +284,24 @@ void* irda_common_decoder_alloc(const IrdaCommonProtocolSpec* protocol) { furi_assert(protocol->databit_len[i] <= protocol->databit_len[0]); } - uint32_t alloc_size = sizeof(IrdaCommonDecoder) + protocol->databit_len[0] / 8 + + uint32_t alloc_size = sizeof(InfraredCommonDecoder) + protocol->databit_len[0] / 8 + !!(protocol->databit_len[0] % 8); - IrdaCommonDecoder* decoder = malloc(alloc_size); + InfraredCommonDecoder* decoder = malloc(alloc_size); decoder->protocol = protocol; decoder->level = true; return decoder; } -void irda_common_decoder_free(IrdaCommonDecoder* decoder) { +void infrared_common_decoder_free(InfraredCommonDecoder* decoder) { furi_assert(decoder); free(decoder); } -void irda_common_decoder_reset_state(IrdaCommonDecoder* decoder) { - decoder->state = IrdaCommonDecoderStateWaitPreamble; +void infrared_common_decoder_reset_state(InfraredCommonDecoder* decoder) { + decoder->state = InfraredCommonDecoderStateWaitPreamble; decoder->databit_cnt = 0; decoder->switch_detect = false; - decoder->message.protocol = IrdaProtocolUnknown; + decoder->message.protocol = InfraredProtocolUnknown; if(decoder->protocol->timings.preamble_mark == 0) { if(decoder->timings_cnt > 0) { decoder->timings_cnt = consume_samples(decoder->timings, decoder->timings_cnt, 1); @@ -306,9 +309,9 @@ void irda_common_decoder_reset_state(IrdaCommonDecoder* decoder) { } } -void irda_common_decoder_reset(IrdaCommonDecoder* decoder) { +void infrared_common_decoder_reset(InfraredCommonDecoder* decoder) { furi_assert(decoder); - irda_common_decoder_reset_state(decoder); + infrared_common_decoder_reset_state(decoder); decoder->timings_cnt = 0; } diff --git a/lib/irda/encoder_decoder/common/irda_common_encoder.c b/lib/infrared/encoder_decoder/common/infrared_common_encoder.c similarity index 64% rename from lib/irda/encoder_decoder/common/irda_common_encoder.c rename to lib/infrared/encoder_decoder/common/infrared_common_encoder.c index 81067650..563c1e64 100644 --- a/lib/irda/encoder_decoder/common/irda_common_encoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_encoder.c @@ -1,19 +1,19 @@ #include "furi/check.h" -#include "irda.h" -#include "irda_common_i.h" +#include "infrared.h" +#include "infrared_common_i.h" #include #include -#include "irda_i.h" +#include "infrared_i.h" #include -static IrdaStatus - irda_common_encode_bits(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level) { - IrdaStatus status = encoder->protocol->encode(encoder, duration, level); - furi_assert(status == IrdaStatusOk); +static InfraredStatus + infrared_common_encode_bits(InfraredCommonEncoder* encoder, uint32_t* duration, bool* level) { + InfraredStatus status = encoder->protocol->encode(encoder, duration, level); + furi_assert(status == InfraredStatusOk); ++encoder->timings_encoded; encoder->timings_sum += *duration; if((encoder->bits_encoded == encoder->bits_to_encode) && *level) { - status = IrdaStatusDone; + status = InfraredStatusDone; } return status; @@ -33,13 +33,15 @@ static IrdaStatus * 0 1 2 | 3 4 | * _____-------_____---___ */ -IrdaStatus - irda_common_encode_manchester(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level) { +InfraredStatus infrared_common_encode_manchester( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level) { furi_assert(encoder); furi_assert(duration); furi_assert(level); - const IrdaTimings* timings = &encoder->protocol->timings; + const InfraredTimings* timings = &encoder->protocol->timings; uint8_t index = encoder->bits_encoded / 8; uint8_t shift = encoder->bits_encoded % 8; // LSB first bool logic_value = !!(encoder->data[index] & (0x01 << shift)); @@ -52,15 +54,16 @@ IrdaStatus else if(*level && (encoder->bits_encoded + 1 == encoder->bits_to_encode)) ++encoder->bits_encoded; /* don't encode last space */ - return IrdaStatusOk; + return InfraredStatusOk; } -IrdaStatus irda_common_encode_pdwm(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level) { +InfraredStatus + infrared_common_encode_pdwm(InfraredCommonEncoder* encoder, uint32_t* duration, bool* level) { furi_assert(encoder); furi_assert(duration); furi_assert(level); - const IrdaTimings* timings = &encoder->protocol->timings; + const InfraredTimings* timings = &encoder->protocol->timings; uint8_t index = encoder->bits_encoded / 8; uint8_t shift = encoder->bits_encoded % 8; // LSB first bool logic_value = !!(encoder->data[index] & (0x01 << shift)); @@ -76,26 +79,27 @@ IrdaStatus irda_common_encode_pdwm(IrdaCommonEncoder* encoder, uint32_t* duratio if(!pwm) ++encoder->bits_encoded; } - return IrdaStatusOk; + return InfraredStatusOk; } -IrdaStatus irda_common_encode(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level) { +InfraredStatus + infrared_common_encode(InfraredCommonEncoder* encoder, uint32_t* duration, bool* level) { furi_assert(encoder); furi_assert(duration); furi_assert(level); - IrdaStatus status = IrdaStatusOk; - const IrdaTimings* timings = &encoder->protocol->timings; + InfraredStatus status = InfraredStatusOk; + const InfraredTimings* timings = &encoder->protocol->timings; switch(encoder->state) { - case IrdaCommonEncoderStateSilence: + case InfraredCommonEncoderStateSilence: *duration = encoder->protocol->timings.silence_time; *level = false; - status = IrdaStatusOk; - encoder->state = IrdaCommonEncoderStatePreamble; + status = InfraredStatusOk; + encoder->state = InfraredCommonEncoderStatePreamble; ++encoder->timings_encoded; encoder->timings_sum = 0; break; - case IrdaCommonEncoderStatePreamble: + case InfraredCommonEncoderStatePreamble: if(timings->preamble_mark) { if(encoder->timings_encoded == 1) { *duration = timings->preamble_mark; @@ -103,39 +107,39 @@ IrdaStatus irda_common_encode(IrdaCommonEncoder* encoder, uint32_t* duration, bo } else { *duration = timings->preamble_space; *level = false; - encoder->state = IrdaCommonEncoderStateEncode; + encoder->state = InfraredCommonEncoderStateEncode; } ++encoder->timings_encoded; encoder->timings_sum += *duration; break; } else { - encoder->state = IrdaCommonEncoderStateEncode; + encoder->state = InfraredCommonEncoderStateEncode; } /* FALLTHROUGH */ - case IrdaCommonEncoderStateEncode: - status = irda_common_encode_bits(encoder, duration, level); - if(status == IrdaStatusDone) { + case InfraredCommonEncoderStateEncode: + status = infrared_common_encode_bits(encoder, duration, level); + if(status == InfraredStatusDone) { if(encoder->protocol->encode_repeat) { - encoder->state = IrdaCommonEncoderStateEncodeRepeat; + encoder->state = InfraredCommonEncoderStateEncodeRepeat; } else { encoder->timings_encoded = 0; encoder->timings_sum = 0; encoder->bits_encoded = 0; encoder->switch_detect = 0; - encoder->state = IrdaCommonEncoderStateSilence; + encoder->state = InfraredCommonEncoderStateSilence; } } break; - case IrdaCommonEncoderStateEncodeRepeat: + case InfraredCommonEncoderStateEncodeRepeat: status = encoder->protocol->encode_repeat(encoder, duration, level); break; } return status; } -void* irda_common_encoder_alloc(const IrdaCommonProtocolSpec* protocol) { +void* infrared_common_encoder_alloc(const InfraredCommonProtocolSpec* protocol) { furi_assert(protocol); - if(protocol->decode == irda_common_decode_pdwm) { + if(protocol->decode == infrared_common_decode_pdwm) { furi_assert( (protocol->timings.bit1_mark == protocol->timings.bit0_mark) ^ (protocol->timings.bit1_space == protocol->timings.bit0_space)); @@ -146,26 +150,26 @@ void* irda_common_encoder_alloc(const IrdaCommonProtocolSpec* protocol) { furi_assert(protocol->databit_len[i] <= protocol->databit_len[0]); } - uint32_t alloc_size = sizeof(IrdaCommonDecoder) + protocol->databit_len[0] / 8 + + uint32_t alloc_size = sizeof(InfraredCommonDecoder) + protocol->databit_len[0] / 8 + !!(protocol->databit_len[0] % 8); - IrdaCommonEncoder* encoder = malloc(alloc_size); + InfraredCommonEncoder* encoder = malloc(alloc_size); memset(encoder, 0, alloc_size); encoder->protocol = protocol; return encoder; } -void irda_common_encoder_free(IrdaCommonEncoder* encoder) { +void infrared_common_encoder_free(InfraredCommonEncoder* encoder) { furi_assert(encoder); free(encoder); } -void irda_common_encoder_reset(IrdaCommonEncoder* encoder) { +void infrared_common_encoder_reset(InfraredCommonEncoder* encoder) { furi_assert(encoder); encoder->timings_encoded = 0; encoder->timings_sum = 0; encoder->bits_encoded = 0; - encoder->state = IrdaCommonEncoderStateSilence; + encoder->state = InfraredCommonEncoderStateSilence; encoder->switch_detect = 0; uint8_t max_databit_len = 0; diff --git a/lib/infrared/encoder_decoder/common/infrared_common_i.h b/lib/infrared/encoder_decoder/common/infrared_common_i.h new file mode 100644 index 00000000..20dbeb79 --- /dev/null +++ b/lib/infrared/encoder_decoder/common/infrared_common_i.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include "infrared.h" +#include "infrared_i.h" + +#define MATCH_TIMING(x, v, delta) (((x) < (v + delta)) && ((x) > (v - delta))) + +typedef struct InfraredCommonDecoder InfraredCommonDecoder; +typedef struct InfraredCommonEncoder InfraredCommonEncoder; + +typedef InfraredStatus (*InfraredCommonDecode)(InfraredCommonDecoder*, bool, uint32_t); +typedef InfraredStatus (*InfraredCommonDecodeRepeat)(InfraredCommonDecoder*); +typedef bool (*InfraredCommonInterpret)(InfraredCommonDecoder*); +typedef InfraredStatus ( + *InfraredCommonEncode)(InfraredCommonEncoder* encoder, uint32_t* out, bool* polarity); + +typedef struct { + InfraredTimings timings; + bool manchester_start_from_space; + bool no_stop_bit; + uint8_t databit_len[4]; + InfraredCommonDecode decode; + InfraredCommonDecodeRepeat decode_repeat; + InfraredCommonInterpret interpret; + InfraredCommonEncode encode; + InfraredCommonEncode encode_repeat; +} InfraredCommonProtocolSpec; + +typedef enum { + InfraredCommonDecoderStateWaitPreamble, + InfraredCommonDecoderStateDecode, + InfraredCommonDecoderStateProcessRepeat, +} InfraredCommonStateDecoder; + +typedef enum { + InfraredCommonEncoderStateSilence, + InfraredCommonEncoderStatePreamble, + InfraredCommonEncoderStateEncode, + InfraredCommonEncoderStateEncodeRepeat, +} InfraredCommonStateEncoder; + +struct InfraredCommonDecoder { + const InfraredCommonProtocolSpec* protocol; + void* context; + uint32_t timings[6]; + InfraredMessage message; + InfraredCommonStateDecoder state; + uint8_t timings_cnt; + bool switch_detect; + bool level; + uint16_t databit_cnt; + uint8_t data[]; +}; + +struct InfraredCommonEncoder { + const InfraredCommonProtocolSpec* protocol; + InfraredCommonStateEncoder state; + bool switch_detect; + uint8_t bits_to_encode; + uint8_t bits_encoded; + uint32_t timings_sum; + uint32_t timings_encoded; + void* context; + uint8_t data[]; +}; + +InfraredMessage* + infrared_common_decode(InfraredCommonDecoder* decoder, bool level, uint32_t duration); +InfraredStatus + infrared_common_decode_pdwm(InfraredCommonDecoder* decoder, bool level, uint32_t timing); +InfraredStatus + infrared_common_decode_manchester(InfraredCommonDecoder* decoder, bool level, uint32_t timing); +void* infrared_common_decoder_alloc(const InfraredCommonProtocolSpec* protocol); +void infrared_common_decoder_free(InfraredCommonDecoder* decoder); +void infrared_common_decoder_reset(InfraredCommonDecoder* decoder); +InfraredMessage* infrared_common_decoder_check_ready(InfraredCommonDecoder* decoder); + +InfraredStatus + infrared_common_encode(InfraredCommonEncoder* encoder, uint32_t* duration, bool* polarity); +InfraredStatus + infrared_common_encode_pdwm(InfraredCommonEncoder* encoder, uint32_t* duration, bool* polarity); +InfraredStatus infrared_common_encode_manchester( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* polarity); +void* infrared_common_encoder_alloc(const InfraredCommonProtocolSpec* protocol); +void infrared_common_encoder_free(InfraredCommonEncoder* encoder); +void infrared_common_encoder_reset(InfraredCommonEncoder* encoder); diff --git a/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c b/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c new file mode 100644 index 00000000..e8f7664a --- /dev/null +++ b/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c @@ -0,0 +1,117 @@ +#include "infrared_common_i.h" +#include "infrared_protocol_defs_i.h" + +const InfraredCommonProtocolSpec protocol_nec = { + .timings = + { + .preamble_mark = INFRARED_NEC_PREAMBLE_MARK, + .preamble_space = INFRARED_NEC_PREAMBLE_SPACE, + .bit1_mark = INFRARED_NEC_BIT1_MARK, + .bit1_space = INFRARED_NEC_BIT1_SPACE, + .bit0_mark = INFRARED_NEC_BIT0_MARK, + .bit0_space = INFRARED_NEC_BIT0_SPACE, + .preamble_tolerance = INFRARED_NEC_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_NEC_BIT_TOLERANCE, + .silence_time = INFRARED_NEC_SILENCE, + .min_split_time = INFRARED_NEC_MIN_SPLIT_TIME, + }, + .databit_len[0] = 42, + .databit_len[1] = 32, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_nec_interpret, + .decode_repeat = infrared_decoder_nec_decode_repeat, + .encode_repeat = infrared_encoder_nec_encode_repeat, +}; + +const InfraredCommonProtocolSpec protocol_samsung32 = { + .timings = + { + .preamble_mark = INFRARED_SAMSUNG_PREAMBLE_MARK, + .preamble_space = INFRARED_SAMSUNG_PREAMBLE_SPACE, + .bit1_mark = INFRARED_SAMSUNG_BIT1_MARK, + .bit1_space = INFRARED_SAMSUNG_BIT1_SPACE, + .bit0_mark = INFRARED_SAMSUNG_BIT0_MARK, + .bit0_space = INFRARED_SAMSUNG_BIT0_SPACE, + .preamble_tolerance = INFRARED_SAMSUNG_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_SAMSUNG_BIT_TOLERANCE, + .silence_time = INFRARED_SAMSUNG_SILENCE, + .min_split_time = INFRARED_SAMSUNG_MIN_SPLIT_TIME, + }, + .databit_len[0] = 32, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_samsung32_interpret, + .decode_repeat = infrared_decoder_samsung32_decode_repeat, + .encode_repeat = infrared_encoder_samsung32_encode_repeat, +}; + +const InfraredCommonProtocolSpec protocol_rc6 = { + .timings = + { + .preamble_mark = INFRARED_RC6_PREAMBLE_MARK, + .preamble_space = INFRARED_RC6_PREAMBLE_SPACE, + .bit1_mark = INFRARED_RC6_BIT, + .preamble_tolerance = INFRARED_RC6_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_RC6_BIT_TOLERANCE, + .silence_time = INFRARED_RC6_SILENCE, + .min_split_time = INFRARED_RC6_MIN_SPLIT_TIME, + }, + .databit_len[0] = + 1 + 3 + 1 + 8 + + 8, // start_bit + 3 mode bits, + 1 toggle bit (x2 timing) + 8 address + 8 command + .manchester_start_from_space = false, + .decode = infrared_decoder_rc6_decode_manchester, + .encode = infrared_encoder_rc6_encode_manchester, + .interpret = infrared_decoder_rc6_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +const InfraredCommonProtocolSpec protocol_rc5 = { + .timings = + { + .preamble_mark = 0, + .preamble_space = 0, + .bit1_mark = INFRARED_RC5_BIT, + .preamble_tolerance = 0, + .bit_tolerance = INFRARED_RC5_BIT_TOLERANCE, + .silence_time = INFRARED_RC5_SILENCE, + .min_split_time = INFRARED_RC5_MIN_SPLIT_TIME, + }, + .databit_len[0] = 1 + 1 + 1 + 5 + + 6, // start_bit + start_bit/command_bit + toggle_bit + 5 address + 6 command + .manchester_start_from_space = true, + .decode = infrared_common_decode_manchester, + .encode = infrared_common_encode_manchester, + .interpret = infrared_decoder_rc5_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +const InfraredCommonProtocolSpec protocol_sirc = { + .timings = + { + .preamble_mark = INFRARED_SIRC_PREAMBLE_MARK, + .preamble_space = INFRARED_SIRC_PREAMBLE_SPACE, + .bit1_mark = INFRARED_SIRC_BIT1_MARK, + .bit1_space = INFRARED_SIRC_BIT1_SPACE, + .bit0_mark = INFRARED_SIRC_BIT0_MARK, + .bit0_space = INFRARED_SIRC_BIT0_SPACE, + .preamble_tolerance = INFRARED_SIRC_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_SIRC_BIT_TOLERANCE, + .silence_time = INFRARED_SIRC_SILENCE, + .min_split_time = INFRARED_SIRC_MIN_SPLIT_TIME, + }, + .databit_len[0] = 20, + .databit_len[1] = 15, + .databit_len[2] = 12, + .no_stop_bit = true, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_sirc_interpret, + .decode_repeat = NULL, + .encode_repeat = infrared_encoder_sirc_encode_repeat, +}; diff --git a/lib/infrared/encoder_decoder/infrared.c b/lib/infrared/encoder_decoder/infrared.c new file mode 100644 index 00000000..5dfa9281 --- /dev/null +++ b/lib/infrared/encoder_decoder/infrared.c @@ -0,0 +1,301 @@ +#include "infrared.h" +#include "furi/check.h" +#include "common/infrared_common_i.h" +#include "infrared_protocol_defs_i.h" +#include +#include +#include +#include +#include "infrared_i.h" +#include + +typedef struct { + InfraredAlloc alloc; + InfraredDecode decode; + InfraredDecoderReset reset; + InfraredFree free; + InfraredDecoderCheckReady check_ready; +} InfraredDecoders; + +typedef struct { + InfraredAlloc alloc; + InfraredEncode encode; + InfraredEncoderReset reset; + InfraredFree free; +} InfraredEncoders; + +struct InfraredDecoderHandler { + void** ctx; +}; + +struct InfraredEncoderHandler { + void* handler; + const InfraredEncoders* encoder; +}; + +typedef struct { + InfraredEncoders encoder; + InfraredDecoders decoder; + InfraredGetProtocolSpec get_protocol_spec; +} InfraredEncoderDecoder; + +static const InfraredEncoderDecoder infrared_encoder_decoder[] = { + { + .decoder = + {.alloc = infrared_decoder_nec_alloc, + .decode = infrared_decoder_nec_decode, + .reset = infrared_decoder_nec_reset, + .check_ready = infrared_decoder_nec_check_ready, + .free = infrared_decoder_nec_free}, + .encoder = + {.alloc = infrared_encoder_nec_alloc, + .encode = infrared_encoder_nec_encode, + .reset = infrared_encoder_nec_reset, + .free = infrared_encoder_nec_free}, + .get_protocol_spec = infrared_nec_get_spec, + }, + { + .decoder = + {.alloc = infrared_decoder_samsung32_alloc, + .decode = infrared_decoder_samsung32_decode, + .reset = infrared_decoder_samsung32_reset, + .check_ready = infrared_decoder_samsung32_check_ready, + .free = infrared_decoder_samsung32_free}, + .encoder = + {.alloc = infrared_encoder_samsung32_alloc, + .encode = infrared_encoder_samsung32_encode, + .reset = infrared_encoder_samsung32_reset, + .free = infrared_encoder_samsung32_free}, + .get_protocol_spec = infrared_samsung32_get_spec, + }, + { + .decoder = + {.alloc = infrared_decoder_rc5_alloc, + .decode = infrared_decoder_rc5_decode, + .reset = infrared_decoder_rc5_reset, + .check_ready = infrared_decoder_rc5_check_ready, + .free = infrared_decoder_rc5_free}, + .encoder = + {.alloc = infrared_encoder_rc5_alloc, + .encode = infrared_encoder_rc5_encode, + .reset = infrared_encoder_rc5_reset, + .free = infrared_encoder_rc5_free}, + .get_protocol_spec = infrared_rc5_get_spec, + }, + { + .decoder = + {.alloc = infrared_decoder_rc6_alloc, + .decode = infrared_decoder_rc6_decode, + .reset = infrared_decoder_rc6_reset, + .check_ready = infrared_decoder_rc6_check_ready, + .free = infrared_decoder_rc6_free}, + .encoder = + {.alloc = infrared_encoder_rc6_alloc, + .encode = infrared_encoder_rc6_encode, + .reset = infrared_encoder_rc6_reset, + .free = infrared_encoder_rc6_free}, + .get_protocol_spec = infrared_rc6_get_spec, + }, + { + .decoder = + {.alloc = infrared_decoder_sirc_alloc, + .decode = infrared_decoder_sirc_decode, + .reset = infrared_decoder_sirc_reset, + .check_ready = infrared_decoder_sirc_check_ready, + .free = infrared_decoder_sirc_free}, + .encoder = + {.alloc = infrared_encoder_sirc_alloc, + .encode = infrared_encoder_sirc_encode, + .reset = infrared_encoder_sirc_reset, + .free = infrared_encoder_sirc_free}, + .get_protocol_spec = infrared_sirc_get_spec, + }, +}; + +static int infrared_find_index_by_protocol(InfraredProtocol protocol); +static const InfraredProtocolSpecification* + infrared_get_spec_by_protocol(InfraredProtocol protocol); + +const InfraredMessage* + infrared_decode(InfraredDecoderHandler* handler, bool level, uint32_t duration) { + furi_assert(handler); + + InfraredMessage* message = NULL; + InfraredMessage* result = NULL; + + for(int i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { + if(infrared_encoder_decoder[i].decoder.decode) { + message = infrared_encoder_decoder[i].decoder.decode(handler->ctx[i], level, duration); + if(!result && message) { + result = message; + } + } + } + + return result; +} + +InfraredDecoderHandler* infrared_alloc_decoder(void) { + InfraredDecoderHandler* handler = malloc(sizeof(InfraredDecoderHandler)); + handler->ctx = malloc(sizeof(void*) * COUNT_OF(infrared_encoder_decoder)); + + for(int i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { + handler->ctx[i] = 0; + if(infrared_encoder_decoder[i].decoder.alloc) + handler->ctx[i] = infrared_encoder_decoder[i].decoder.alloc(); + } + + infrared_reset_decoder(handler); + return handler; +} + +void infrared_free_decoder(InfraredDecoderHandler* handler) { + furi_assert(handler); + furi_assert(handler->ctx); + + for(int i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { + if(infrared_encoder_decoder[i].decoder.free) + infrared_encoder_decoder[i].decoder.free(handler->ctx[i]); + } + + free(handler->ctx); + free(handler); +} + +void infrared_reset_decoder(InfraredDecoderHandler* handler) { + for(int i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { + if(infrared_encoder_decoder[i].decoder.reset) + infrared_encoder_decoder[i].decoder.reset(handler->ctx[i]); + } +} + +const InfraredMessage* infrared_check_decoder_ready(InfraredDecoderHandler* handler) { + furi_assert(handler); + + InfraredMessage* message = NULL; + InfraredMessage* result = NULL; + + for(int i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { + if(infrared_encoder_decoder[i].decoder.check_ready) { + message = infrared_encoder_decoder[i].decoder.check_ready(handler->ctx[i]); + if(!result && message) { + result = message; + } + } + } + + return result; +} + +InfraredEncoderHandler* infrared_alloc_encoder(void) { + InfraredEncoderHandler* handler = malloc(sizeof(InfraredEncoderHandler)); + handler->handler = NULL; + handler->encoder = NULL; + return handler; +} + +void infrared_free_encoder(InfraredEncoderHandler* handler) { + furi_assert(handler); + const InfraredEncoders* encoder = handler->encoder; + + if(encoder || handler->handler) { + furi_assert(encoder); + furi_assert(handler->handler); + furi_assert(encoder->free); + encoder->free(handler->handler); + } + + free(handler); +} + +static int infrared_find_index_by_protocol(InfraredProtocol protocol) { + for(int i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { + if(infrared_encoder_decoder[i].get_protocol_spec(protocol)) { + return i; + } + } + + return -1; +} + +void infrared_reset_encoder(InfraredEncoderHandler* handler, const InfraredMessage* message) { + furi_assert(handler); + furi_assert(message); + int index = infrared_find_index_by_protocol(message->protocol); + furi_check(index >= 0); + + const InfraredEncoders* required_encoder = &infrared_encoder_decoder[index].encoder; + furi_assert(required_encoder); + furi_assert(required_encoder->reset); + furi_assert(required_encoder->alloc); + + /* Realloc encoder if different protocol set */ + if(required_encoder != handler->encoder) { + if(handler->handler != NULL) { + furi_assert(handler->encoder->free); + handler->encoder->free(handler->handler); + } + handler->encoder = required_encoder; + handler->handler = handler->encoder->alloc(); + } + + handler->encoder->reset(handler->handler, message); +} + +InfraredStatus infrared_encode(InfraredEncoderHandler* handler, uint32_t* duration, bool* level) { + furi_assert(handler); + furi_assert(duration); + furi_assert(level); + const InfraredEncoders* encoder = handler->encoder; + furi_assert(encoder); + furi_assert(encoder->encode); + + InfraredStatus status = encoder->encode(handler->handler, duration, level); + furi_assert(status != InfraredStatusError); + + return status; +} + +bool infrared_is_protocol_valid(InfraredProtocol protocol) { + return infrared_find_index_by_protocol(protocol) >= 0; +} + +InfraredProtocol infrared_get_protocol_by_name(const char* protocol_name) { + for(InfraredProtocol protocol = 0; protocol < InfraredProtocolMAX; ++protocol) { + const char* name = infrared_get_protocol_name(protocol); + if(!strcmp(name, protocol_name)) return protocol; + } + return InfraredProtocolUnknown; +} + +static const InfraredProtocolSpecification* + infrared_get_spec_by_protocol(InfraredProtocol protocol) { + int index = infrared_find_index_by_protocol(protocol); + const InfraredProtocolSpecification* spec = NULL; + if(index >= 0) { + spec = infrared_encoder_decoder[index].get_protocol_spec(protocol); + } + + furi_assert(spec); + return spec; +} + +const char* infrared_get_protocol_name(InfraredProtocol protocol) { + return infrared_get_spec_by_protocol(protocol)->name; +} + +uint8_t infrared_get_protocol_address_length(InfraredProtocol protocol) { + return infrared_get_spec_by_protocol(protocol)->address_length; +} + +uint8_t infrared_get_protocol_command_length(InfraredProtocol protocol) { + return infrared_get_spec_by_protocol(protocol)->command_length; +} + +uint32_t infrared_get_protocol_frequency(InfraredProtocol protocol) { + return infrared_get_spec_by_protocol(protocol)->frequency; +} + +float infrared_get_protocol_duty_cycle(InfraredProtocol protocol) { + return infrared_get_spec_by_protocol(protocol)->duty_cycle; +} diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h new file mode 100644 index 00000000..94591300 --- /dev/null +++ b/lib/infrared/encoder_decoder/infrared.h @@ -0,0 +1,205 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define INFRARED_COMMON_CARRIER_FREQUENCY ((uint32_t)38000) +#define INFRARED_COMMON_DUTY_CYCLE ((float)0.33) + +/* if we want to see splitted raw signals during brutforce, + * we have to have RX raw timing delay less than TX */ +#define INFRARED_RAW_RX_TIMING_DELAY_US 150000 +#define INFRARED_RAW_TX_TIMING_DELAY_US 180000 + +typedef struct InfraredDecoderHandler InfraredDecoderHandler; +typedef struct InfraredEncoderHandler InfraredEncoderHandler; + +typedef enum { + InfraredProtocolUnknown = -1, + InfraredProtocolNEC = 0, + InfraredProtocolNECext, + InfraredProtocolNEC42, + InfraredProtocolNEC42ext, + InfraredProtocolSamsung32, + InfraredProtocolRC6, + InfraredProtocolRC5, + InfraredProtocolRC5X, + InfraredProtocolSIRC, + InfraredProtocolSIRC15, + InfraredProtocolSIRC20, + InfraredProtocolMAX, +} InfraredProtocol; + +typedef struct { + InfraredProtocol protocol; + uint32_t address; + uint32_t command; + bool repeat; +} InfraredMessage; + +typedef enum { + InfraredStatusError, + InfraredStatusOk, + InfraredStatusDone, + InfraredStatusReady, +} InfraredStatus; + +/** + * Initialize decoder. + * + * \return returns pointer to INFRARED decoder handler if success, otherwise - error. + */ +InfraredDecoderHandler* infrared_alloc_decoder(void); + +/** + * Provide to decoder next timing. + * + * \param[in] handler - handler to INFRARED decoders. Should be acquired with \c infrared_alloc_decoder(). + * \param[in] level - high(true) or low(false) level of input signal to analyze. + * it should alternate every call, otherwise it is an error case, + * and decoder resets its state and start decoding from the start. + * \param[in] duration - duration of steady high/low input signal. + * \return if message is ready, returns pointer to decoded message, returns NULL. + * Note: ownership of returned ptr belongs to handler. So pointer is valid + * up to next infrared_free_decoder(), infrared_reset_decoder(), + * infrared_decode(), infrared_check_decoder_ready() calls. + */ +const InfraredMessage* + infrared_decode(InfraredDecoderHandler* handler, bool level, uint32_t duration); + +/** + * Check whether decoder is ready. + * Functionality is quite similar to infrared_decode(), but with no timing providing. + * Some protocols (e.g. Sony SIRC) has variable payload length, which means we + * can't recognize end of message right after receiving last bit. That's why + * application should call to infrared_check_decoder_ready() after some timeout to + * retrieve decoded message, if so. + * + * \param[in] handler - handler to INFRARED decoders. Should be acquired with \c infrared_alloc_decoder(). + * \return if message is ready, returns pointer to decoded message, returns NULL. + * Note: ownership of returned ptr belongs to handler. So pointer is valid + * up to next infrared_free_decoder(), infrared_reset_decoder(), + * infrared_decode(), infrared_check_decoder_ready() calls. + */ +const InfraredMessage* infrared_check_decoder_ready(InfraredDecoderHandler* handler); + +/** + * Deinitialize decoder and free allocated memory. + * + * \param[in] handler - handler to INFRARED decoders. Should be acquired with \c infrared_alloc_decoder(). + */ +void infrared_free_decoder(InfraredDecoderHandler* handler); + +/** + * Reset INFRARED decoder. + * + * \param[in] handler - handler to INFRARED decoders. Should be acquired with \c infrared_alloc_decoder(). + */ +void infrared_reset_decoder(InfraredDecoderHandler* handler); + +/** + * Get protocol name by protocol enum. + * + * \param[in] protocol - protocol identifier. + * \return string to protocol name. + */ +const char* infrared_get_protocol_name(InfraredProtocol protocol); + +/** + * Get protocol enum by protocol name. + * + * \param[in] protocol_name - string to protocol name. + * \return protocol identifier. + */ +InfraredProtocol infrared_get_protocol_by_name(const char* protocol_name); + +/** + * Get address length by protocol enum. + * + * \param[in] protocol - protocol identifier. + * \return length of address in bits. + */ +uint8_t infrared_get_protocol_address_length(InfraredProtocol protocol); + +/** + * Get command length by protocol enum. + * + * \param[in] protocol - protocol identifier. + * \return length of command in bits. + */ +uint8_t infrared_get_protocol_command_length(InfraredProtocol protocol); + +/** + * Checks whether protocol valid. + * + * \param[in] protocol - protocol identifier. + * \return true if protocol is valid, false otherwise. + */ +bool infrared_is_protocol_valid(InfraredProtocol protocol); + +/** + * Allocate INFRARED encoder. + * + * \return encoder handler. + */ +InfraredEncoderHandler* infrared_alloc_encoder(void); + +/** + * Free encoder handler previously allocated with \c infrared_alloc_encoder(). + * + * \param[in] handler - handler to INFRARED encoder. Should be acquired with \c infrared_alloc_encoder(). + */ +void infrared_free_encoder(InfraredEncoderHandler* handler); + +/** + * Encode previously set INFRARED message. + * Usage: + * 1) alloc with \c infrared_alloc_encoder() + * 2) set message to encode with \c infrared_reset_encoder() + * 3) call for \c infrared_encode() to continuously get one at a time timings. + * 4) when \c infrared_encode() returns InfraredStatusDone, it means new message is fully encoded. + * 5) to encode additional timings, just continue calling \c infrared_encode(). + * + * \param[in] handler - handler to INFRARED encoder. Should be acquired with \c infrared_alloc_encoder(). + * \param[out] duration - encoded timing. + * \param[out] level - encoded level. + * + * \return status of encode operation. + */ +InfraredStatus infrared_encode(InfraredEncoderHandler* handler, uint32_t* duration, bool* level); + +/** + * Reset INFRARED encoder and set new message to encode. If it's not called after receiveing + * InfraredStatusDone in \c infrared_encode(), encoder will encode repeat messages + * till the end of time. + * + * \param[in] handler - handler to INFRARED encoder. Should be acquired with \c infrared_alloc_encoder(). + * \param[in] message - message to encode. + */ +void infrared_reset_encoder(InfraredEncoderHandler* handler, const InfraredMessage* message); + +/** + * Get PWM frequency value for selected protocol + * + * \param[in] protocol - protocol to get from PWM frequency + * + * \return frequency + */ +uint32_t infrared_get_protocol_frequency(InfraredProtocol protocol); + +/** + * Get PWM duty cycle value for selected protocol + * + * \param[in] protocol - protocol to get from PWM duty cycle + * + * \return duty cycle + */ +float infrared_get_protocol_duty_cycle(InfraredProtocol protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/irda/encoder_decoder/irda_i.h b/lib/infrared/encoder_decoder/infrared_i.h similarity index 53% rename from lib/irda/encoder_decoder/irda_i.h rename to lib/infrared/encoder_decoder/infrared_i.h index 3ff980d5..3a645ced 100644 --- a/lib/irda/encoder_decoder/irda_i.h +++ b/lib/infrared/encoder_decoder/infrared_i.h @@ -1,5 +1,5 @@ #pragma once -#include "irda.h" +#include "infrared.h" #include #include @@ -14,7 +14,7 @@ typedef struct { uint16_t bit0_space; uint32_t preamble_tolerance; uint32_t bit_tolerance; -} IrdaTimings; +} InfraredTimings; typedef struct { const char* name; @@ -22,19 +22,19 @@ typedef struct { uint8_t command_length; uint32_t frequency; float duty_cycle; -} IrdaProtocolSpecification; +} InfraredProtocolSpecification; -typedef const IrdaProtocolSpecification* (*IrdaGetProtocolSpec)(IrdaProtocol protocol); +typedef const InfraredProtocolSpecification* (*InfraredGetProtocolSpec)(InfraredProtocol protocol); -typedef void* (*IrdaAlloc)(void); -typedef void (*IrdaFree)(void*); +typedef void* (*InfraredAlloc)(void); +typedef void (*InfraredFree)(void*); -typedef void (*IrdaDecoderReset)(void*); -typedef IrdaMessage* (*IrdaDecode)(void* ctx, bool level, uint32_t duration); -typedef IrdaMessage* (*IrdaDecoderCheckReady)(void*); +typedef void (*InfraredDecoderReset)(void*); +typedef InfraredMessage* (*InfraredDecode)(void* ctx, bool level, uint32_t duration); +typedef InfraredMessage* (*InfraredDecoderCheckReady)(void*); -typedef void (*IrdaEncoderReset)(void* encoder, const IrdaMessage* message); -typedef IrdaStatus (*IrdaEncode)(void* encoder, uint32_t* out, bool* polarity); +typedef void (*InfraredEncoderReset)(void* encoder, const InfraredMessage* message); +typedef InfraredStatus (*InfraredEncode)(void* encoder, uint32_t* out, bool* polarity); static inline uint8_t reverse(uint8_t value) { uint8_t reverse_value = 0; diff --git a/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h b/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h new file mode 100644 index 00000000..575961f1 --- /dev/null +++ b/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h @@ -0,0 +1,269 @@ +#pragma once + +#include +#include +#include +#include "infrared.h" +#include "common/infrared_common_i.h" + +/*************************************************************************************************** +* NEC protocol description +* https://radioparty.ru/manuals/encyclopedia/213-ircontrol?start=1 +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Stop +* mark space Modulation up to period repeat repeat bit +* mark space +* +* 9000 4500 32 bit + stop bit ...110000 9000 2250 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ _ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ____________ ___ +* +***************************************************************************************************/ + +#define INFRARED_NEC_PREAMBLE_MARK 9000 +#define INFRARED_NEC_PREAMBLE_SPACE 4500 +#define INFRARED_NEC_BIT1_MARK 560 +#define INFRARED_NEC_BIT1_SPACE 1690 +#define INFRARED_NEC_BIT0_MARK 560 +#define INFRARED_NEC_BIT0_SPACE 560 +#define INFRARED_NEC_REPEAT_PERIOD 110000 +#define INFRARED_NEC_SILENCE INFRARED_NEC_REPEAT_PERIOD +#define INFRARED_NEC_MIN_SPLIT_TIME INFRARED_NEC_REPEAT_PAUSE_MIN +#define INFRARED_NEC_REPEAT_PAUSE_MIN 4000 +#define INFRARED_NEC_REPEAT_PAUSE_MAX 150000 +#define INFRARED_NEC_REPEAT_MARK 9000 +#define INFRARED_NEC_REPEAT_SPACE 2250 +#define INFRARED_NEC_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_NEC_BIT_TOLERANCE 120 // us + +void* infrared_decoder_nec_alloc(void); +void infrared_decoder_nec_reset(void* decoder); +void infrared_decoder_nec_free(void* decoder); +InfraredMessage* infrared_decoder_nec_check_ready(void* decoder); +InfraredMessage* infrared_decoder_nec_decode(void* decoder, bool level, uint32_t duration); +void* infrared_encoder_nec_alloc(void); +InfraredStatus infrared_encoder_nec_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_nec_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_nec_free(void* encoder_ptr); +bool infrared_decoder_nec_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_nec_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_nec_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); +const InfraredProtocolSpecification* infrared_nec_get_spec(InfraredProtocol protocol); + +extern const InfraredCommonProtocolSpec protocol_nec; + +/*************************************************************************************************** +* SAMSUNG32 protocol description +* https://www.mikrocontroller.net/articles/IRMP_-_english#SAMSUNG +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Bit1 Stop +* mark space Modulation repeat repeat bit +* mark space +* +* 4500 4500 32 bit + stop bit 40000/100000 4500 4500 +* __________ _ _ _ _ _ _ _ _ _ _ _ ___________ _ _ +* _ __________ __ _ __ __ __ _ _ __ __ _ ________________ ____________ ____ ___ +* +***************************************************************************************************/ + +#define INFRARED_SAMSUNG_PREAMBLE_MARK 4500 +#define INFRARED_SAMSUNG_PREAMBLE_SPACE 4500 +#define INFRARED_SAMSUNG_BIT1_MARK 550 +#define INFRARED_SAMSUNG_BIT1_SPACE 1650 +#define INFRARED_SAMSUNG_BIT0_MARK 550 +#define INFRARED_SAMSUNG_BIT0_SPACE 550 +#define INFRARED_SAMSUNG_REPEAT_PAUSE_MIN 30000 +#define INFRARED_SAMSUNG_REPEAT_PAUSE1 46000 +#define INFRARED_SAMSUNG_REPEAT_PAUSE2 97000 +/* Samsung silence have to be greater than REPEAT MAX + * otherwise there can be problems during unit tests parsing + * of some data. Real tolerances we don't know, but in real life + * silence time should be greater than max repeat time. This is + * because of similar preambule timings for repeat and first messages. */ +#define INFRARED_SAMSUNG_MIN_SPLIT_TIME 5000 +#define INFRARED_SAMSUNG_SILENCE 145000 +#define INFRARED_SAMSUNG_REPEAT_PAUSE_MAX 140000 +#define INFRARED_SAMSUNG_REPEAT_MARK 4500 +#define INFRARED_SAMSUNG_REPEAT_SPACE 4500 +#define INFRARED_SAMSUNG_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_SAMSUNG_BIT_TOLERANCE 120 // us + +void* infrared_decoder_samsung32_alloc(void); +void infrared_decoder_samsung32_reset(void* decoder); +void infrared_decoder_samsung32_free(void* decoder); +InfraredMessage* infrared_decoder_samsung32_check_ready(void* ctx); +InfraredMessage* infrared_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration); +InfraredStatus + infrared_encoder_samsung32_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_samsung32_reset(void* encoder_ptr, const InfraredMessage* message); +void* infrared_encoder_samsung32_alloc(void); +void infrared_encoder_samsung32_free(void* encoder_ptr); +bool infrared_decoder_samsung32_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_samsung32_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_samsung32_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); +const InfraredProtocolSpecification* infrared_samsung32_get_spec(InfraredProtocol protocol); + +extern const InfraredCommonProtocolSpec protocol_samsung32; + +/*************************************************************************************************** +* RC6 protocol description +* https://www.mikrocontroller.net/articles/IRMP_-_english#RC6_.2B_RC6A +**************************************************************************************************** +* Preamble Manchester/biphase Silence +* mark/space Modulation +* +* 2666 889 444/888 - bit (x2 for toggle bit) 2666 +* +* ________ __ __ __ __ ____ __ __ __ __ __ __ __ __ +* _ _________ ____ __ __ ____ __ __ __ __ __ __ __ __ _______________ +* | 1 | 0 | 0 | 0 | 0 | ... | ... | | +* s m2 m1 m0 T address (MSB) command (MSB) +* +* s - start bit (always 1) +* m0-2 - mode (000 for RC6) +* T - toggle bit, twice longer +* address - 8 bit +* command - 8 bit +***************************************************************************************************/ + +#define INFRARED_RC6_CARRIER_FREQUENCY 36000 +#define INFRARED_RC6_DUTY_CYCLE 0.33 + +#define INFRARED_RC6_PREAMBLE_MARK 2666 +#define INFRARED_RC6_PREAMBLE_SPACE 889 +#define INFRARED_RC6_BIT 444 // half of time-quant for 1 bit +#define INFRARED_RC6_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_RC6_BIT_TOLERANCE 120 // us +/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ +#define INFRARED_RC6_SILENCE (2700 * 10) +#define INFRARED_RC6_MIN_SPLIT_TIME 2700 + +void* infrared_decoder_rc6_alloc(void); +void infrared_decoder_rc6_reset(void* decoder); +void infrared_decoder_rc6_free(void* decoder); +InfraredMessage* infrared_decoder_rc6_check_ready(void* ctx); +InfraredMessage* infrared_decoder_rc6_decode(void* decoder, bool level, uint32_t duration); +void* infrared_encoder_rc6_alloc(void); +void infrared_encoder_rc6_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_rc6_free(void* decoder); +InfraredStatus infrared_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); +bool infrared_decoder_rc6_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_rc6_decode_manchester( + InfraredCommonDecoder* decoder, + bool level, + uint32_t timing); +InfraredStatus infrared_encoder_rc6_encode_manchester( + InfraredCommonEncoder* encoder_ptr, + uint32_t* duration, + bool* polarity); +const InfraredProtocolSpecification* infrared_rc6_get_spec(InfraredProtocol protocol); + +extern const InfraredCommonProtocolSpec protocol_rc6; + +/*************************************************************************************************** +* RC5 protocol description +* https://www.mikrocontroller.net/articles/IRMP_-_english#RC5_.2B_RC5X +**************************************************************************************************** +* Manchester/biphase +* Modulation +* +* 888/1776 - bit (x2 for toggle bit) +* +* __ ____ __ __ __ __ __ __ __ __ +* __ __ ____ __ __ __ __ __ __ __ _ +* | 1 | 1 | 0 | ... | ... | +* s si T address (MSB) command (MSB) +* +* Note: manchester starts from space timing, so it have to be handled properly +* s - start bit (always 1) +* si - RC5: start bit (always 1), RC5X - 7-th bit of address (in our case always 0) +* T - toggle bit, change it's value every button press +* address - 5 bit +* command - 6/7 bit +***************************************************************************************************/ + +#define INFRARED_RC5_CARRIER_FREQUENCY 36000 +#define INFRARED_RC5_DUTY_CYCLE 0.33 + +#define INFRARED_RC5_PREAMBLE_MARK 0 +#define INFRARED_RC5_PREAMBLE_SPACE 0 +#define INFRARED_RC5_BIT 888 // half of time-quant for 1 bit +#define INFRARED_RC5_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_RC5_BIT_TOLERANCE 120 // us +/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ +#define INFRARED_RC5_SILENCE (2700 * 10) +#define INFRARED_RC5_MIN_SPLIT_TIME 2700 + +void* infrared_decoder_rc5_alloc(void); +void infrared_decoder_rc5_reset(void* decoder); +void infrared_decoder_rc5_free(void* decoder); +InfraredMessage* infrared_decoder_rc5_check_ready(void* ctx); +InfraredMessage* infrared_decoder_rc5_decode(void* decoder, bool level, uint32_t duration); +void* infrared_encoder_rc5_alloc(void); +void infrared_encoder_rc5_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_rc5_free(void* decoder); +InfraredStatus infrared_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); +bool infrared_decoder_rc5_interpret(InfraredCommonDecoder* decoder); +const InfraredProtocolSpecification* infrared_rc5_get_spec(InfraredProtocol protocol); + +extern const InfraredCommonProtocolSpec protocol_rc5; + +/*************************************************************************************************** +* Sony SIRC protocol description +* https://www.sbprojects.net/knowledge/ir/sirc.php +* http://picprojects.org.uk/ +**************************************************************************************************** +* Preamble Preamble Pulse Width Modulation Pause Entirely repeat +* mark space up to period message.. +* +* 2400 600 12/15/20 bits (600,1200) ...45000 2400 600 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ __________ _ _ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ____________________ __________ _ +* | command | address | +* SIRC | 7b LSB | 5b LSB | +* SIRC15 | 7b LSB | 8b LSB | +* SIRC20 | 7b LSB | 13b LSB | +* +* No way to determine either next message is repeat or not, +* so recognize only fact message received. Sony remotes always send at least 3 messages. +* Assume 8 last extended bits for SIRC20 are address bits. +***************************************************************************************************/ + +#define INFRARED_SIRC_CARRIER_FREQUENCY 40000 +#define INFRARED_SIRC_DUTY_CYCLE 0.33 +#define INFRARED_SIRC_PREAMBLE_MARK 2400 +#define INFRARED_SIRC_PREAMBLE_SPACE 600 +#define INFRARED_SIRC_BIT1_MARK 1200 +#define INFRARED_SIRC_BIT1_SPACE 600 +#define INFRARED_SIRC_BIT0_MARK 600 +#define INFRARED_SIRC_BIT0_SPACE 600 +#define INFRARED_SIRC_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_SIRC_BIT_TOLERANCE 120 // us +#define INFRARED_SIRC_SILENCE 10000 +#define INFRARED_SIRC_MIN_SPLIT_TIME (INFRARED_SIRC_SILENCE - 1000) +#define INFRARED_SIRC_REPEAT_PERIOD 45000 + +void* infrared_decoder_sirc_alloc(void); +void infrared_decoder_sirc_reset(void* decoder); +InfraredMessage* infrared_decoder_sirc_check_ready(void* decoder); +uint32_t infrared_decoder_sirc_get_timeout(void* decoder); +void infrared_decoder_sirc_free(void* decoder); +InfraredMessage* infrared_decoder_sirc_decode(void* decoder, bool level, uint32_t duration); +void* infrared_encoder_sirc_alloc(void); +void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_sirc_free(void* decoder); +InfraredStatus infrared_encoder_sirc_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); +bool infrared_decoder_sirc_interpret(InfraredCommonDecoder* decoder); +const InfraredProtocolSpecification* infrared_sirc_get_spec(InfraredProtocol protocol); +InfraredStatus infrared_encoder_sirc_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); + +extern const InfraredCommonProtocolSpec protocol_sirc; diff --git a/lib/irda/encoder_decoder/nec/irda_decoder_nec.c b/lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c similarity index 58% rename from lib/irda/encoder_decoder/nec/irda_decoder_nec.c rename to lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c index f5418535..10ff56da 100644 --- a/lib/irda/encoder_decoder/nec/irda_decoder_nec.c +++ b/lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c @@ -1,16 +1,16 @@ -#include "common/irda_common_i.h" -#include "irda.h" -#include "irda_protocol_defs_i.h" +#include "common/infrared_common_i.h" +#include "infrared.h" +#include "infrared_protocol_defs_i.h" #include #include #include -#include "../irda_i.h" +#include "../infrared_i.h" -IrdaMessage* irda_decoder_nec_check_ready(void* ctx) { - return irda_common_decoder_check_ready(ctx); +InfraredMessage* infrared_decoder_nec_check_ready(void* ctx) { + return infrared_common_decoder_check_ready(ctx); } -bool irda_decoder_nec_interpret(IrdaCommonDecoder* decoder) { +bool infrared_decoder_nec_interpret(InfraredCommonDecoder* decoder) { furi_assert(decoder); bool result = false; @@ -21,13 +21,13 @@ bool irda_decoder_nec_interpret(IrdaCommonDecoder* decoder) { uint8_t command = decoder->data[2]; uint8_t command_inverse = decoder->data[3]; if((command == (uint8_t)~command_inverse) && (address == (uint8_t)~address_inverse)) { - decoder->message.protocol = IrdaProtocolNEC; + decoder->message.protocol = InfraredProtocolNEC; decoder->message.address = address; decoder->message.command = command; decoder->message.repeat = false; result = true; } else { - decoder->message.protocol = IrdaProtocolNECext; + decoder->message.protocol = InfraredProtocolNECext; decoder->message.address = decoder->data[0] | (decoder->data[1] << 8); decoder->message.command = decoder->data[2] | (decoder->data[3] << 8); decoder->message.repeat = false; @@ -42,13 +42,13 @@ bool irda_decoder_nec_interpret(IrdaCommonDecoder* decoder) { uint16_t command_inverse = (*data2 >> 2) & 0xFF; if((address == (~address_inverse & 0x1FFF)) && (command == (~command_inverse & 0xFF))) { - decoder->message.protocol = IrdaProtocolNEC42; + decoder->message.protocol = InfraredProtocolNEC42; decoder->message.address = address; decoder->message.command = command; decoder->message.repeat = false; result = true; } else { - decoder->message.protocol = IrdaProtocolNEC42ext; + decoder->message.protocol = InfraredProtocolNEC42ext; decoder->message.address = address | (address_inverse << 13); decoder->message.command = command | (command_inverse << 8); decoder->message.repeat = false; @@ -60,41 +60,41 @@ bool irda_decoder_nec_interpret(IrdaCommonDecoder* decoder) { } // timings start from Space (delay between message and repeat) -IrdaStatus irda_decoder_nec_decode_repeat(IrdaCommonDecoder* decoder) { +InfraredStatus infrared_decoder_nec_decode_repeat(InfraredCommonDecoder* decoder) { furi_assert(decoder); float preamble_tolerance = decoder->protocol->timings.preamble_tolerance; uint32_t bit_tolerance = decoder->protocol->timings.bit_tolerance; - IrdaStatus status = IrdaStatusError; + InfraredStatus status = InfraredStatusError; - if(decoder->timings_cnt < 4) return IrdaStatusOk; + if(decoder->timings_cnt < 4) return InfraredStatusOk; - if((decoder->timings[0] > IRDA_NEC_REPEAT_PAUSE_MIN) && - (decoder->timings[0] < IRDA_NEC_REPEAT_PAUSE_MAX) && - MATCH_TIMING(decoder->timings[1], IRDA_NEC_REPEAT_MARK, preamble_tolerance) && - MATCH_TIMING(decoder->timings[2], IRDA_NEC_REPEAT_SPACE, preamble_tolerance) && + if((decoder->timings[0] > INFRARED_NEC_REPEAT_PAUSE_MIN) && + (decoder->timings[0] < INFRARED_NEC_REPEAT_PAUSE_MAX) && + MATCH_TIMING(decoder->timings[1], INFRARED_NEC_REPEAT_MARK, preamble_tolerance) && + MATCH_TIMING(decoder->timings[2], INFRARED_NEC_REPEAT_SPACE, preamble_tolerance) && MATCH_TIMING(decoder->timings[3], decoder->protocol->timings.bit1_mark, bit_tolerance)) { - status = IrdaStatusReady; + status = InfraredStatusReady; decoder->timings_cnt = 0; } else { - status = IrdaStatusError; + status = InfraredStatusError; } return status; } -void* irda_decoder_nec_alloc(void) { - return irda_common_decoder_alloc(&protocol_nec); +void* infrared_decoder_nec_alloc(void) { + return infrared_common_decoder_alloc(&protocol_nec); } -IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duration) { - return irda_common_decode(decoder, level, duration); +InfraredMessage* infrared_decoder_nec_decode(void* decoder, bool level, uint32_t duration) { + return infrared_common_decode(decoder, level, duration); } -void irda_decoder_nec_free(void* decoder) { - irda_common_decoder_free(decoder); +void infrared_decoder_nec_free(void* decoder) { + infrared_common_decoder_free(decoder); } -void irda_decoder_nec_reset(void* decoder) { - irda_common_decoder_reset(decoder); +void infrared_decoder_nec_reset(void* decoder) { + infrared_common_decoder_reset(decoder); } diff --git a/lib/irda/encoder_decoder/nec/irda_encoder_nec.c b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c similarity index 58% rename from lib/irda/encoder_decoder/nec/irda_encoder_nec.c rename to lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c index e818f128..e1afae7b 100644 --- a/lib/irda/encoder_decoder/nec/irda_encoder_nec.c +++ b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c @@ -1,28 +1,29 @@ #include "furi/check.h" -#include "irda.h" -#include "common/irda_common_i.h" +#include "infrared.h" +#include "common/infrared_common_i.h" #include -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" #include static const uint32_t repeat_timings[] = { - IRDA_NEC_REPEAT_PERIOD - IRDA_NEC_REPEAT_MARK - IRDA_NEC_REPEAT_SPACE - IRDA_NEC_BIT1_MARK, - IRDA_NEC_REPEAT_MARK, - IRDA_NEC_REPEAT_SPACE, - IRDA_NEC_BIT1_MARK, + INFRARED_NEC_REPEAT_PERIOD - INFRARED_NEC_REPEAT_MARK - INFRARED_NEC_REPEAT_SPACE - + INFRARED_NEC_BIT1_MARK, + INFRARED_NEC_REPEAT_MARK, + INFRARED_NEC_REPEAT_SPACE, + INFRARED_NEC_BIT1_MARK, }; -void irda_encoder_nec_reset(void* encoder_ptr, const IrdaMessage* message) { +void infrared_encoder_nec_reset(void* encoder_ptr, const InfraredMessage* message) { furi_assert(encoder_ptr); furi_assert(message); - IrdaCommonEncoder* encoder = encoder_ptr; - irda_common_encoder_reset(encoder); + InfraredCommonEncoder* encoder = encoder_ptr; + infrared_common_encoder_reset(encoder); uint32_t* data1 = (void*)encoder->data; uint32_t* data2 = data1 + 1; - if(message->protocol == IrdaProtocolNEC) { + if(message->protocol == InfraredProtocolNEC) { uint8_t address = message->address; uint8_t address_inverse = ~address; uint8_t command = message->command; @@ -32,11 +33,11 @@ void irda_encoder_nec_reset(void* encoder_ptr, const IrdaMessage* message) { *data1 |= command << 16; *data1 |= command_inverse << 24; encoder->bits_to_encode = 32; - } else if(message->protocol == IrdaProtocolNECext) { + } else if(message->protocol == InfraredProtocolNECext) { *data1 = (uint16_t)message->address; *data1 |= (message->command & 0xFFFF) << 16; encoder->bits_to_encode = 32; - } else if(message->protocol == IrdaProtocolNEC42) { + } else if(message->protocol == InfraredProtocolNEC42) { /* 13 address + 13 inverse address + 8 command + 8 inv command */ *data1 = message->address & 0x1FFFUL; *data1 |= (~message->address & 0x1FFFUL) << 13; @@ -44,7 +45,7 @@ void irda_encoder_nec_reset(void* encoder_ptr, const IrdaMessage* message) { *data2 = (message->command & 0xC0UL) >> 6; *data2 |= (~message->command & 0xFFUL) << 2; encoder->bits_to_encode = 42; - } else if(message->protocol == IrdaProtocolNEC42ext) { + } else if(message->protocol == InfraredProtocolNEC42ext) { *data1 = message->address & 0x3FFFFFF; *data1 |= ((message->command & 0x3F) << 26); *data2 = (message->command & 0xFFC0) >> 6; @@ -54,8 +55,10 @@ void irda_encoder_nec_reset(void* encoder_ptr, const IrdaMessage* message) { } } -IrdaStatus - irda_encoder_nec_encode_repeat(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level) { +InfraredStatus infrared_encoder_nec_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level) { furi_assert(encoder); /* space + 2 timings preambule + payload + stop bit */ @@ -67,24 +70,24 @@ IrdaStatus if(repeat_cnt > 0) { *duration = repeat_timings[repeat_cnt % COUNT_OF(repeat_timings)]; } else { - *duration = IRDA_NEC_REPEAT_PERIOD - encoder->timings_sum; + *duration = INFRARED_NEC_REPEAT_PERIOD - encoder->timings_sum; } *level = repeat_cnt % 2; ++encoder->timings_encoded; bool done = (!((repeat_cnt + 1) % COUNT_OF(repeat_timings))); - return done ? IrdaStatusDone : IrdaStatusOk; + return done ? InfraredStatusDone : InfraredStatusOk; } -void* irda_encoder_nec_alloc(void) { - return irda_common_encoder_alloc(&protocol_nec); +void* infrared_encoder_nec_alloc(void) { + return infrared_common_encoder_alloc(&protocol_nec); } -void irda_encoder_nec_free(void* encoder_ptr) { - irda_common_encoder_free(encoder_ptr); +void infrared_encoder_nec_free(void* encoder_ptr) { + infrared_common_encoder_free(encoder_ptr); } -IrdaStatus irda_encoder_nec_encode(void* encoder_ptr, uint32_t* duration, bool* level) { - return irda_common_encode(encoder_ptr, duration, level); +InfraredStatus infrared_encoder_nec_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + return infrared_common_encode(encoder_ptr, duration, level); } diff --git a/lib/infrared/encoder_decoder/nec/infrared_nec_spec.c b/lib/infrared/encoder_decoder/nec/infrared_nec_spec.c new file mode 100644 index 00000000..16cab8b5 --- /dev/null +++ b/lib/infrared/encoder_decoder/nec/infrared_nec_spec.c @@ -0,0 +1,47 @@ +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" + +static const InfraredProtocolSpecification infrared_nec_protocol_specification = { + .name = "NEC", + .address_length = 8, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, +}; + +static const InfraredProtocolSpecification infrared_necext_protocol_specification = { + .name = "NECext", + .address_length = 16, + .command_length = 16, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, +}; + +static const InfraredProtocolSpecification infrared_nec42_protocol_specification = { + .name = "NEC42", + .address_length = 13, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, +}; + +static const InfraredProtocolSpecification infrared_nec42ext_protocol_specification = { + .name = "NEC42ext", + .address_length = 26, + .command_length = 16, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, +}; + +const InfraredProtocolSpecification* infrared_nec_get_spec(InfraredProtocol protocol) { + if(protocol == InfraredProtocolNEC) + return &infrared_nec_protocol_specification; + else if(protocol == InfraredProtocolNECext) + return &infrared_necext_protocol_specification; + else if(protocol == InfraredProtocolNEC42) + return &infrared_nec42_protocol_specification; + else if(protocol == InfraredProtocolNEC42ext) + return &infrared_nec42ext_protocol_specification; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c b/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c new file mode 100644 index 00000000..6b4a7c2e --- /dev/null +++ b/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c @@ -0,0 +1,82 @@ +#include "infrared.h" +#include +#include +#include +#include +#include "../infrared_i.h" +#include "../infrared_protocol_defs_i.h" + +typedef struct { + InfraredCommonDecoder* common_decoder; + bool toggle; +} InfraredRc5Decoder; + +InfraredMessage* infrared_decoder_rc5_check_ready(void* ctx) { + InfraredRc5Decoder* decoder = ctx; + return infrared_common_decoder_check_ready(decoder->common_decoder); +} + +bool infrared_decoder_rc5_interpret(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + bool result = false; + uint32_t* data = (void*)&decoder->data[0]; + /* Manchester (inverse): + * 0->1 : 1 + * 1->0 : 0 + */ + decoder->data[0] = ~decoder->data[0]; + decoder->data[1] = ~decoder->data[1]; + + // MSB first + uint8_t address = reverse((uint8_t)decoder->data[0]) & 0x1F; + uint8_t command = (reverse((uint8_t)decoder->data[1]) >> 2) & 0x3F; + bool start_bit1 = *data & 0x01; + bool start_bit2 = *data & 0x02; + bool toggle = !!(*data & 0x04); + + if(start_bit1 == 1) { + InfraredProtocol protocol = start_bit2 ? InfraredProtocolRC5 : InfraredProtocolRC5X; + InfraredMessage* message = &decoder->message; + InfraredRc5Decoder* rc5_decoder = decoder->context; + bool* prev_toggle = &rc5_decoder->toggle; + if((message->address == address) && (message->command == command) && + (message->protocol == protocol)) { + message->repeat = (toggle == *prev_toggle); + } else { + message->repeat = false; + } + *prev_toggle = toggle; + message->command = command; + message->address = address; + message->protocol = protocol; + + result = true; + } + + return result; +} + +void* infrared_decoder_rc5_alloc(void) { + InfraredRc5Decoder* decoder = malloc(sizeof(InfraredRc5Decoder)); + decoder->toggle = false; + decoder->common_decoder = infrared_common_decoder_alloc(&protocol_rc5); + decoder->common_decoder->context = decoder; + return decoder; +} + +InfraredMessage* infrared_decoder_rc5_decode(void* decoder, bool level, uint32_t duration) { + InfraredRc5Decoder* decoder_rc5 = decoder; + return infrared_common_decode(decoder_rc5->common_decoder, level, duration); +} + +void infrared_decoder_rc5_free(void* decoder) { + InfraredRc5Decoder* decoder_rc5 = decoder; + infrared_common_decoder_free(decoder_rc5->common_decoder); + free(decoder_rc5); +} + +void infrared_decoder_rc5_reset(void* decoder) { + InfraredRc5Decoder* decoder_rc5 = decoder; + infrared_common_decoder_reset(decoder_rc5->common_decoder); +} diff --git a/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c b/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c new file mode 100644 index 00000000..f063f7e5 --- /dev/null +++ b/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c @@ -0,0 +1,55 @@ +#include "furi/memmgr.h" +#include "infrared.h" +#include "common/infrared_common_i.h" +#include "infrared_protocol_defs_i.h" +#include +#include "../infrared_i.h" + +typedef struct InfraredEncoderRC5 { + InfraredCommonEncoder* common_encoder; + bool toggle_bit; +} InfraredEncoderRC5; + +void infrared_encoder_rc5_reset(void* encoder_ptr, const InfraredMessage* message) { + furi_assert(encoder_ptr); + + InfraredEncoderRC5* encoder = encoder_ptr; + InfraredCommonEncoder* common_encoder = encoder->common_encoder; + infrared_common_encoder_reset(common_encoder); + + uint32_t* data = (void*)common_encoder->data; + /* RC5 */ + *data |= 0x01; // start bit + if(message->protocol == InfraredProtocolRC5) { + *data |= 0x02; // start bit + } + *data |= encoder->toggle_bit ? 0x04 : 0; + *data |= (reverse(message->address) >> 3) << 3; /* address 5 bit */ + *data |= (reverse(message->command) >> 2) << 8; /* command 6 bit */ + + common_encoder->data[0] = ~common_encoder->data[0]; + common_encoder->data[1] = ~common_encoder->data[1]; + + common_encoder->bits_to_encode = common_encoder->protocol->databit_len[0]; + encoder->toggle_bit ^= 1; +} + +InfraredStatus infrared_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + InfraredEncoderRC5* encoder = encoder_ptr; + return infrared_common_encode(encoder->common_encoder, duration, level); +} + +void* infrared_encoder_rc5_alloc(void) { + InfraredEncoderRC5* encoder = malloc(sizeof(InfraredEncoderRC5)); + encoder->common_encoder = infrared_common_encoder_alloc(&protocol_rc5); + encoder->toggle_bit = false; + return encoder; +} + +void infrared_encoder_rc5_free(void* encoder_ptr) { + furi_assert(encoder_ptr); + + InfraredEncoderRC5* encoder = encoder_ptr; + free(encoder->common_encoder); + free(encoder); +} diff --git a/lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c b/lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c new file mode 100644 index 00000000..25ea230e --- /dev/null +++ b/lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c @@ -0,0 +1,27 @@ +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" + +static const InfraredProtocolSpecification infrared_rc5_protocol_specification = { + .name = "RC5", + .address_length = 5, + .command_length = 6, + .frequency = INFRARED_RC5_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_RC5_DUTY_CYCLE, +}; + +static const InfraredProtocolSpecification infrared_rc5x_protocol_specification = { + .name = "RC5X", + .address_length = 5, + .command_length = 7, + .frequency = INFRARED_RC5_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_RC5_DUTY_CYCLE, +}; + +const InfraredProtocolSpecification* infrared_rc5_get_spec(InfraredProtocol protocol) { + if(protocol == InfraredProtocolRC5) + return &infrared_rc5_protocol_specification; + else if(protocol == InfraredProtocolRC5X) + return &infrared_rc5x_protocol_specification; + else + return NULL; +} diff --git a/lib/irda/encoder_decoder/rc6/irda_decoder_rc6.c b/lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c similarity index 56% rename from lib/irda/encoder_decoder/rc6/irda_decoder_rc6.c rename to lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c index a38d2c77..9101cac1 100644 --- a/lib/irda/encoder_decoder/rc6/irda_decoder_rc6.c +++ b/lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c @@ -1,22 +1,22 @@ -#include "irda.h" +#include "infrared.h" #include #include #include #include -#include "../irda_i.h" -#include "../irda_protocol_defs_i.h" +#include "../infrared_i.h" +#include "../infrared_protocol_defs_i.h" typedef struct { - IrdaCommonDecoder* common_decoder; + InfraredCommonDecoder* common_decoder; bool toggle; -} IrdaRc6Decoder; +} InfraredRc6Decoder; -IrdaMessage* irda_decoder_rc6_check_ready(void* ctx) { - IrdaRc6Decoder* decoder_rc6 = ctx; - return irda_common_decoder_check_ready(decoder_rc6->common_decoder); +InfraredMessage* infrared_decoder_rc6_check_ready(void* ctx) { + InfraredRc6Decoder* decoder_rc6 = ctx; + return infrared_common_decoder_check_ready(decoder_rc6->common_decoder); } -bool irda_decoder_rc6_interpret(IrdaCommonDecoder* decoder) { +bool infrared_decoder_rc6_interpret(InfraredCommonDecoder* decoder) { furi_assert(decoder); bool result = false; @@ -29,11 +29,11 @@ bool irda_decoder_rc6_interpret(IrdaCommonDecoder* decoder) { uint8_t mode = (*data >> 1) & 0x7; if((start_bit == 1) && (mode == 0)) { - IrdaMessage* message = &decoder->message; - IrdaRc6Decoder* rc6_decoder = decoder->context; + InfraredMessage* message = &decoder->message; + InfraredRc6Decoder* rc6_decoder = decoder->context; bool* prev_toggle = &rc6_decoder->toggle; if((message->address == address) && (message->command == command) && - (message->protocol == IrdaProtocolRC6)) { + (message->protocol == InfraredProtocolRC6)) { message->repeat = (toggle == *prev_toggle); } else { message->repeat = false; @@ -41,7 +41,7 @@ bool irda_decoder_rc6_interpret(IrdaCommonDecoder* decoder) { *prev_toggle = toggle; message->command = command; message->address = address; - message->protocol = IrdaProtocolRC6; + message->protocol = InfraredProtocolRC6; result = true; } @@ -54,10 +54,12 @@ bool irda_decoder_rc6_interpret(IrdaCommonDecoder* decoder) { * it separately and than pass decoding for other bits to * common manchester decode function. */ -IrdaStatus - irda_decoder_rc6_decode_manchester(IrdaCommonDecoder* decoder, bool level, uint32_t timing) { +InfraredStatus infrared_decoder_rc6_decode_manchester( + InfraredCommonDecoder* decoder, + bool level, + uint32_t timing) { // 4th bit lasts 2x times more - IrdaStatus status = IrdaStatusError; + InfraredStatus status = InfraredStatusError; uint16_t bit = decoder->protocol->timings.bit1_mark; uint16_t tolerance = decoder->protocol->timings.bit_tolerance; @@ -71,43 +73,43 @@ IrdaStatus if(single_timing ^ triple_timing) { ++decoder->databit_cnt; decoder->data[0] |= (single_timing ? !level : level) << 4; - status = IrdaStatusOk; + status = InfraredStatusOk; } } else if(decoder->databit_cnt == 5) { if(single_timing || triple_timing) { if(triple_timing) timing = bit; decoder->switch_detect = false; - status = irda_common_decode_manchester(decoder, level, timing); + status = infrared_common_decode_manchester(decoder, level, timing); } else if(double_timing) { - status = IrdaStatusOk; + status = InfraredStatusOk; } } else { - status = irda_common_decode_manchester(decoder, level, timing); + status = infrared_common_decode_manchester(decoder, level, timing); } return status; } -void* irda_decoder_rc6_alloc(void) { - IrdaRc6Decoder* decoder = malloc(sizeof(IrdaRc6Decoder)); +void* infrared_decoder_rc6_alloc(void) { + InfraredRc6Decoder* decoder = malloc(sizeof(InfraredRc6Decoder)); decoder->toggle = false; - decoder->common_decoder = irda_common_decoder_alloc(&protocol_rc6); + decoder->common_decoder = infrared_common_decoder_alloc(&protocol_rc6); decoder->common_decoder->context = decoder; return decoder; } -IrdaMessage* irda_decoder_rc6_decode(void* decoder, bool level, uint32_t duration) { - IrdaRc6Decoder* decoder_rc6 = decoder; - return irda_common_decode(decoder_rc6->common_decoder, level, duration); +InfraredMessage* infrared_decoder_rc6_decode(void* decoder, bool level, uint32_t duration) { + InfraredRc6Decoder* decoder_rc6 = decoder; + return infrared_common_decode(decoder_rc6->common_decoder, level, duration); } -void irda_decoder_rc6_free(void* decoder) { - IrdaRc6Decoder* decoder_rc6 = decoder; - irda_common_decoder_free(decoder_rc6->common_decoder); +void infrared_decoder_rc6_free(void* decoder) { + InfraredRc6Decoder* decoder_rc6 = decoder; + infrared_common_decoder_free(decoder_rc6->common_decoder); free(decoder_rc6); } -void irda_decoder_rc6_reset(void* decoder) { - IrdaRc6Decoder* decoder_rc6 = decoder; - irda_common_decoder_reset(decoder_rc6->common_decoder); +void infrared_decoder_rc6_reset(void* decoder) { + InfraredRc6Decoder* decoder_rc6 = decoder; + infrared_common_decoder_reset(decoder_rc6->common_decoder); } diff --git a/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c b/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c new file mode 100644 index 00000000..7d8ff975 --- /dev/null +++ b/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c @@ -0,0 +1,61 @@ +#include "furi/memmgr.h" +#include "infrared.h" +#include "common/infrared_common_i.h" +#include "infrared_protocol_defs_i.h" +#include +#include "../infrared_i.h" + +typedef struct InfraredEncoderRC6 { + InfraredCommonEncoder* common_encoder; + bool toggle_bit; +} InfraredEncoderRC6; + +void infrared_encoder_rc6_reset(void* encoder_ptr, const InfraredMessage* message) { + furi_assert(encoder_ptr); + + InfraredEncoderRC6* encoder = encoder_ptr; + InfraredCommonEncoder* common_encoder = encoder->common_encoder; + infrared_common_encoder_reset(common_encoder); + + uint32_t* data = (void*)common_encoder->data; + *data |= 0x01; // start bit + (void)*data; // 3 bits for mode == 0 + *data |= encoder->toggle_bit ? 0x10 : 0; + *data |= reverse(message->address) << 5; + *data |= reverse(message->command) << 13; + + common_encoder->bits_to_encode = common_encoder->protocol->databit_len[0]; + encoder->toggle_bit ^= 1; +} + +InfraredStatus infrared_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + InfraredEncoderRC6* encoder = encoder_ptr; + return infrared_common_encode(encoder->common_encoder, duration, level); +} + +void* infrared_encoder_rc6_alloc(void) { + InfraredEncoderRC6* encoder = malloc(sizeof(InfraredEncoderRC6)); + encoder->common_encoder = infrared_common_encoder_alloc(&protocol_rc6); + encoder->toggle_bit = false; + return encoder; +} + +void infrared_encoder_rc6_free(void* encoder_ptr) { + furi_assert(encoder_ptr); + + InfraredEncoderRC6* encoder = encoder_ptr; + free(encoder->common_encoder); + free(encoder); +} + +InfraredStatus infrared_encoder_rc6_encode_manchester( + InfraredCommonEncoder* common_encoder, + uint32_t* duration, + bool* polarity) { + InfraredStatus status = InfraredStatusError; + + bool toggle_bit = (common_encoder->bits_encoded == 4); + status = infrared_common_encode_manchester(common_encoder, duration, polarity); + if(toggle_bit) *duration *= 2; + return status; +} diff --git a/lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c b/lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c new file mode 100644 index 00000000..9e0ba746 --- /dev/null +++ b/lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c @@ -0,0 +1,17 @@ +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" + +static const InfraredProtocolSpecification infrared_rc6_protocol_specification = { + .name = "RC6", + .address_length = 8, + .command_length = 8, + .frequency = INFRARED_RC6_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_RC6_DUTY_CYCLE, +}; + +const InfraredProtocolSpecification* infrared_rc6_get_spec(InfraredProtocol protocol) { + if(protocol == InfraredProtocolRC6) + return &infrared_rc6_protocol_specification; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c b/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c new file mode 100644 index 00000000..4450ffd1 --- /dev/null +++ b/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c @@ -0,0 +1,72 @@ +#include "infrared.h" +#include "infrared_protocol_defs_i.h" +#include +#include +#include +#include "../infrared_i.h" + +InfraredMessage* infrared_decoder_samsung32_check_ready(void* ctx) { + return infrared_common_decoder_check_ready(ctx); +} + +bool infrared_decoder_samsung32_interpret(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + bool result = false; + uint8_t address1 = decoder->data[0]; + uint8_t address2 = decoder->data[1]; + uint8_t command = decoder->data[2]; + uint8_t command_inverse = decoder->data[3]; + + if((address1 == address2) && (command == (uint8_t)~command_inverse)) { + decoder->message.command = command; + decoder->message.address = address1; + decoder->message.protocol = InfraredProtocolSamsung32; + decoder->message.repeat = false; + result = true; + } + + return result; +} + +// timings start from Space (delay between message and repeat) +InfraredStatus infrared_decoder_samsung32_decode_repeat(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + float preamble_tolerance = decoder->protocol->timings.preamble_tolerance; + uint32_t bit_tolerance = decoder->protocol->timings.bit_tolerance; + InfraredStatus status = InfraredStatusError; + + if(decoder->timings_cnt < 6) return InfraredStatusOk; + + if((decoder->timings[0] > INFRARED_SAMSUNG_REPEAT_PAUSE_MIN) && + (decoder->timings[0] < INFRARED_SAMSUNG_REPEAT_PAUSE_MAX) && + MATCH_TIMING(decoder->timings[1], INFRARED_SAMSUNG_REPEAT_MARK, preamble_tolerance) && + MATCH_TIMING(decoder->timings[2], INFRARED_SAMSUNG_REPEAT_SPACE, preamble_tolerance) && + MATCH_TIMING(decoder->timings[3], decoder->protocol->timings.bit1_mark, bit_tolerance) && + MATCH_TIMING(decoder->timings[4], decoder->protocol->timings.bit1_space, bit_tolerance) && + MATCH_TIMING(decoder->timings[5], decoder->protocol->timings.bit1_mark, bit_tolerance)) { + status = InfraredStatusReady; + decoder->timings_cnt = 0; + } else { + status = InfraredStatusError; + } + + return status; +} + +void* infrared_decoder_samsung32_alloc(void) { + return infrared_common_decoder_alloc(&protocol_samsung32); +} + +InfraredMessage* infrared_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration) { + return infrared_common_decode(decoder, level, duration); +} + +void infrared_decoder_samsung32_free(void* decoder) { + infrared_common_decoder_free(decoder); +} + +void infrared_decoder_samsung32_reset(void* decoder) { + infrared_common_decoder_reset(decoder); +} diff --git a/lib/irda/encoder_decoder/samsung/irda_encoder_samsung.c b/lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c similarity index 50% rename from lib/irda/encoder_decoder/samsung/irda_encoder_samsung.c rename to lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c index 522db8a2..88745ff5 100644 --- a/lib/irda/encoder_decoder/samsung/irda_encoder_samsung.c +++ b/lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c @@ -1,24 +1,24 @@ #include "furi/check.h" -#include "common/irda_common_i.h" +#include "common/infrared_common_i.h" #include -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" #include static const uint32_t repeat_timings[] = { - IRDA_SAMSUNG_REPEAT_PAUSE2, - IRDA_SAMSUNG_REPEAT_MARK, - IRDA_SAMSUNG_REPEAT_SPACE, - IRDA_SAMSUNG_BIT1_MARK, - IRDA_SAMSUNG_BIT1_SPACE, - IRDA_SAMSUNG_BIT1_MARK, + INFRARED_SAMSUNG_REPEAT_PAUSE2, + INFRARED_SAMSUNG_REPEAT_MARK, + INFRARED_SAMSUNG_REPEAT_SPACE, + INFRARED_SAMSUNG_BIT1_MARK, + INFRARED_SAMSUNG_BIT1_SPACE, + INFRARED_SAMSUNG_BIT1_MARK, }; -void irda_encoder_samsung32_reset(void* encoder_ptr, const IrdaMessage* message) { +void infrared_encoder_samsung32_reset(void* encoder_ptr, const InfraredMessage* message) { furi_assert(encoder_ptr); - IrdaCommonEncoder* encoder = encoder_ptr; - irda_common_encoder_reset(encoder); + InfraredCommonEncoder* encoder = encoder_ptr; + infrared_common_encoder_reset(encoder); uint8_t address = message->address; uint8_t command = message->command; @@ -33,8 +33,8 @@ void irda_encoder_samsung32_reset(void* encoder_ptr, const IrdaMessage* message) encoder->bits_to_encode = encoder->protocol->databit_len[0]; } -IrdaStatus irda_encoder_samsung32_encode_repeat( - IrdaCommonEncoder* encoder, +InfraredStatus infrared_encoder_samsung32_encode_repeat( + InfraredCommonEncoder* encoder, uint32_t* duration, bool* level) { furi_assert(encoder); @@ -48,23 +48,24 @@ IrdaStatus irda_encoder_samsung32_encode_repeat( if(repeat_cnt > 0) *duration = repeat_timings[repeat_cnt % COUNT_OF(repeat_timings)]; else - *duration = IRDA_SAMSUNG_REPEAT_PAUSE1; + *duration = INFRARED_SAMSUNG_REPEAT_PAUSE1; *level = repeat_cnt % 2; ++encoder->timings_encoded; bool done = (!((repeat_cnt + 1) % COUNT_OF(repeat_timings))); - return done ? IrdaStatusDone : IrdaStatusOk; + return done ? InfraredStatusDone : InfraredStatusOk; } -void* irda_encoder_samsung32_alloc(void) { - return irda_common_encoder_alloc(&protocol_samsung32); +void* infrared_encoder_samsung32_alloc(void) { + return infrared_common_encoder_alloc(&protocol_samsung32); } -void irda_encoder_samsung32_free(void* encoder_ptr) { - irda_common_encoder_free(encoder_ptr); +void infrared_encoder_samsung32_free(void* encoder_ptr) { + infrared_common_encoder_free(encoder_ptr); } -IrdaStatus irda_encoder_samsung32_encode(void* encoder_ptr, uint32_t* duration, bool* level) { - return irda_common_encode(encoder_ptr, duration, level); +InfraredStatus + infrared_encoder_samsung32_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + return infrared_common_encode(encoder_ptr, duration, level); } diff --git a/lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c b/lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c new file mode 100644 index 00000000..f4cbf699 --- /dev/null +++ b/lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c @@ -0,0 +1,17 @@ +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" + +static const InfraredProtocolSpecification infrared_samsung32_protocol_specification = { + .name = "Samsung32", + .address_length = 8, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, +}; + +const InfraredProtocolSpecification* infrared_samsung32_get_spec(InfraredProtocol protocol) { + if(protocol == InfraredProtocolSamsung32) + return &infrared_samsung32_protocol_specification; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c new file mode 100644 index 00000000..45f06c94 --- /dev/null +++ b/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c @@ -0,0 +1,60 @@ +#include "common/infrared_common_i.h" +#include "infrared.h" +#include "infrared_protocol_defs_i.h" +#include +#include +#include +#include "../infrared_i.h" + +InfraredMessage* infrared_decoder_sirc_check_ready(void* ctx) { + return infrared_common_decoder_check_ready(ctx); +} + +bool infrared_decoder_sirc_interpret(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + uint32_t* data = (void*)&decoder->data[0]; + uint16_t address = 0; + uint8_t command = 0; + InfraredProtocol protocol = InfraredProtocolUnknown; + + if(decoder->databit_cnt == 12) { + address = (*data >> 7) & 0x1F; + command = *data & 0x7F; + protocol = InfraredProtocolSIRC; + } else if(decoder->databit_cnt == 15) { + address = (*data >> 7) & 0xFF; + command = *data & 0x7F; + protocol = InfraredProtocolSIRC15; + } else if(decoder->databit_cnt == 20) { + address = (*data >> 7) & 0x1FFF; + command = *data & 0x7F; + protocol = InfraredProtocolSIRC20; + } else { + return false; + } + + decoder->message.protocol = protocol; + decoder->message.address = address; + decoder->message.command = command; + /* SIRC doesn't specify repeat detection */ + decoder->message.repeat = false; + + return true; +} + +void* infrared_decoder_sirc_alloc(void) { + return infrared_common_decoder_alloc(&protocol_sirc); +} + +InfraredMessage* infrared_decoder_sirc_decode(void* decoder, bool level, uint32_t duration) { + return infrared_common_decode(decoder, level, duration); +} + +void infrared_decoder_sirc_free(void* decoder) { + infrared_common_decoder_free(decoder); +} + +void infrared_decoder_sirc_reset(void* decoder) { + infrared_common_decoder_reset(decoder); +} diff --git a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c new file mode 100644 index 00000000..a6918cee --- /dev/null +++ b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c @@ -0,0 +1,73 @@ +#include "furi/check.h" +#include "infrared.h" +#include "common/infrared_common_i.h" +#include +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" +#include + +void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* message) { + furi_assert(encoder_ptr); + furi_assert(message); + + InfraredCommonEncoder* encoder = encoder_ptr; + infrared_common_encoder_reset(encoder); + + uint32_t* data = (void*)encoder->data; + + if(message->protocol == InfraredProtocolSIRC) { + *data = (message->command & 0x7F); + *data |= (message->address & 0x1F) << 7; + encoder->bits_to_encode = 12; + } else if(message->protocol == InfraredProtocolSIRC15) { + *data = (message->command & 0x7F); + *data |= (message->address & 0xFF) << 7; + encoder->bits_to_encode = 15; + } else if(message->protocol == InfraredProtocolSIRC20) { + *data = (message->command & 0x7F); + *data |= (message->address & 0x1FFF) << 7; + encoder->bits_to_encode = 20; + } else { + furi_assert(0); + } +} + +InfraredStatus infrared_encoder_sirc_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level) { + furi_assert(encoder); + + furi_assert(encoder->timings_encoded == (1 + 2 + encoder->bits_to_encode * 2 - 1)); + + furi_assert(encoder->timings_sum < INFRARED_SIRC_REPEAT_PERIOD); + *duration = INFRARED_SIRC_REPEAT_PERIOD - encoder->timings_sum; + *level = false; + + encoder->timings_sum = 0; + encoder->timings_encoded = 1; + encoder->bits_encoded = 0; + encoder->state = InfraredCommonEncoderStatePreamble; + + return InfraredStatusOk; +} + +void* infrared_encoder_sirc_alloc(void) { + return infrared_common_encoder_alloc(&protocol_sirc); +} + +void infrared_encoder_sirc_free(void* encoder_ptr) { + infrared_common_encoder_free(encoder_ptr); +} + +InfraredStatus infrared_encoder_sirc_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + InfraredCommonEncoder* encoder = encoder_ptr; + + InfraredStatus status = infrared_common_encode(encoder, duration, level); + if((status == InfraredStatusOk) && (encoder->bits_encoded == encoder->bits_to_encode)) { + furi_assert(!*level); + status = InfraredStatusDone; + encoder->state = InfraredCommonEncoderStateEncodeRepeat; + } + return status; +} diff --git a/lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c b/lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c new file mode 100644 index 00000000..9bf35908 --- /dev/null +++ b/lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c @@ -0,0 +1,37 @@ +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" + +static const InfraredProtocolSpecification infrared_sirc_protocol_specification = { + .name = "SIRC", + .address_length = 5, + .command_length = 7, + .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, +}; + +static const InfraredProtocolSpecification infrared_sirc15_protocol_specification = { + .name = "SIRC15", + .address_length = 8, + .command_length = 7, + .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, +}; + +static const InfraredProtocolSpecification infrared_sirc20_protocol_specification = { + .name = "SIRC20", + .address_length = 13, + .command_length = 7, + .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, +}; + +const InfraredProtocolSpecification* infrared_sirc_get_spec(InfraredProtocol protocol) { + if(protocol == InfraredProtocolSIRC) + return &infrared_sirc_protocol_specification; + else if(protocol == InfraredProtocolSIRC15) + return &infrared_sirc15_protocol_specification; + else if(protocol == InfraredProtocolSIRC20) + return &infrared_sirc20_protocol_specification; + else + return NULL; +} diff --git a/lib/infrared/worker/infrared_transmit.c b/lib/infrared/worker/infrared_transmit.c new file mode 100644 index 00000000..d4c87ce5 --- /dev/null +++ b/lib/infrared/worker/infrared_transmit.c @@ -0,0 +1,116 @@ +#include "infrared.h" +#include +#include +#include +#include +#include +#include + +static uint32_t infrared_tx_number_of_transmissions = 0; +static uint32_t infrared_tx_raw_timings_index = 0; +static uint32_t infrared_tx_raw_timings_number = 0; +static uint32_t infrared_tx_raw_start_from_mark = 0; +static bool infrared_tx_raw_add_silence = false; + +FuriHalInfraredTxGetDataState + infrared_get_raw_data_callback(void* context, uint32_t* duration, bool* level) { + furi_assert(duration); + furi_assert(level); + furi_assert(context); + + FuriHalInfraredTxGetDataState state = FuriHalInfraredTxGetDataStateOk; + const uint32_t* timings = context; + + if(infrared_tx_raw_add_silence && (infrared_tx_raw_timings_index == 0)) { + infrared_tx_raw_add_silence = false; + *level = false; + *duration = INFRARED_RAW_TX_TIMING_DELAY_US; + } else { + *level = infrared_tx_raw_start_from_mark ^ (infrared_tx_raw_timings_index % 2); + *duration = timings[infrared_tx_raw_timings_index++]; + } + + if(infrared_tx_raw_timings_number == infrared_tx_raw_timings_index) { + state = FuriHalInfraredTxGetDataStateLastDone; + } + + return state; +} + +void infrared_send_raw_ext( + const uint32_t timings[], + uint32_t timings_cnt, + bool start_from_mark, + uint32_t frequency, + float duty_cycle) { + furi_assert(timings); + + infrared_tx_raw_start_from_mark = start_from_mark; + infrared_tx_raw_timings_index = 0; + infrared_tx_raw_timings_number = timings_cnt; + infrared_tx_raw_add_silence = start_from_mark; + furi_hal_infrared_async_tx_set_data_isr_callback( + infrared_get_raw_data_callback, (void*)timings); + furi_hal_infrared_async_tx_start(frequency, duty_cycle); + furi_hal_infrared_async_tx_wait_termination(); + + furi_assert(!furi_hal_infrared_is_busy()); +} + +void infrared_send_raw(const uint32_t timings[], uint32_t timings_cnt, bool start_from_mark) { + infrared_send_raw_ext( + timings, + timings_cnt, + start_from_mark, + INFRARED_COMMON_CARRIER_FREQUENCY, + INFRARED_COMMON_DUTY_CYCLE); +} + +FuriHalInfraredTxGetDataState + infrared_get_data_callback(void* context, uint32_t* duration, bool* level) { + FuriHalInfraredTxGetDataState state = FuriHalInfraredTxGetDataStateLastDone; + InfraredEncoderHandler* handler = context; + InfraredStatus status = InfraredStatusError; + + if(infrared_tx_number_of_transmissions > 0) { + status = infrared_encode(handler, duration, level); + } + + if(status == InfraredStatusError) { + state = FuriHalInfraredTxGetDataStateLastDone; + *duration = 0; + *level = 0; + } else if(status == InfraredStatusOk) { + state = FuriHalInfraredTxGetDataStateOk; + } else if(status == InfraredStatusDone) { + state = FuriHalInfraredTxGetDataStateDone; + if(--infrared_tx_number_of_transmissions == 0) { + state = FuriHalInfraredTxGetDataStateLastDone; + } + } else { + furi_crash(NULL); + } + + return state; +} + +void infrared_send(const InfraredMessage* message, int times) { + furi_assert(message); + furi_assert(times); + furi_assert(infrared_is_protocol_valid(message->protocol)); + + InfraredEncoderHandler* handler = infrared_alloc_encoder(); + infrared_reset_encoder(handler, message); + infrared_tx_number_of_transmissions = times; + + uint32_t frequency = infrared_get_protocol_frequency(message->protocol); + float duty_cycle = infrared_get_protocol_duty_cycle(message->protocol); + + furi_hal_infrared_async_tx_set_data_isr_callback(infrared_get_data_callback, handler); + furi_hal_infrared_async_tx_start(frequency, duty_cycle); + furi_hal_infrared_async_tx_wait_termination(); + + infrared_free_encoder(handler); + + furi_assert(!furi_hal_infrared_is_busy()); +} diff --git a/lib/irda/worker/irda_transmit.h b/lib/infrared/worker/infrared_transmit.h similarity index 78% rename from lib/irda/worker/irda_transmit.h rename to lib/infrared/worker/infrared_transmit.h index 6eaa0d0c..27e6a7df 100644 --- a/lib/irda/worker/irda_transmit.h +++ b/lib/infrared/worker/infrared_transmit.h @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #ifdef __cplusplus @@ -7,12 +7,12 @@ extern "C" { #endif /** - * Send message over IRDA. + * Send message over INFRARED. * * \param[in] message - message to send. * \param[in] times - number of times message should be sent. */ -void irda_send(const IrdaMessage* message, int times); +void infrared_send(const InfraredMessage* message, int times); /** * Send raw data through infrared port. @@ -22,7 +22,7 @@ void irda_send(const IrdaMessage* message, int times); * \param[in] start_from_mark - true if timings starts from mark, * otherwise from space */ -void irda_send_raw(const uint32_t timings[], uint32_t timings_cnt, bool start_from_mark); +void infrared_send_raw(const uint32_t timings[], uint32_t timings_cnt, bool start_from_mark); /** * Send raw data through infrared port, with additional settings. @@ -34,7 +34,7 @@ void irda_send_raw(const uint32_t timings[], uint32_t timings_cnt, bool start_fr * \param[in] duty_cycle - duty cycle to generate on PWM * \param[in] frequency - frequency to generate on PWM */ -void irda_send_raw_ext( +void infrared_send_raw_ext( const uint32_t timings[], uint32_t timings_cnt, bool start_from_mark, diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c new file mode 100644 index 00000000..86a88367 --- /dev/null +++ b/lib/infrared/worker/infrared_worker.c @@ -0,0 +1,608 @@ +#include "furi/check.h" +#include "furi/common_defines.h" +#include "sys/_stdint.h" +#include "infrared_worker.h" +#include +#include +#include +#include +#include + +#include +#include + +#define INFRARED_WORKER_RX_TIMEOUT INFRARED_RAW_RX_TIMING_DELAY_US + +#define INFRARED_WORKER_RX_RECEIVED 0x01 +#define INFRARED_WORKER_RX_TIMEOUT_RECEIVED 0x02 +#define INFRARED_WORKER_OVERRUN 0x04 +#define INFRARED_WORKER_EXIT 0x08 +#define INFRARED_WORKER_TX_FILL_BUFFER 0x10 +#define INFRARED_WORKER_TX_MESSAGE_SENT 0x20 + +#define INFRARED_WORKER_ALL_RX_EVENTS \ + (INFRARED_WORKER_RX_RECEIVED | INFRARED_WORKER_RX_TIMEOUT_RECEIVED | \ + INFRARED_WORKER_OVERRUN | INFRARED_WORKER_EXIT) + +#define INFRARED_WORKER_ALL_TX_EVENTS \ + (INFRARED_WORKER_TX_FILL_BUFFER | INFRARED_WORKER_TX_MESSAGE_SENT | INFRARED_WORKER_EXIT) + +#define INFRARED_WORKER_ALL_EVENTS (INFRARED_WORKER_ALL_RX_EVENTS | INFRARED_WORKER_ALL_TX_EVENTS) + +typedef enum { + InfraredWorkerStateIdle, + InfraredWorkerStateRunRx, + InfraredWorkerStateRunTx, + InfraredWorkerStateWaitTxEnd, + InfraredWorkerStateStopTx, + InfraredWorkerStateStartTx, +} InfraredWorkerState; + +struct InfraredWorkerSignal { + bool decoded; + size_t timings_cnt; + union { + InfraredMessage message; + /* +1 is for pause we add at the beginning */ + uint32_t timings[MAX_TIMINGS_AMOUNT + 1]; + }; +}; + +struct InfraredWorker { + FuriThread* thread; + StreamBufferHandle_t stream; + osEventFlagsId_t events; + + InfraredWorkerSignal signal; + InfraredWorkerState state; + InfraredEncoderHandler* infrared_encoder; + InfraredDecoderHandler* infrared_decoder; + NotificationApp* notification; + bool blink_enable; + + union { + struct { + InfraredWorkerGetSignalCallback get_signal_callback; + InfraredWorkerMessageSentCallback message_sent_callback; + void* get_signal_context; + void* message_sent_context; + uint32_t frequency; + float duty_cycle; + uint32_t tx_raw_cnt; + bool need_reinitialization; + bool steady_signal_sent; + } tx; + struct { + InfraredWorkerReceivedSignalCallback received_signal_callback; + void* received_signal_context; + bool overrun; + } rx; + }; +}; + +typedef struct { + uint32_t duration; + bool level; + FuriHalInfraredTxGetDataState state; +} InfraredWorkerTiming; + +static int32_t infrared_worker_tx_thread(void* context); +static FuriHalInfraredTxGetDataState + infrared_worker_furi_hal_data_isr_callback(void* context, uint32_t* duration, bool* level); +static void infrared_worker_furi_hal_message_sent_isr_callback(void* context); + +static void infrared_worker_rx_timeout_callback(void* context) { + InfraredWorker* instance = context; + uint32_t flags_set = osEventFlagsSet(instance->events, INFRARED_WORKER_RX_TIMEOUT_RECEIVED); + furi_check(flags_set & INFRARED_WORKER_RX_TIMEOUT_RECEIVED); +} + +static void infrared_worker_rx_callback(void* context, bool level, uint32_t duration) { + InfraredWorker* instance = context; + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + furi_assert(duration != 0); + LevelDuration level_duration = level_duration_make(level, duration); + + size_t ret = xStreamBufferSendFromISR( + instance->stream, &level_duration, sizeof(LevelDuration), &xHigherPriorityTaskWoken); + uint32_t events = (ret == sizeof(LevelDuration)) ? INFRARED_WORKER_RX_RECEIVED : + INFRARED_WORKER_OVERRUN; + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + + uint32_t flags_set = osEventFlagsSet(instance->events, events); + furi_check(flags_set & events); +} + +static void infrared_worker_process_timeout(InfraredWorker* instance) { + if(instance->signal.timings_cnt < 2) return; + + const InfraredMessage* message_decoded = + infrared_check_decoder_ready(instance->infrared_decoder); + if(message_decoded) { + instance->signal.message = *message_decoded; + instance->signal.timings_cnt = 0; + instance->signal.decoded = true; + } else { + instance->signal.decoded = false; + } + if(instance->rx.received_signal_callback) + instance->rx.received_signal_callback( + instance->rx.received_signal_context, &instance->signal); +} + +static void + infrared_worker_process_timings(InfraredWorker* instance, uint32_t duration, bool level) { + const InfraredMessage* message_decoded = + infrared_decode(instance->infrared_decoder, level, duration); + if(message_decoded) { + instance->signal.message = *message_decoded; + instance->signal.timings_cnt = 0; + instance->signal.decoded = true; + if(instance->rx.received_signal_callback) + instance->rx.received_signal_callback( + instance->rx.received_signal_context, &instance->signal); + } else { + /* Skip first timing if it starts from Space */ + if((instance->signal.timings_cnt == 0) && !level) { + return; + } + + if(instance->signal.timings_cnt < MAX_TIMINGS_AMOUNT) { + instance->signal.timings[instance->signal.timings_cnt] = duration; + ++instance->signal.timings_cnt; + } else { + uint32_t flags_set = osEventFlagsSet(instance->events, INFRARED_WORKER_OVERRUN); + furi_check(flags_set & INFRARED_WORKER_OVERRUN); + instance->rx.overrun = true; + } + } +} + +static int32_t infrared_worker_rx_thread(void* thread_context) { + InfraredWorker* instance = thread_context; + uint32_t events = 0; + LevelDuration level_duration; + TickType_t last_blink_time = 0; + + while(1) { + events = + osEventFlagsWait(instance->events, INFRARED_WORKER_ALL_RX_EVENTS, 0, osWaitForever); + furi_check(events & INFRARED_WORKER_ALL_RX_EVENTS); /* at least one caught */ + + if(events & INFRARED_WORKER_RX_RECEIVED) { + if(!instance->rx.overrun && instance->blink_enable && + ((xTaskGetTickCount() - last_blink_time) > 80)) { + last_blink_time = xTaskGetTickCount(); + notification_message(instance->notification, &sequence_blink_blue_10); + } + if(instance->signal.timings_cnt == 0) + notification_message(instance->notification, &sequence_display_on); + while(sizeof(LevelDuration) == + xStreamBufferReceive( + instance->stream, &level_duration, sizeof(LevelDuration), 0)) { + if(!instance->rx.overrun) { + bool level = level_duration_get_level(level_duration); + uint32_t duration = level_duration_get_duration(level_duration); + infrared_worker_process_timings(instance, duration, level); + } + } + } + if(events & INFRARED_WORKER_OVERRUN) { + printf("#"); + infrared_reset_decoder(instance->infrared_decoder); + instance->signal.timings_cnt = 0; + if(instance->blink_enable) + notification_message(instance->notification, &sequence_set_red_255); + } + if(events & INFRARED_WORKER_RX_TIMEOUT_RECEIVED) { + if(instance->rx.overrun) { + printf("\nOVERRUN, max samples: %d\n", MAX_TIMINGS_AMOUNT); + instance->rx.overrun = false; + if(instance->blink_enable) + notification_message(instance->notification, &sequence_reset_red); + } else { + infrared_worker_process_timeout(instance); + } + instance->signal.timings_cnt = 0; + } + if(events & INFRARED_WORKER_EXIT) break; + } + + return 0; +} + +void infrared_worker_rx_set_received_signal_callback( + InfraredWorker* instance, + InfraredWorkerReceivedSignalCallback callback, + void* context) { + furi_assert(instance); + instance->rx.received_signal_callback = callback; + instance->rx.received_signal_context = context; +} + +InfraredWorker* infrared_worker_alloc() { + InfraredWorker* instance = malloc(sizeof(InfraredWorker)); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "InfraredWorker"); + furi_thread_set_stack_size(instance->thread, 2048); + furi_thread_set_context(instance->thread, instance); + + size_t buffer_size = + MAX(sizeof(InfraredWorkerTiming) * (MAX_TIMINGS_AMOUNT + 1), + sizeof(LevelDuration) * MAX_TIMINGS_AMOUNT); + instance->stream = xStreamBufferCreate(buffer_size, sizeof(InfraredWorkerTiming)); + instance->infrared_decoder = infrared_alloc_decoder(); + instance->infrared_encoder = infrared_alloc_encoder(); + instance->blink_enable = false; + instance->notification = furi_record_open("notification"); + instance->state = InfraredWorkerStateIdle; + instance->events = osEventFlagsNew(NULL); + + return instance; +} + +void infrared_worker_free(InfraredWorker* instance) { + furi_assert(instance); + furi_assert(instance->state == InfraredWorkerStateIdle); + + furi_record_close("notification"); + infrared_free_decoder(instance->infrared_decoder); + infrared_free_encoder(instance->infrared_encoder); + vStreamBufferDelete(instance->stream); + furi_thread_free(instance->thread); + osEventFlagsDelete(instance->events); + + free(instance); +} + +void infrared_worker_rx_start(InfraredWorker* instance) { + furi_assert(instance); + furi_assert(instance->state == InfraredWorkerStateIdle); + + xStreamBufferSetTriggerLevel(instance->stream, sizeof(LevelDuration)); + + osEventFlagsClear(instance->events, INFRARED_WORKER_ALL_EVENTS); + furi_thread_set_callback(instance->thread, infrared_worker_rx_thread); + furi_thread_start(instance->thread); + + furi_hal_infrared_async_rx_set_capture_isr_callback(infrared_worker_rx_callback, instance); + furi_hal_infrared_async_rx_set_timeout_isr_callback( + infrared_worker_rx_timeout_callback, instance); + furi_hal_infrared_async_rx_start(); + furi_hal_infrared_async_rx_set_timeout(INFRARED_WORKER_RX_TIMEOUT); + + instance->rx.overrun = false; + instance->state = InfraredWorkerStateRunRx; +} + +void infrared_worker_rx_stop(InfraredWorker* instance) { + furi_assert(instance); + furi_assert(instance->state == InfraredWorkerStateRunRx); + + furi_hal_infrared_async_rx_set_timeout_isr_callback(NULL, NULL); + furi_hal_infrared_async_rx_set_capture_isr_callback(NULL, NULL); + furi_hal_infrared_async_rx_stop(); + + osEventFlagsSet(instance->events, INFRARED_WORKER_EXIT); + furi_thread_join(instance->thread); + + BaseType_t xReturn = xStreamBufferReset(instance->stream); + furi_assert(xReturn == pdPASS); + (void)xReturn; + + instance->state = InfraredWorkerStateIdle; +} + +bool infrared_worker_signal_is_decoded(const InfraredWorkerSignal* signal) { + furi_assert(signal); + return signal->decoded; +} + +void infrared_worker_get_raw_signal( + const InfraredWorkerSignal* signal, + const uint32_t** timings, + size_t* timings_cnt) { + furi_assert(signal); + furi_assert(timings); + furi_assert(timings_cnt); + + *timings = signal->timings; + *timings_cnt = signal->timings_cnt; +} + +const InfraredMessage* infrared_worker_get_decoded_signal(const InfraredWorkerSignal* signal) { + furi_assert(signal); + return &signal->message; +} + +void infrared_worker_rx_enable_blink_on_receiving(InfraredWorker* instance, bool enable) { + furi_assert(instance); + instance->blink_enable = enable; +} + +void infrared_worker_tx_start(InfraredWorker* instance) { + furi_assert(instance); + furi_assert(instance->state == InfraredWorkerStateIdle); + furi_assert(instance->tx.get_signal_callback); + + // size have to be greater than api hal infrared async tx buffer size + xStreamBufferSetTriggerLevel(instance->stream, sizeof(InfraredWorkerTiming)); + + osEventFlagsClear(instance->events, INFRARED_WORKER_ALL_EVENTS); + furi_thread_set_callback(instance->thread, infrared_worker_tx_thread); + + instance->tx.steady_signal_sent = false; + instance->tx.need_reinitialization = false; + furi_hal_infrared_async_tx_set_data_isr_callback( + infrared_worker_furi_hal_data_isr_callback, instance); + furi_hal_infrared_async_tx_set_signal_sent_isr_callback( + infrared_worker_furi_hal_message_sent_isr_callback, instance); + + instance->state = InfraredWorkerStateStartTx; + furi_thread_start(instance->thread); +} + +static void infrared_worker_furi_hal_message_sent_isr_callback(void* context) { + InfraredWorker* instance = context; + uint32_t flags_set = osEventFlagsSet(instance->events, INFRARED_WORKER_TX_MESSAGE_SENT); + furi_check(flags_set & INFRARED_WORKER_TX_MESSAGE_SENT); +} + +static FuriHalInfraredTxGetDataState + infrared_worker_furi_hal_data_isr_callback(void* context, uint32_t* duration, bool* level) { + furi_assert(context); + furi_assert(duration); + furi_assert(level); + + InfraredWorker* instance = context; + InfraredWorkerTiming timing; + FuriHalInfraredTxGetDataState state; + + if(sizeof(InfraredWorkerTiming) == + xStreamBufferReceiveFromISR(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0)) { + *level = timing.level; + *duration = timing.duration; + state = timing.state; + } else { + furi_assert(0); + *level = 0; + *duration = 100; + state = FuriHalInfraredTxGetDataStateDone; + } + + uint32_t flags_set = osEventFlagsSet(instance->events, INFRARED_WORKER_TX_FILL_BUFFER); + furi_check(flags_set & INFRARED_WORKER_TX_FILL_BUFFER); + + return state; +} + +static bool infrared_get_new_signal(InfraredWorker* instance) { + bool new_signal_obtained = false; + + InfraredWorkerGetSignalResponse response = + instance->tx.get_signal_callback(instance->tx.get_signal_context, instance); + if(response == InfraredWorkerGetSignalResponseNew) { + uint32_t new_tx_frequency = 0; + float new_tx_duty_cycle = 0; + if(instance->signal.decoded) { + new_tx_frequency = infrared_get_protocol_frequency(instance->signal.message.protocol); + new_tx_duty_cycle = + infrared_get_protocol_duty_cycle(instance->signal.message.protocol); + } else { + furi_assert(instance->signal.timings_cnt > 1); + new_tx_frequency = INFRARED_COMMON_CARRIER_FREQUENCY; + new_tx_duty_cycle = INFRARED_COMMON_DUTY_CYCLE; + } + + instance->tx.tx_raw_cnt = 0; + instance->tx.need_reinitialization = (new_tx_frequency != instance->tx.frequency) || + (new_tx_duty_cycle != instance->tx.duty_cycle); + instance->tx.frequency = new_tx_frequency; + instance->tx.duty_cycle = new_tx_duty_cycle; + if(instance->signal.decoded) { + infrared_reset_encoder(instance->infrared_encoder, &instance->signal.message); + } + new_signal_obtained = true; + } else if(response == InfraredWorkerGetSignalResponseSame) { + new_signal_obtained = true; + /* no need to reinit */ + } else if(response == InfraredWorkerGetSignalResponseStop) { + new_signal_obtained = false; + } else { + furi_assert(0); + } + + return new_signal_obtained; +} + +static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { + bool new_data_available = true; + InfraredWorkerTiming timing; + InfraredStatus status = InfraredStatusError; + + while(!xStreamBufferIsFull(instance->stream) && !instance->tx.need_reinitialization && + new_data_available) { + if(instance->signal.decoded) { + status = infrared_encode(instance->infrared_encoder, &timing.duration, &timing.level); + } else { + timing.duration = instance->signal.timings[instance->tx.tx_raw_cnt]; + /* raw always starts from Mark, but we fill it with space delay at start */ + timing.level = (instance->tx.tx_raw_cnt % 2); + ++instance->tx.tx_raw_cnt; + if(instance->tx.tx_raw_cnt >= instance->signal.timings_cnt) { + instance->tx.tx_raw_cnt = 0; + status = InfraredStatusDone; + } else { + status = InfraredStatusOk; + } + } + + if(status == InfraredStatusError) { + furi_assert(0); + new_data_available = false; + break; + } else if(status == InfraredStatusOk) { + timing.state = FuriHalInfraredTxGetDataStateOk; + } else if(status == InfraredStatusDone) { + timing.state = FuriHalInfraredTxGetDataStateDone; + + new_data_available = infrared_get_new_signal(instance); + if(instance->tx.need_reinitialization || !new_data_available) { + timing.state = FuriHalInfraredTxGetDataStateLastDone; + } + } else { + furi_assert(0); + } + uint32_t written_size = + xStreamBufferSend(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0); + furi_assert(sizeof(InfraredWorkerTiming) == written_size); + (void)written_size; + } + + return new_data_available; +} + +static int32_t infrared_worker_tx_thread(void* thread_context) { + InfraredWorker* instance = thread_context; + furi_assert(instance->state == InfraredWorkerStateStartTx); + furi_assert(thread_context); + + uint32_t events = 0; + bool new_data_available = true; + bool exit = false; + + exit = !infrared_get_new_signal(instance); + furi_assert(!exit); + + while(!exit) { + switch(instance->state) { + case InfraredWorkerStateStartTx: + instance->tx.need_reinitialization = false; + new_data_available = infrared_worker_tx_fill_buffer(instance); + furi_hal_infrared_async_tx_start(instance->tx.frequency, instance->tx.duty_cycle); + + if(!new_data_available) { + instance->state = InfraredWorkerStateStopTx; + } else if(instance->tx.need_reinitialization) { + instance->state = InfraredWorkerStateWaitTxEnd; + } else { + instance->state = InfraredWorkerStateRunTx; + } + + break; + case InfraredWorkerStateStopTx: + furi_hal_infrared_async_tx_stop(); + exit = true; + break; + case InfraredWorkerStateWaitTxEnd: + furi_hal_infrared_async_tx_wait_termination(); + instance->state = InfraredWorkerStateStartTx; + + events = osEventFlagsGet(instance->events); + if(events & INFRARED_WORKER_EXIT) { + exit = true; + break; + } + + break; + case InfraredWorkerStateRunTx: + events = osEventFlagsWait( + instance->events, INFRARED_WORKER_ALL_TX_EVENTS, 0, osWaitForever); + furi_check(events & INFRARED_WORKER_ALL_TX_EVENTS); /* at least one caught */ + + if(events & INFRARED_WORKER_EXIT) { + instance->state = InfraredWorkerStateStopTx; + break; + } + + if(events & INFRARED_WORKER_TX_FILL_BUFFER) { + infrared_worker_tx_fill_buffer(instance); + + if(instance->tx.need_reinitialization) { + instance->state = InfraredWorkerStateWaitTxEnd; + } + } + + if(events & INFRARED_WORKER_TX_MESSAGE_SENT) { + if(instance->tx.message_sent_callback) + instance->tx.message_sent_callback(instance->tx.message_sent_context); + } + break; + default: + furi_assert(0); + break; + } + } + + return 0; +} + +void infrared_worker_tx_set_get_signal_callback( + InfraredWorker* instance, + InfraredWorkerGetSignalCallback callback, + void* context) { + furi_assert(instance); + instance->tx.get_signal_callback = callback; + instance->tx.get_signal_context = context; +} + +void infrared_worker_tx_set_signal_sent_callback( + InfraredWorker* instance, + InfraredWorkerMessageSentCallback callback, + void* context) { + furi_assert(instance); + instance->tx.message_sent_callback = callback; + instance->tx.message_sent_context = context; +} + +void infrared_worker_tx_stop(InfraredWorker* instance) { + furi_assert(instance); + furi_assert(instance->state != InfraredWorkerStateRunRx); + + osEventFlagsSet(instance->events, INFRARED_WORKER_EXIT); + furi_thread_join(instance->thread); + furi_hal_infrared_async_tx_set_data_isr_callback(NULL, NULL); + furi_hal_infrared_async_tx_set_signal_sent_isr_callback(NULL, NULL); + + instance->signal.timings_cnt = 0; + BaseType_t xReturn = pdFAIL; + xReturn = xStreamBufferReset(instance->stream); + furi_assert(xReturn == pdPASS); + (void)xReturn; + instance->state = InfraredWorkerStateIdle; +} + +void infrared_worker_set_decoded_signal(InfraredWorker* instance, const InfraredMessage* message) { + furi_assert(instance); + furi_assert(message); + + instance->signal.decoded = true; + instance->signal.message = *message; +} + +void infrared_worker_set_raw_signal( + InfraredWorker* instance, + const uint32_t* timings, + size_t timings_cnt) { + furi_assert(instance); + furi_assert(timings); + furi_assert(timings_cnt > 0); + size_t max_copy_num = COUNT_OF(instance->signal.timings) - 1; + furi_check(timings_cnt <= max_copy_num); + + instance->signal.timings[0] = INFRARED_RAW_TX_TIMING_DELAY_US; + memcpy(&instance->signal.timings[1], timings, timings_cnt * sizeof(uint32_t)); + instance->signal.decoded = false; + instance->signal.timings_cnt = timings_cnt + 1; +} + +InfraredWorkerGetSignalResponse + infrared_worker_tx_get_signal_steady_callback(void* context, InfraredWorker* instance) { + InfraredWorkerGetSignalResponse response = instance->tx.steady_signal_sent ? + InfraredWorkerGetSignalResponseSame : + InfraredWorkerGetSignalResponseNew; + instance->tx.steady_signal_sent = true; + return response; +} diff --git a/lib/infrared/worker/infrared_worker.h b/lib/infrared/worker/infrared_worker.h new file mode 100644 index 00000000..f54b3e01 --- /dev/null +++ b/lib/infrared/worker/infrared_worker.h @@ -0,0 +1,175 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_TIMINGS_AMOUNT 512 + +/** Interface struct of infrared worker */ +typedef struct InfraredWorker InfraredWorker; +/** Interface struct of received signal */ +typedef struct InfraredWorkerSignal InfraredWorkerSignal; + +typedef enum { + InfraredWorkerGetSignalResponseNew, /** Signal, provided by callback is new and encoder should be reseted */ + InfraredWorkerGetSignalResponseSame, /** Signal, provided by callback is same. No encoder resetting. */ + InfraredWorkerGetSignalResponseStop, /** No more signals available. */ +} InfraredWorkerGetSignalResponse; + +/** Callback type for providing next signal to send. Should be used with + * infrared_worker_make_decoded_signal() or infrared_worker_make_raw_signal() + */ +typedef InfraredWorkerGetSignalResponse ( + *InfraredWorkerGetSignalCallback)(void* context, InfraredWorker* instance); + +/** Callback type for 'message is sent' event */ +typedef void (*InfraredWorkerMessageSentCallback)(void* context); + +/** Callback type to call by InfraredWorker thread when new signal is received */ +typedef void ( + *InfraredWorkerReceivedSignalCallback)(void* context, InfraredWorkerSignal* received_signal); + +/** Allocate InfraredWorker + * + * @return just created instance of InfraredWorker + */ +InfraredWorker* infrared_worker_alloc(); + +/** Free InfraredWorker + * + * @param[in] instance - InfraredWorker instance + */ +void infrared_worker_free(InfraredWorker* instance); + +/** Start InfraredWorker thread, initialise furi_hal, prepare all work. + * + * @param[in] instance - InfraredWorker instance + */ +void infrared_worker_rx_start(InfraredWorker* instance); + +/** Stop InfraredWorker thread, deinitialize furi_hal. + * + * @param[in] instance - InfraredWorker instance + */ +void infrared_worker_rx_stop(InfraredWorker* instance); + +/** Set received data callback InfraredWorker + * + * @param[in] instance - InfraredWorker instance + * @param[in] context - context to pass to callbacks + * @param[in] callback - InfraredWorkerReceivedSignalCallback callback + */ +void infrared_worker_rx_set_received_signal_callback( + InfraredWorker* instance, + InfraredWorkerReceivedSignalCallback callback, + void* context); + +/** Enable blinking on receiving any signal on IR port. + * + * @param[in] instance - instance of InfraredWorker + * @param[in] enable - true if you want to enable blinking + * false otherwise + */ +void infrared_worker_rx_enable_blink_on_receiving(InfraredWorker* instance, bool enable); + +/** Clarify is received signal either decoded or raw + * + * @param[in] signal - received signal + * @return true if signal is decoded, false if signal is raw + */ +bool infrared_worker_signal_is_decoded(const InfraredWorkerSignal* signal); + +/** Start transmitting signal. Callback InfraredWorkerGetSignalCallback should be + * set before this function is called, as it calls for it to fill buffer before + * starting transmission. + * + * @param[in] instance - InfraredWorker instance + */ +void infrared_worker_tx_start(InfraredWorker* instance); + +/** Stop transmitting signal. Waits for end of current signal and stops transmission. + * + * @param[in] instance - InfraredWorker instance + */ +void infrared_worker_tx_stop(InfraredWorker* instance); + +/** Set callback for providing next signal to send + * + * @param[in] instance - InfraredWorker instance + * @param[in] context - context to pass to callbacks + * @param[in] callback - InfraredWorkerGetSignalCallback callback + */ +void infrared_worker_tx_set_get_signal_callback( + InfraredWorker* instance, + InfraredWorkerGetSignalCallback callback, + void* context); + +/** Set callback for end of signal transmitting + * + * @param[in] instance - InfraredWorker instance + * @param[in] context - context to pass to callbacks + * @param[in] callback - InfraredWorkerMessageSentCallback callback + */ +void infrared_worker_tx_set_signal_sent_callback( + InfraredWorker* instance, + InfraredWorkerMessageSentCallback callback, + void* context); + +/** Callback to pass to infrared_worker_tx_set_get_signal_callback() if signal + * is steady and will not be changed between infrared_worker start and stop. + * Before starting transmission, desired steady signal must be set with + * infrared_worker_make_decoded_signal() or infrared_worker_make_raw_signal(). + * + * This function should not be implicitly called. + * + * @param[in] context - context + * @param[out] instance - InfraredWorker instance + */ +InfraredWorkerGetSignalResponse + infrared_worker_tx_get_signal_steady_callback(void* context, InfraredWorker* instance); + +/** Acquire raw signal from interface struct 'InfraredWorkerSignal'. + * First, you have to ensure that signal is raw. + * + * @param[in] signal - received signal + * @param[out] timings - pointer to array of timings + * @param[out] timings_cnt - pointer to amount of timings + */ +void infrared_worker_get_raw_signal( + const InfraredWorkerSignal* signal, + const uint32_t** timings, + size_t* timings_cnt); + +/** Acquire decoded message from interface struct 'InfraredWorkerSignal'. + * First, you have to ensure that signal is decoded. + * + * @param[in] signal - received signal + * @return decoded INFRARED message + */ +const InfraredMessage* infrared_worker_get_decoded_signal(const InfraredWorkerSignal* signal); + +/** Set current decoded signal for InfraredWorker instance + * + * @param[out] instance - InfraredWorker instance + * @param[in] message - decoded signal + */ +void infrared_worker_set_decoded_signal(InfraredWorker* instance, const InfraredMessage* message); + +/** Set current raw signal for InfraredWorker instance + * + * @param[out] instance - InfraredWorker instance + * @param[in] timings - array of raw timings + * @param[in] timings_cnt - size of array of raw timings + */ +void infrared_worker_set_raw_signal( + InfraredWorker* instance, + const uint32_t* timings, + size_t timings_cnt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/irda/encoder_decoder/common/irda_common_i.h b/lib/irda/encoder_decoder/common/irda_common_i.h deleted file mode 100644 index aab32e1c..00000000 --- a/lib/irda/encoder_decoder/common/irda_common_i.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include "irda.h" -#include "irda_i.h" - -#define MATCH_TIMING(x, v, delta) (((x) < (v + delta)) && ((x) > (v - delta))) - -typedef struct IrdaCommonDecoder IrdaCommonDecoder; -typedef struct IrdaCommonEncoder IrdaCommonEncoder; - -typedef IrdaStatus (*IrdaCommonDecode)(IrdaCommonDecoder*, bool, uint32_t); -typedef IrdaStatus (*IrdaCommonDecodeRepeat)(IrdaCommonDecoder*); -typedef bool (*IrdaCommonInterpret)(IrdaCommonDecoder*); -typedef IrdaStatus (*IrdaCommonEncode)(IrdaCommonEncoder* encoder, uint32_t* out, bool* polarity); - -typedef struct { - IrdaTimings timings; - bool manchester_start_from_space; - bool no_stop_bit; - uint8_t databit_len[4]; - IrdaCommonDecode decode; - IrdaCommonDecodeRepeat decode_repeat; - IrdaCommonInterpret interpret; - IrdaCommonEncode encode; - IrdaCommonEncode encode_repeat; -} IrdaCommonProtocolSpec; - -typedef enum { - IrdaCommonDecoderStateWaitPreamble, - IrdaCommonDecoderStateDecode, - IrdaCommonDecoderStateProcessRepeat, -} IrdaCommonStateDecoder; - -typedef enum { - IrdaCommonEncoderStateSilence, - IrdaCommonEncoderStatePreamble, - IrdaCommonEncoderStateEncode, - IrdaCommonEncoderStateEncodeRepeat, -} IrdaCommonStateEncoder; - -struct IrdaCommonDecoder { - const IrdaCommonProtocolSpec* protocol; - void* context; - uint32_t timings[6]; - IrdaMessage message; - IrdaCommonStateDecoder state; - uint8_t timings_cnt; - bool switch_detect; - bool level; - uint16_t databit_cnt; - uint8_t data[]; -}; - -struct IrdaCommonEncoder { - const IrdaCommonProtocolSpec* protocol; - IrdaCommonStateEncoder state; - bool switch_detect; - uint8_t bits_to_encode; - uint8_t bits_encoded; - uint32_t timings_sum; - uint32_t timings_encoded; - void* context; - uint8_t data[]; -}; - -IrdaMessage* irda_common_decode(IrdaCommonDecoder* decoder, bool level, uint32_t duration); -IrdaStatus irda_common_decode_pdwm(IrdaCommonDecoder* decoder, bool level, uint32_t timing); -IrdaStatus irda_common_decode_manchester(IrdaCommonDecoder* decoder, bool level, uint32_t timing); -void* irda_common_decoder_alloc(const IrdaCommonProtocolSpec* protocol); -void irda_common_decoder_free(IrdaCommonDecoder* decoder); -void irda_common_decoder_reset(IrdaCommonDecoder* decoder); -IrdaMessage* irda_common_decoder_check_ready(IrdaCommonDecoder* decoder); - -IrdaStatus irda_common_encode(IrdaCommonEncoder* encoder, uint32_t* duration, bool* polarity); -IrdaStatus irda_common_encode_pdwm(IrdaCommonEncoder* encoder, uint32_t* duration, bool* polarity); -IrdaStatus - irda_common_encode_manchester(IrdaCommonEncoder* encoder, uint32_t* duration, bool* polarity); -void* irda_common_encoder_alloc(const IrdaCommonProtocolSpec* protocol); -void irda_common_encoder_free(IrdaCommonEncoder* encoder); -void irda_common_encoder_reset(IrdaCommonEncoder* encoder); diff --git a/lib/irda/encoder_decoder/common/irda_common_protocol_defs.c b/lib/irda/encoder_decoder/common/irda_common_protocol_defs.c deleted file mode 100644 index 7edc0974..00000000 --- a/lib/irda/encoder_decoder/common/irda_common_protocol_defs.c +++ /dev/null @@ -1,117 +0,0 @@ -#include "irda_common_i.h" -#include "irda_protocol_defs_i.h" - -const IrdaCommonProtocolSpec protocol_nec = { - .timings = - { - .preamble_mark = IRDA_NEC_PREAMBLE_MARK, - .preamble_space = IRDA_NEC_PREAMBLE_SPACE, - .bit1_mark = IRDA_NEC_BIT1_MARK, - .bit1_space = IRDA_NEC_BIT1_SPACE, - .bit0_mark = IRDA_NEC_BIT0_MARK, - .bit0_space = IRDA_NEC_BIT0_SPACE, - .preamble_tolerance = IRDA_NEC_PREAMBLE_TOLERANCE, - .bit_tolerance = IRDA_NEC_BIT_TOLERANCE, - .silence_time = IRDA_NEC_SILENCE, - .min_split_time = IRDA_NEC_MIN_SPLIT_TIME, - }, - .databit_len[0] = 42, - .databit_len[1] = 32, - .no_stop_bit = false, - .decode = irda_common_decode_pdwm, - .encode = irda_common_encode_pdwm, - .interpret = irda_decoder_nec_interpret, - .decode_repeat = irda_decoder_nec_decode_repeat, - .encode_repeat = irda_encoder_nec_encode_repeat, -}; - -const IrdaCommonProtocolSpec protocol_samsung32 = { - .timings = - { - .preamble_mark = IRDA_SAMSUNG_PREAMBLE_MARK, - .preamble_space = IRDA_SAMSUNG_PREAMBLE_SPACE, - .bit1_mark = IRDA_SAMSUNG_BIT1_MARK, - .bit1_space = IRDA_SAMSUNG_BIT1_SPACE, - .bit0_mark = IRDA_SAMSUNG_BIT0_MARK, - .bit0_space = IRDA_SAMSUNG_BIT0_SPACE, - .preamble_tolerance = IRDA_SAMSUNG_PREAMBLE_TOLERANCE, - .bit_tolerance = IRDA_SAMSUNG_BIT_TOLERANCE, - .silence_time = IRDA_SAMSUNG_SILENCE, - .min_split_time = IRDA_SAMSUNG_MIN_SPLIT_TIME, - }, - .databit_len[0] = 32, - .no_stop_bit = false, - .decode = irda_common_decode_pdwm, - .encode = irda_common_encode_pdwm, - .interpret = irda_decoder_samsung32_interpret, - .decode_repeat = irda_decoder_samsung32_decode_repeat, - .encode_repeat = irda_encoder_samsung32_encode_repeat, -}; - -const IrdaCommonProtocolSpec protocol_rc6 = { - .timings = - { - .preamble_mark = IRDA_RC6_PREAMBLE_MARK, - .preamble_space = IRDA_RC6_PREAMBLE_SPACE, - .bit1_mark = IRDA_RC6_BIT, - .preamble_tolerance = IRDA_RC6_PREAMBLE_TOLERANCE, - .bit_tolerance = IRDA_RC6_BIT_TOLERANCE, - .silence_time = IRDA_RC6_SILENCE, - .min_split_time = IRDA_RC6_MIN_SPLIT_TIME, - }, - .databit_len[0] = - 1 + 3 + 1 + 8 + - 8, // start_bit + 3 mode bits, + 1 toggle bit (x2 timing) + 8 address + 8 command - .manchester_start_from_space = false, - .decode = irda_decoder_rc6_decode_manchester, - .encode = irda_encoder_rc6_encode_manchester, - .interpret = irda_decoder_rc6_interpret, - .decode_repeat = NULL, - .encode_repeat = NULL, -}; - -const IrdaCommonProtocolSpec protocol_rc5 = { - .timings = - { - .preamble_mark = 0, - .preamble_space = 0, - .bit1_mark = IRDA_RC5_BIT, - .preamble_tolerance = 0, - .bit_tolerance = IRDA_RC5_BIT_TOLERANCE, - .silence_time = IRDA_RC5_SILENCE, - .min_split_time = IRDA_RC5_MIN_SPLIT_TIME, - }, - .databit_len[0] = 1 + 1 + 1 + 5 + - 6, // start_bit + start_bit/command_bit + toggle_bit + 5 address + 6 command - .manchester_start_from_space = true, - .decode = irda_common_decode_manchester, - .encode = irda_common_encode_manchester, - .interpret = irda_decoder_rc5_interpret, - .decode_repeat = NULL, - .encode_repeat = NULL, -}; - -const IrdaCommonProtocolSpec protocol_sirc = { - .timings = - { - .preamble_mark = IRDA_SIRC_PREAMBLE_MARK, - .preamble_space = IRDA_SIRC_PREAMBLE_SPACE, - .bit1_mark = IRDA_SIRC_BIT1_MARK, - .bit1_space = IRDA_SIRC_BIT1_SPACE, - .bit0_mark = IRDA_SIRC_BIT0_MARK, - .bit0_space = IRDA_SIRC_BIT0_SPACE, - .preamble_tolerance = IRDA_SIRC_PREAMBLE_TOLERANCE, - .bit_tolerance = IRDA_SIRC_BIT_TOLERANCE, - .silence_time = IRDA_SIRC_SILENCE, - .min_split_time = IRDA_SIRC_MIN_SPLIT_TIME, - }, - .databit_len[0] = 20, - .databit_len[1] = 15, - .databit_len[2] = 12, - .no_stop_bit = true, - .decode = irda_common_decode_pdwm, - .encode = irda_common_encode_pdwm, - .interpret = irda_decoder_sirc_interpret, - .decode_repeat = NULL, - .encode_repeat = irda_encoder_sirc_encode_repeat, -}; diff --git a/lib/irda/encoder_decoder/irda.c b/lib/irda/encoder_decoder/irda.c deleted file mode 100644 index f6fbb6af..00000000 --- a/lib/irda/encoder_decoder/irda.c +++ /dev/null @@ -1,298 +0,0 @@ -#include "irda.h" -#include "furi/check.h" -#include "common/irda_common_i.h" -#include "irda_protocol_defs_i.h" -#include -#include -#include -#include -#include "irda_i.h" -#include - -typedef struct { - IrdaAlloc alloc; - IrdaDecode decode; - IrdaDecoderReset reset; - IrdaFree free; - IrdaDecoderCheckReady check_ready; -} IrdaDecoders; - -typedef struct { - IrdaAlloc alloc; - IrdaEncode encode; - IrdaEncoderReset reset; - IrdaFree free; -} IrdaEncoders; - -struct IrdaDecoderHandler { - void** ctx; -}; - -struct IrdaEncoderHandler { - void* handler; - const IrdaEncoders* encoder; -}; - -typedef struct { - IrdaEncoders encoder; - IrdaDecoders decoder; - IrdaGetProtocolSpec get_protocol_spec; -} IrdaEncoderDecoder; - -static const IrdaEncoderDecoder irda_encoder_decoder[] = { - { - .decoder = - {.alloc = irda_decoder_nec_alloc, - .decode = irda_decoder_nec_decode, - .reset = irda_decoder_nec_reset, - .check_ready = irda_decoder_nec_check_ready, - .free = irda_decoder_nec_free}, - .encoder = - {.alloc = irda_encoder_nec_alloc, - .encode = irda_encoder_nec_encode, - .reset = irda_encoder_nec_reset, - .free = irda_encoder_nec_free}, - .get_protocol_spec = irda_nec_get_spec, - }, - { - .decoder = - {.alloc = irda_decoder_samsung32_alloc, - .decode = irda_decoder_samsung32_decode, - .reset = irda_decoder_samsung32_reset, - .check_ready = irda_decoder_samsung32_check_ready, - .free = irda_decoder_samsung32_free}, - .encoder = - {.alloc = irda_encoder_samsung32_alloc, - .encode = irda_encoder_samsung32_encode, - .reset = irda_encoder_samsung32_reset, - .free = irda_encoder_samsung32_free}, - .get_protocol_spec = irda_samsung32_get_spec, - }, - { - .decoder = - {.alloc = irda_decoder_rc5_alloc, - .decode = irda_decoder_rc5_decode, - .reset = irda_decoder_rc5_reset, - .check_ready = irda_decoder_rc5_check_ready, - .free = irda_decoder_rc5_free}, - .encoder = - {.alloc = irda_encoder_rc5_alloc, - .encode = irda_encoder_rc5_encode, - .reset = irda_encoder_rc5_reset, - .free = irda_encoder_rc5_free}, - .get_protocol_spec = irda_rc5_get_spec, - }, - { - .decoder = - {.alloc = irda_decoder_rc6_alloc, - .decode = irda_decoder_rc6_decode, - .reset = irda_decoder_rc6_reset, - .check_ready = irda_decoder_rc6_check_ready, - .free = irda_decoder_rc6_free}, - .encoder = - {.alloc = irda_encoder_rc6_alloc, - .encode = irda_encoder_rc6_encode, - .reset = irda_encoder_rc6_reset, - .free = irda_encoder_rc6_free}, - .get_protocol_spec = irda_rc6_get_spec, - }, - { - .decoder = - {.alloc = irda_decoder_sirc_alloc, - .decode = irda_decoder_sirc_decode, - .reset = irda_decoder_sirc_reset, - .check_ready = irda_decoder_sirc_check_ready, - .free = irda_decoder_sirc_free}, - .encoder = - {.alloc = irda_encoder_sirc_alloc, - .encode = irda_encoder_sirc_encode, - .reset = irda_encoder_sirc_reset, - .free = irda_encoder_sirc_free}, - .get_protocol_spec = irda_sirc_get_spec, - }, -}; - -static int irda_find_index_by_protocol(IrdaProtocol protocol); -static const IrdaProtocolSpecification* irda_get_spec_by_protocol(IrdaProtocol protocol); - -const IrdaMessage* irda_decode(IrdaDecoderHandler* handler, bool level, uint32_t duration) { - furi_assert(handler); - - IrdaMessage* message = NULL; - IrdaMessage* result = NULL; - - for(int i = 0; i < COUNT_OF(irda_encoder_decoder); ++i) { - if(irda_encoder_decoder[i].decoder.decode) { - message = irda_encoder_decoder[i].decoder.decode(handler->ctx[i], level, duration); - if(!result && message) { - result = message; - } - } - } - - return result; -} - -IrdaDecoderHandler* irda_alloc_decoder(void) { - IrdaDecoderHandler* handler = malloc(sizeof(IrdaDecoderHandler)); - handler->ctx = malloc(sizeof(void*) * COUNT_OF(irda_encoder_decoder)); - - for(int i = 0; i < COUNT_OF(irda_encoder_decoder); ++i) { - handler->ctx[i] = 0; - if(irda_encoder_decoder[i].decoder.alloc) - handler->ctx[i] = irda_encoder_decoder[i].decoder.alloc(); - } - - irda_reset_decoder(handler); - return handler; -} - -void irda_free_decoder(IrdaDecoderHandler* handler) { - furi_assert(handler); - furi_assert(handler->ctx); - - for(int i = 0; i < COUNT_OF(irda_encoder_decoder); ++i) { - if(irda_encoder_decoder[i].decoder.free) - irda_encoder_decoder[i].decoder.free(handler->ctx[i]); - } - - free(handler->ctx); - free(handler); -} - -void irda_reset_decoder(IrdaDecoderHandler* handler) { - for(int i = 0; i < COUNT_OF(irda_encoder_decoder); ++i) { - if(irda_encoder_decoder[i].decoder.reset) - irda_encoder_decoder[i].decoder.reset(handler->ctx[i]); - } -} - -const IrdaMessage* irda_check_decoder_ready(IrdaDecoderHandler* handler) { - furi_assert(handler); - - IrdaMessage* message = NULL; - IrdaMessage* result = NULL; - - for(int i = 0; i < COUNT_OF(irda_encoder_decoder); ++i) { - if(irda_encoder_decoder[i].decoder.check_ready) { - message = irda_encoder_decoder[i].decoder.check_ready(handler->ctx[i]); - if(!result && message) { - result = message; - } - } - } - - return result; -} - -IrdaEncoderHandler* irda_alloc_encoder(void) { - IrdaEncoderHandler* handler = malloc(sizeof(IrdaEncoderHandler)); - handler->handler = NULL; - handler->encoder = NULL; - return handler; -} - -void irda_free_encoder(IrdaEncoderHandler* handler) { - furi_assert(handler); - const IrdaEncoders* encoder = handler->encoder; - - if(encoder || handler->handler) { - furi_assert(encoder); - furi_assert(handler->handler); - furi_assert(encoder->free); - encoder->free(handler->handler); - } - - free(handler); -} - -static int irda_find_index_by_protocol(IrdaProtocol protocol) { - for(int i = 0; i < COUNT_OF(irda_encoder_decoder); ++i) { - if(irda_encoder_decoder[i].get_protocol_spec(protocol)) { - return i; - } - } - - return -1; -} - -void irda_reset_encoder(IrdaEncoderHandler* handler, const IrdaMessage* message) { - furi_assert(handler); - furi_assert(message); - int index = irda_find_index_by_protocol(message->protocol); - furi_check(index >= 0); - - const IrdaEncoders* required_encoder = &irda_encoder_decoder[index].encoder; - furi_assert(required_encoder); - furi_assert(required_encoder->reset); - furi_assert(required_encoder->alloc); - - /* Realloc encoder if different protocol set */ - if(required_encoder != handler->encoder) { - if(handler->handler != NULL) { - furi_assert(handler->encoder->free); - handler->encoder->free(handler->handler); - } - handler->encoder = required_encoder; - handler->handler = handler->encoder->alloc(); - } - - handler->encoder->reset(handler->handler, message); -} - -IrdaStatus irda_encode(IrdaEncoderHandler* handler, uint32_t* duration, bool* level) { - furi_assert(handler); - furi_assert(duration); - furi_assert(level); - const IrdaEncoders* encoder = handler->encoder; - furi_assert(encoder); - furi_assert(encoder->encode); - - IrdaStatus status = encoder->encode(handler->handler, duration, level); - furi_assert(status != IrdaStatusError); - - return status; -} - -bool irda_is_protocol_valid(IrdaProtocol protocol) { - return irda_find_index_by_protocol(protocol) >= 0; -} - -IrdaProtocol irda_get_protocol_by_name(const char* protocol_name) { - for(IrdaProtocol protocol = 0; protocol < IrdaProtocolMAX; ++protocol) { - const char* name = irda_get_protocol_name(protocol); - if(!strcmp(name, protocol_name)) return protocol; - } - return IrdaProtocolUnknown; -} - -static const IrdaProtocolSpecification* irda_get_spec_by_protocol(IrdaProtocol protocol) { - int index = irda_find_index_by_protocol(protocol); - const IrdaProtocolSpecification* spec = NULL; - if(index >= 0) { - spec = irda_encoder_decoder[index].get_protocol_spec(protocol); - } - - furi_assert(spec); - return spec; -} - -const char* irda_get_protocol_name(IrdaProtocol protocol) { - return irda_get_spec_by_protocol(protocol)->name; -} - -uint8_t irda_get_protocol_address_length(IrdaProtocol protocol) { - return irda_get_spec_by_protocol(protocol)->address_length; -} - -uint8_t irda_get_protocol_command_length(IrdaProtocol protocol) { - return irda_get_spec_by_protocol(protocol)->command_length; -} - -uint32_t irda_get_protocol_frequency(IrdaProtocol protocol) { - return irda_get_spec_by_protocol(protocol)->frequency; -} - -float irda_get_protocol_duty_cycle(IrdaProtocol protocol) { - return irda_get_spec_by_protocol(protocol)->duty_cycle; -} diff --git a/lib/irda/encoder_decoder/irda.h b/lib/irda/encoder_decoder/irda.h deleted file mode 100644 index ac7c6026..00000000 --- a/lib/irda/encoder_decoder/irda.h +++ /dev/null @@ -1,204 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define IRDA_COMMON_CARRIER_FREQUENCY ((uint32_t)38000) -#define IRDA_COMMON_DUTY_CYCLE ((float)0.33) - -/* if we want to see splitted raw signals during brutforce, - * we have to have RX raw timing delay less than TX */ -#define IRDA_RAW_RX_TIMING_DELAY_US 150000 -#define IRDA_RAW_TX_TIMING_DELAY_US 180000 - -typedef struct IrdaDecoderHandler IrdaDecoderHandler; -typedef struct IrdaEncoderHandler IrdaEncoderHandler; - -typedef enum { - IrdaProtocolUnknown = -1, - IrdaProtocolNEC = 0, - IrdaProtocolNECext, - IrdaProtocolNEC42, - IrdaProtocolNEC42ext, - IrdaProtocolSamsung32, - IrdaProtocolRC6, - IrdaProtocolRC5, - IrdaProtocolRC5X, - IrdaProtocolSIRC, - IrdaProtocolSIRC15, - IrdaProtocolSIRC20, - IrdaProtocolMAX, -} IrdaProtocol; - -typedef struct { - IrdaProtocol protocol; - uint32_t address; - uint32_t command; - bool repeat; -} IrdaMessage; - -typedef enum { - IrdaStatusError, - IrdaStatusOk, - IrdaStatusDone, - IrdaStatusReady, -} IrdaStatus; - -/** - * Initialize decoder. - * - * \return returns pointer to IRDA decoder handler if success, otherwise - error. - */ -IrdaDecoderHandler* irda_alloc_decoder(void); - -/** - * Provide to decoder next timing. - * - * \param[in] handler - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder(). - * \param[in] level - high(true) or low(false) level of input signal to analyze. - * it should alternate every call, otherwise it is an error case, - * and decoder resets its state and start decoding from the start. - * \param[in] duration - duration of steady high/low input signal. - * \return if message is ready, returns pointer to decoded message, returns NULL. - * Note: ownership of returned ptr belongs to handler. So pointer is valid - * up to next irda_free_decoder(), irda_reset_decoder(), - * irda_decode(), irda_check_decoder_ready() calls. - */ -const IrdaMessage* irda_decode(IrdaDecoderHandler* handler, bool level, uint32_t duration); - -/** - * Check whether decoder is ready. - * Functionality is quite similar to irda_decode(), but with no timing providing. - * Some protocols (e.g. Sony SIRC) has variable payload length, which means we - * can't recognize end of message right after receiving last bit. That's why - * application should call to irda_check_decoder_ready() after some timeout to - * retrieve decoded message, if so. - * - * \param[in] handler - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder(). - * \return if message is ready, returns pointer to decoded message, returns NULL. - * Note: ownership of returned ptr belongs to handler. So pointer is valid - * up to next irda_free_decoder(), irda_reset_decoder(), - * irda_decode(), irda_check_decoder_ready() calls. - */ -const IrdaMessage* irda_check_decoder_ready(IrdaDecoderHandler* handler); - -/** - * Deinitialize decoder and free allocated memory. - * - * \param[in] handler - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder(). - */ -void irda_free_decoder(IrdaDecoderHandler* handler); - -/** - * Reset IRDA decoder. - * - * \param[in] handler - handler to IRDA decoders. Should be acquired with \c irda_alloc_decoder(). - */ -void irda_reset_decoder(IrdaDecoderHandler* handler); - -/** - * Get protocol name by protocol enum. - * - * \param[in] protocol - protocol identifier. - * \return string to protocol name. - */ -const char* irda_get_protocol_name(IrdaProtocol protocol); - -/** - * Get protocol enum by protocol name. - * - * \param[in] protocol_name - string to protocol name. - * \return protocol identifier. - */ -IrdaProtocol irda_get_protocol_by_name(const char* protocol_name); - -/** - * Get address length by protocol enum. - * - * \param[in] protocol - protocol identifier. - * \return length of address in bits. - */ -uint8_t irda_get_protocol_address_length(IrdaProtocol protocol); - -/** - * Get command length by protocol enum. - * - * \param[in] protocol - protocol identifier. - * \return length of command in bits. - */ -uint8_t irda_get_protocol_command_length(IrdaProtocol protocol); - -/** - * Checks whether protocol valid. - * - * \param[in] protocol - protocol identifier. - * \return true if protocol is valid, false otherwise. - */ -bool irda_is_protocol_valid(IrdaProtocol protocol); - -/** - * Allocate IRDA encoder. - * - * \return encoder handler. - */ -IrdaEncoderHandler* irda_alloc_encoder(void); - -/** - * Free encoder handler previously allocated with \c irda_alloc_encoder(). - * - * \param[in] handler - handler to IRDA encoder. Should be acquired with \c irda_alloc_encoder(). - */ -void irda_free_encoder(IrdaEncoderHandler* handler); - -/** - * Encode previously set IRDA message. - * Usage: - * 1) alloc with \c irda_alloc_encoder() - * 2) set message to encode with \c irda_reset_encoder() - * 3) call for \c irda_encode() to continuously get one at a time timings. - * 4) when \c irda_encode() returns IrdaStatusDone, it means new message is fully encoded. - * 5) to encode additional timings, just continue calling \c irda_encode(). - * - * \param[in] handler - handler to IRDA encoder. Should be acquired with \c irda_alloc_encoder(). - * \param[out] duration - encoded timing. - * \param[out] level - encoded level. - * - * \return status of encode operation. - */ -IrdaStatus irda_encode(IrdaEncoderHandler* handler, uint32_t* duration, bool* level); - -/** - * Reset IRDA encoder and set new message to encode. If it's not called after receiveing - * IrdaStatusDone in \c irda_encode(), encoder will encode repeat messages - * till the end of time. - * - * \param[in] handler - handler to IRDA encoder. Should be acquired with \c irda_alloc_encoder(). - * \param[in] message - message to encode. - */ -void irda_reset_encoder(IrdaEncoderHandler* handler, const IrdaMessage* message); - -/** - * Get PWM frequency value for selected protocol - * - * \param[in] protocol - protocol to get from PWM frequency - * - * \return frequency - */ -uint32_t irda_get_protocol_frequency(IrdaProtocol protocol); - -/** - * Get PWM duty cycle value for selected protocol - * - * \param[in] protocol - protocol to get from PWM duty cycle - * - * \return duty cycle - */ -float irda_get_protocol_duty_cycle(IrdaProtocol protocol); - -#ifdef __cplusplus -} -#endif diff --git a/lib/irda/encoder_decoder/irda_protocol_defs_i.h b/lib/irda/encoder_decoder/irda_protocol_defs_i.h deleted file mode 100644 index 87c4d906..00000000 --- a/lib/irda/encoder_decoder/irda_protocol_defs_i.h +++ /dev/null @@ -1,262 +0,0 @@ -#pragma once - -#include -#include -#include -#include "irda.h" -#include "common/irda_common_i.h" - -/*************************************************************************************************** -* NEC protocol description -* https://radioparty.ru/manuals/encyclopedia/213-ircontrol?start=1 -**************************************************************************************************** -* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Stop -* mark space Modulation up to period repeat repeat bit -* mark space -* -* 9000 4500 32 bit + stop bit ...110000 9000 2250 -* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ _ -* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ____________ ___ -* -***************************************************************************************************/ - -#define IRDA_NEC_PREAMBLE_MARK 9000 -#define IRDA_NEC_PREAMBLE_SPACE 4500 -#define IRDA_NEC_BIT1_MARK 560 -#define IRDA_NEC_BIT1_SPACE 1690 -#define IRDA_NEC_BIT0_MARK 560 -#define IRDA_NEC_BIT0_SPACE 560 -#define IRDA_NEC_REPEAT_PERIOD 110000 -#define IRDA_NEC_SILENCE IRDA_NEC_REPEAT_PERIOD -#define IRDA_NEC_MIN_SPLIT_TIME IRDA_NEC_REPEAT_PAUSE_MIN -#define IRDA_NEC_REPEAT_PAUSE_MIN 4000 -#define IRDA_NEC_REPEAT_PAUSE_MAX 150000 -#define IRDA_NEC_REPEAT_MARK 9000 -#define IRDA_NEC_REPEAT_SPACE 2250 -#define IRDA_NEC_PREAMBLE_TOLERANCE 200 // us -#define IRDA_NEC_BIT_TOLERANCE 120 // us - -void* irda_decoder_nec_alloc(void); -void irda_decoder_nec_reset(void* decoder); -void irda_decoder_nec_free(void* decoder); -IrdaMessage* irda_decoder_nec_check_ready(void* decoder); -IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duration); -void* irda_encoder_nec_alloc(void); -IrdaStatus irda_encoder_nec_encode(void* encoder_ptr, uint32_t* duration, bool* level); -void irda_encoder_nec_reset(void* encoder_ptr, const IrdaMessage* message); -void irda_encoder_nec_free(void* encoder_ptr); -bool irda_decoder_nec_interpret(IrdaCommonDecoder* decoder); -IrdaStatus irda_decoder_nec_decode_repeat(IrdaCommonDecoder* decoder); -IrdaStatus - irda_encoder_nec_encode_repeat(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level); -const IrdaProtocolSpecification* irda_nec_get_spec(IrdaProtocol protocol); - -extern const IrdaCommonProtocolSpec protocol_nec; - -/*************************************************************************************************** -* SAMSUNG32 protocol description -* https://www.mikrocontroller.net/articles/IRMP_-_english#SAMSUNG -**************************************************************************************************** -* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Bit1 Stop -* mark space Modulation repeat repeat bit -* mark space -* -* 4500 4500 32 bit + stop bit 40000/100000 4500 4500 -* __________ _ _ _ _ _ _ _ _ _ _ _ ___________ _ _ -* _ __________ __ _ __ __ __ _ _ __ __ _ ________________ ____________ ____ ___ -* -***************************************************************************************************/ - -#define IRDA_SAMSUNG_PREAMBLE_MARK 4500 -#define IRDA_SAMSUNG_PREAMBLE_SPACE 4500 -#define IRDA_SAMSUNG_BIT1_MARK 550 -#define IRDA_SAMSUNG_BIT1_SPACE 1650 -#define IRDA_SAMSUNG_BIT0_MARK 550 -#define IRDA_SAMSUNG_BIT0_SPACE 550 -#define IRDA_SAMSUNG_REPEAT_PAUSE_MIN 30000 -#define IRDA_SAMSUNG_REPEAT_PAUSE1 46000 -#define IRDA_SAMSUNG_REPEAT_PAUSE2 97000 -/* Samsung silence have to be greater than REPEAT MAX - * otherwise there can be problems during unit tests parsing - * of some data. Real tolerances we don't know, but in real life - * silence time should be greater than max repeat time. This is - * because of similar preambule timings for repeat and first messages. */ -#define IRDA_SAMSUNG_MIN_SPLIT_TIME 5000 -#define IRDA_SAMSUNG_SILENCE 145000 -#define IRDA_SAMSUNG_REPEAT_PAUSE_MAX 140000 -#define IRDA_SAMSUNG_REPEAT_MARK 4500 -#define IRDA_SAMSUNG_REPEAT_SPACE 4500 -#define IRDA_SAMSUNG_PREAMBLE_TOLERANCE 200 // us -#define IRDA_SAMSUNG_BIT_TOLERANCE 120 // us - -void* irda_decoder_samsung32_alloc(void); -void irda_decoder_samsung32_reset(void* decoder); -void irda_decoder_samsung32_free(void* decoder); -IrdaMessage* irda_decoder_samsung32_check_ready(void* ctx); -IrdaMessage* irda_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration); -IrdaStatus irda_encoder_samsung32_encode(void* encoder_ptr, uint32_t* duration, bool* level); -void irda_encoder_samsung32_reset(void* encoder_ptr, const IrdaMessage* message); -void* irda_encoder_samsung32_alloc(void); -void irda_encoder_samsung32_free(void* encoder_ptr); -bool irda_decoder_samsung32_interpret(IrdaCommonDecoder* decoder); -IrdaStatus irda_decoder_samsung32_decode_repeat(IrdaCommonDecoder* decoder); -IrdaStatus irda_encoder_samsung32_encode_repeat( - IrdaCommonEncoder* encoder, - uint32_t* duration, - bool* level); -const IrdaProtocolSpecification* irda_samsung32_get_spec(IrdaProtocol protocol); - -extern const IrdaCommonProtocolSpec protocol_samsung32; - -/*************************************************************************************************** -* RC6 protocol description -* https://www.mikrocontroller.net/articles/IRMP_-_english#RC6_.2B_RC6A -**************************************************************************************************** -* Preamble Manchester/biphase Silence -* mark/space Modulation -* -* 2666 889 444/888 - bit (x2 for toggle bit) 2666 -* -* ________ __ __ __ __ ____ __ __ __ __ __ __ __ __ -* _ _________ ____ __ __ ____ __ __ __ __ __ __ __ __ _______________ -* | 1 | 0 | 0 | 0 | 0 | ... | ... | | -* s m2 m1 m0 T address (MSB) command (MSB) -* -* s - start bit (always 1) -* m0-2 - mode (000 for RC6) -* T - toggle bit, twice longer -* address - 8 bit -* command - 8 bit -***************************************************************************************************/ - -#define IRDA_RC6_CARRIER_FREQUENCY 36000 -#define IRDA_RC6_DUTY_CYCLE 0.33 - -#define IRDA_RC6_PREAMBLE_MARK 2666 -#define IRDA_RC6_PREAMBLE_SPACE 889 -#define IRDA_RC6_BIT 444 // half of time-quant for 1 bit -#define IRDA_RC6_PREAMBLE_TOLERANCE 200 // us -#define IRDA_RC6_BIT_TOLERANCE 120 // us -/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ -#define IRDA_RC6_SILENCE (2700 * 10) -#define IRDA_RC6_MIN_SPLIT_TIME 2700 - -void* irda_decoder_rc6_alloc(void); -void irda_decoder_rc6_reset(void* decoder); -void irda_decoder_rc6_free(void* decoder); -IrdaMessage* irda_decoder_rc6_check_ready(void* ctx); -IrdaMessage* irda_decoder_rc6_decode(void* decoder, bool level, uint32_t duration); -void* irda_encoder_rc6_alloc(void); -void irda_encoder_rc6_reset(void* encoder_ptr, const IrdaMessage* message); -void irda_encoder_rc6_free(void* decoder); -IrdaStatus irda_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); -bool irda_decoder_rc6_interpret(IrdaCommonDecoder* decoder); -IrdaStatus - irda_decoder_rc6_decode_manchester(IrdaCommonDecoder* decoder, bool level, uint32_t timing); -IrdaStatus irda_encoder_rc6_encode_manchester( - IrdaCommonEncoder* encoder_ptr, - uint32_t* duration, - bool* polarity); -const IrdaProtocolSpecification* irda_rc6_get_spec(IrdaProtocol protocol); - -extern const IrdaCommonProtocolSpec protocol_rc6; - -/*************************************************************************************************** -* RC5 protocol description -* https://www.mikrocontroller.net/articles/IRMP_-_english#RC5_.2B_RC5X -**************************************************************************************************** -* Manchester/biphase -* Modulation -* -* 888/1776 - bit (x2 for toggle bit) -* -* __ ____ __ __ __ __ __ __ __ __ -* __ __ ____ __ __ __ __ __ __ __ _ -* | 1 | 1 | 0 | ... | ... | -* s si T address (MSB) command (MSB) -* -* Note: manchester starts from space timing, so it have to be handled properly -* s - start bit (always 1) -* si - RC5: start bit (always 1), RC5X - 7-th bit of address (in our case always 0) -* T - toggle bit, change it's value every button press -* address - 5 bit -* command - 6/7 bit -***************************************************************************************************/ - -#define IRDA_RC5_CARRIER_FREQUENCY 36000 -#define IRDA_RC5_DUTY_CYCLE 0.33 - -#define IRDA_RC5_PREAMBLE_MARK 0 -#define IRDA_RC5_PREAMBLE_SPACE 0 -#define IRDA_RC5_BIT 888 // half of time-quant for 1 bit -#define IRDA_RC5_PREAMBLE_TOLERANCE 200 // us -#define IRDA_RC5_BIT_TOLERANCE 120 // us -/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ -#define IRDA_RC5_SILENCE (2700 * 10) -#define IRDA_RC5_MIN_SPLIT_TIME 2700 - -void* irda_decoder_rc5_alloc(void); -void irda_decoder_rc5_reset(void* decoder); -void irda_decoder_rc5_free(void* decoder); -IrdaMessage* irda_decoder_rc5_check_ready(void* ctx); -IrdaMessage* irda_decoder_rc5_decode(void* decoder, bool level, uint32_t duration); -void* irda_encoder_rc5_alloc(void); -void irda_encoder_rc5_reset(void* encoder_ptr, const IrdaMessage* message); -void irda_encoder_rc5_free(void* decoder); -IrdaStatus irda_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); -bool irda_decoder_rc5_interpret(IrdaCommonDecoder* decoder); -const IrdaProtocolSpecification* irda_rc5_get_spec(IrdaProtocol protocol); - -extern const IrdaCommonProtocolSpec protocol_rc5; - -/*************************************************************************************************** -* Sony SIRC protocol description -* https://www.sbprojects.net/knowledge/ir/sirc.php -* http://picprojects.org.uk/ -**************************************************************************************************** -* Preamble Preamble Pulse Width Modulation Pause Entirely repeat -* mark space up to period message.. -* -* 2400 600 12/15/20 bits (600,1200) ...45000 2400 600 -* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ __________ _ _ -* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ____________________ __________ _ -* | command | address | -* SIRC | 7b LSB | 5b LSB | -* SIRC15 | 7b LSB | 8b LSB | -* SIRC20 | 7b LSB | 13b LSB | -* -* No way to determine either next message is repeat or not, -* so recognize only fact message received. Sony remotes always send at least 3 messages. -* Assume 8 last extended bits for SIRC20 are address bits. -***************************************************************************************************/ - -#define IRDA_SIRC_CARRIER_FREQUENCY 40000 -#define IRDA_SIRC_DUTY_CYCLE 0.33 -#define IRDA_SIRC_PREAMBLE_MARK 2400 -#define IRDA_SIRC_PREAMBLE_SPACE 600 -#define IRDA_SIRC_BIT1_MARK 1200 -#define IRDA_SIRC_BIT1_SPACE 600 -#define IRDA_SIRC_BIT0_MARK 600 -#define IRDA_SIRC_BIT0_SPACE 600 -#define IRDA_SIRC_PREAMBLE_TOLERANCE 200 // us -#define IRDA_SIRC_BIT_TOLERANCE 120 // us -#define IRDA_SIRC_SILENCE 10000 -#define IRDA_SIRC_MIN_SPLIT_TIME (IRDA_SIRC_SILENCE - 1000) -#define IRDA_SIRC_REPEAT_PERIOD 45000 - -void* irda_decoder_sirc_alloc(void); -void irda_decoder_sirc_reset(void* decoder); -IrdaMessage* irda_decoder_sirc_check_ready(void* decoder); -uint32_t irda_decoder_sirc_get_timeout(void* decoder); -void irda_decoder_sirc_free(void* decoder); -IrdaMessage* irda_decoder_sirc_decode(void* decoder, bool level, uint32_t duration); -void* irda_encoder_sirc_alloc(void); -void irda_encoder_sirc_reset(void* encoder_ptr, const IrdaMessage* message); -void irda_encoder_sirc_free(void* decoder); -IrdaStatus irda_encoder_sirc_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); -bool irda_decoder_sirc_interpret(IrdaCommonDecoder* decoder); -const IrdaProtocolSpecification* irda_sirc_get_spec(IrdaProtocol protocol); -IrdaStatus - irda_encoder_sirc_encode_repeat(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level); - -extern const IrdaCommonProtocolSpec protocol_sirc; diff --git a/lib/irda/encoder_decoder/nec/irda_nec_spec.c b/lib/irda/encoder_decoder/nec/irda_nec_spec.c deleted file mode 100644 index b193b6b4..00000000 --- a/lib/irda/encoder_decoder/nec/irda_nec_spec.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" - -static const IrdaProtocolSpecification irda_nec_protocol_specification = { - .name = "NEC", - .address_length = 8, - .command_length = 8, - .frequency = IRDA_COMMON_CARRIER_FREQUENCY, - .duty_cycle = IRDA_COMMON_DUTY_CYCLE, -}; - -static const IrdaProtocolSpecification irda_necext_protocol_specification = { - .name = "NECext", - .address_length = 16, - .command_length = 16, - .frequency = IRDA_COMMON_CARRIER_FREQUENCY, - .duty_cycle = IRDA_COMMON_DUTY_CYCLE, -}; - -static const IrdaProtocolSpecification irda_nec42_protocol_specification = { - .name = "NEC42", - .address_length = 13, - .command_length = 8, - .frequency = IRDA_COMMON_CARRIER_FREQUENCY, - .duty_cycle = IRDA_COMMON_DUTY_CYCLE, -}; - -static const IrdaProtocolSpecification irda_nec42ext_protocol_specification = { - .name = "NEC42ext", - .address_length = 26, - .command_length = 16, - .frequency = IRDA_COMMON_CARRIER_FREQUENCY, - .duty_cycle = IRDA_COMMON_DUTY_CYCLE, -}; - -const IrdaProtocolSpecification* irda_nec_get_spec(IrdaProtocol protocol) { - if(protocol == IrdaProtocolNEC) - return &irda_nec_protocol_specification; - else if(protocol == IrdaProtocolNECext) - return &irda_necext_protocol_specification; - else if(protocol == IrdaProtocolNEC42) - return &irda_nec42_protocol_specification; - else if(protocol == IrdaProtocolNEC42ext) - return &irda_nec42ext_protocol_specification; - else - return NULL; -} diff --git a/lib/irda/encoder_decoder/rc5/irda_decoder_rc5.c b/lib/irda/encoder_decoder/rc5/irda_decoder_rc5.c deleted file mode 100644 index a9c72e65..00000000 --- a/lib/irda/encoder_decoder/rc5/irda_decoder_rc5.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "irda.h" -#include -#include -#include -#include -#include "../irda_i.h" -#include "../irda_protocol_defs_i.h" - -typedef struct { - IrdaCommonDecoder* common_decoder; - bool toggle; -} IrdaRc5Decoder; - -IrdaMessage* irda_decoder_rc5_check_ready(void* ctx) { - IrdaRc5Decoder* decoder = ctx; - return irda_common_decoder_check_ready(decoder->common_decoder); -} - -bool irda_decoder_rc5_interpret(IrdaCommonDecoder* decoder) { - furi_assert(decoder); - - bool result = false; - uint32_t* data = (void*)&decoder->data[0]; - /* Manchester (inverse): - * 0->1 : 1 - * 1->0 : 0 - */ - decoder->data[0] = ~decoder->data[0]; - decoder->data[1] = ~decoder->data[1]; - - // MSB first - uint8_t address = reverse((uint8_t)decoder->data[0]) & 0x1F; - uint8_t command = (reverse((uint8_t)decoder->data[1]) >> 2) & 0x3F; - bool start_bit1 = *data & 0x01; - bool start_bit2 = *data & 0x02; - bool toggle = !!(*data & 0x04); - - if(start_bit1 == 1) { - IrdaProtocol protocol = start_bit2 ? IrdaProtocolRC5 : IrdaProtocolRC5X; - IrdaMessage* message = &decoder->message; - IrdaRc5Decoder* rc5_decoder = decoder->context; - bool* prev_toggle = &rc5_decoder->toggle; - if((message->address == address) && (message->command == command) && - (message->protocol == protocol)) { - message->repeat = (toggle == *prev_toggle); - } else { - message->repeat = false; - } - *prev_toggle = toggle; - message->command = command; - message->address = address; - message->protocol = protocol; - - result = true; - } - - return result; -} - -void* irda_decoder_rc5_alloc(void) { - IrdaRc5Decoder* decoder = malloc(sizeof(IrdaRc5Decoder)); - decoder->toggle = false; - decoder->common_decoder = irda_common_decoder_alloc(&protocol_rc5); - decoder->common_decoder->context = decoder; - return decoder; -} - -IrdaMessage* irda_decoder_rc5_decode(void* decoder, bool level, uint32_t duration) { - IrdaRc5Decoder* decoder_rc5 = decoder; - return irda_common_decode(decoder_rc5->common_decoder, level, duration); -} - -void irda_decoder_rc5_free(void* decoder) { - IrdaRc5Decoder* decoder_rc5 = decoder; - irda_common_decoder_free(decoder_rc5->common_decoder); - free(decoder_rc5); -} - -void irda_decoder_rc5_reset(void* decoder) { - IrdaRc5Decoder* decoder_rc5 = decoder; - irda_common_decoder_reset(decoder_rc5->common_decoder); -} diff --git a/lib/irda/encoder_decoder/rc5/irda_encoder_rc5.c b/lib/irda/encoder_decoder/rc5/irda_encoder_rc5.c deleted file mode 100644 index 36e742cd..00000000 --- a/lib/irda/encoder_decoder/rc5/irda_encoder_rc5.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "furi/memmgr.h" -#include "irda.h" -#include "common/irda_common_i.h" -#include "irda_protocol_defs_i.h" -#include -#include "../irda_i.h" - -typedef struct IrdaEncoderRC5 { - IrdaCommonEncoder* common_encoder; - bool toggle_bit; -} IrdaEncoderRC5; - -void irda_encoder_rc5_reset(void* encoder_ptr, const IrdaMessage* message) { - furi_assert(encoder_ptr); - - IrdaEncoderRC5* encoder = encoder_ptr; - IrdaCommonEncoder* common_encoder = encoder->common_encoder; - irda_common_encoder_reset(common_encoder); - - uint32_t* data = (void*)common_encoder->data; - /* RC5 */ - *data |= 0x01; // start bit - if(message->protocol == IrdaProtocolRC5) { - *data |= 0x02; // start bit - } - *data |= encoder->toggle_bit ? 0x04 : 0; - *data |= (reverse(message->address) >> 3) << 3; /* address 5 bit */ - *data |= (reverse(message->command) >> 2) << 8; /* command 6 bit */ - - common_encoder->data[0] = ~common_encoder->data[0]; - common_encoder->data[1] = ~common_encoder->data[1]; - - common_encoder->bits_to_encode = common_encoder->protocol->databit_len[0]; - encoder->toggle_bit ^= 1; -} - -IrdaStatus irda_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration, bool* level) { - IrdaEncoderRC5* encoder = encoder_ptr; - return irda_common_encode(encoder->common_encoder, duration, level); -} - -void* irda_encoder_rc5_alloc(void) { - IrdaEncoderRC5* encoder = malloc(sizeof(IrdaEncoderRC5)); - encoder->common_encoder = irda_common_encoder_alloc(&protocol_rc5); - encoder->toggle_bit = false; - return encoder; -} - -void irda_encoder_rc5_free(void* encoder_ptr) { - furi_assert(encoder_ptr); - - IrdaEncoderRC5* encoder = encoder_ptr; - free(encoder->common_encoder); - free(encoder); -} diff --git a/lib/irda/encoder_decoder/rc5/irda_rc5_spec.c b/lib/irda/encoder_decoder/rc5/irda_rc5_spec.c deleted file mode 100644 index 89d597f2..00000000 --- a/lib/irda/encoder_decoder/rc5/irda_rc5_spec.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" - -static const IrdaProtocolSpecification irda_rc5_protocol_specification = { - .name = "RC5", - .address_length = 5, - .command_length = 6, - .frequency = IRDA_RC5_CARRIER_FREQUENCY, - .duty_cycle = IRDA_RC5_DUTY_CYCLE, -}; - -static const IrdaProtocolSpecification irda_rc5x_protocol_specification = { - .name = "RC5X", - .address_length = 5, - .command_length = 7, - .frequency = IRDA_RC5_CARRIER_FREQUENCY, - .duty_cycle = IRDA_RC5_DUTY_CYCLE, -}; - -const IrdaProtocolSpecification* irda_rc5_get_spec(IrdaProtocol protocol) { - if(protocol == IrdaProtocolRC5) - return &irda_rc5_protocol_specification; - else if(protocol == IrdaProtocolRC5X) - return &irda_rc5x_protocol_specification; - else - return NULL; -} diff --git a/lib/irda/encoder_decoder/rc6/irda_encoder_rc6.c b/lib/irda/encoder_decoder/rc6/irda_encoder_rc6.c deleted file mode 100644 index 41a397cf..00000000 --- a/lib/irda/encoder_decoder/rc6/irda_encoder_rc6.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "furi/memmgr.h" -#include "irda.h" -#include "common/irda_common_i.h" -#include "irda_protocol_defs_i.h" -#include -#include "../irda_i.h" - -typedef struct IrdaEncoderRC6 { - IrdaCommonEncoder* common_encoder; - bool toggle_bit; -} IrdaEncoderRC6; - -void irda_encoder_rc6_reset(void* encoder_ptr, const IrdaMessage* message) { - furi_assert(encoder_ptr); - - IrdaEncoderRC6* encoder = encoder_ptr; - IrdaCommonEncoder* common_encoder = encoder->common_encoder; - irda_common_encoder_reset(common_encoder); - - uint32_t* data = (void*)common_encoder->data; - *data |= 0x01; // start bit - (void)*data; // 3 bits for mode == 0 - *data |= encoder->toggle_bit ? 0x10 : 0; - *data |= reverse(message->address) << 5; - *data |= reverse(message->command) << 13; - - common_encoder->bits_to_encode = common_encoder->protocol->databit_len[0]; - encoder->toggle_bit ^= 1; -} - -IrdaStatus irda_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration, bool* level) { - IrdaEncoderRC6* encoder = encoder_ptr; - return irda_common_encode(encoder->common_encoder, duration, level); -} - -void* irda_encoder_rc6_alloc(void) { - IrdaEncoderRC6* encoder = malloc(sizeof(IrdaEncoderRC6)); - encoder->common_encoder = irda_common_encoder_alloc(&protocol_rc6); - encoder->toggle_bit = false; - return encoder; -} - -void irda_encoder_rc6_free(void* encoder_ptr) { - furi_assert(encoder_ptr); - - IrdaEncoderRC6* encoder = encoder_ptr; - free(encoder->common_encoder); - free(encoder); -} - -IrdaStatus irda_encoder_rc6_encode_manchester( - IrdaCommonEncoder* common_encoder, - uint32_t* duration, - bool* polarity) { - IrdaStatus status = IrdaStatusError; - - bool toggle_bit = (common_encoder->bits_encoded == 4); - status = irda_common_encode_manchester(common_encoder, duration, polarity); - if(toggle_bit) *duration *= 2; - return status; -} diff --git a/lib/irda/encoder_decoder/rc6/irda_rc6_spec.c b/lib/irda/encoder_decoder/rc6/irda_rc6_spec.c deleted file mode 100644 index 5f333bb0..00000000 --- a/lib/irda/encoder_decoder/rc6/irda_rc6_spec.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" - -static const IrdaProtocolSpecification irda_rc6_protocol_specification = { - .name = "RC6", - .address_length = 8, - .command_length = 8, - .frequency = IRDA_RC6_CARRIER_FREQUENCY, - .duty_cycle = IRDA_RC6_DUTY_CYCLE, -}; - -const IrdaProtocolSpecification* irda_rc6_get_spec(IrdaProtocol protocol) { - if(protocol == IrdaProtocolRC6) - return &irda_rc6_protocol_specification; - else - return NULL; -} diff --git a/lib/irda/encoder_decoder/samsung/irda_decoder_samsung.c b/lib/irda/encoder_decoder/samsung/irda_decoder_samsung.c deleted file mode 100644 index 68fa41dd..00000000 --- a/lib/irda/encoder_decoder/samsung/irda_decoder_samsung.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "irda.h" -#include "irda_protocol_defs_i.h" -#include -#include -#include -#include "../irda_i.h" - -IrdaMessage* irda_decoder_samsung32_check_ready(void* ctx) { - return irda_common_decoder_check_ready(ctx); -} - -bool irda_decoder_samsung32_interpret(IrdaCommonDecoder* decoder) { - furi_assert(decoder); - - bool result = false; - uint8_t address1 = decoder->data[0]; - uint8_t address2 = decoder->data[1]; - uint8_t command = decoder->data[2]; - uint8_t command_inverse = decoder->data[3]; - - if((address1 == address2) && (command == (uint8_t)~command_inverse)) { - decoder->message.command = command; - decoder->message.address = address1; - decoder->message.protocol = IrdaProtocolSamsung32; - decoder->message.repeat = false; - result = true; - } - - return result; -} - -// timings start from Space (delay between message and repeat) -IrdaStatus irda_decoder_samsung32_decode_repeat(IrdaCommonDecoder* decoder) { - furi_assert(decoder); - - float preamble_tolerance = decoder->protocol->timings.preamble_tolerance; - uint32_t bit_tolerance = decoder->protocol->timings.bit_tolerance; - IrdaStatus status = IrdaStatusError; - - if(decoder->timings_cnt < 6) return IrdaStatusOk; - - if((decoder->timings[0] > IRDA_SAMSUNG_REPEAT_PAUSE_MIN) && - (decoder->timings[0] < IRDA_SAMSUNG_REPEAT_PAUSE_MAX) && - MATCH_TIMING(decoder->timings[1], IRDA_SAMSUNG_REPEAT_MARK, preamble_tolerance) && - MATCH_TIMING(decoder->timings[2], IRDA_SAMSUNG_REPEAT_SPACE, preamble_tolerance) && - MATCH_TIMING(decoder->timings[3], decoder->protocol->timings.bit1_mark, bit_tolerance) && - MATCH_TIMING(decoder->timings[4], decoder->protocol->timings.bit1_space, bit_tolerance) && - MATCH_TIMING(decoder->timings[5], decoder->protocol->timings.bit1_mark, bit_tolerance)) { - status = IrdaStatusReady; - decoder->timings_cnt = 0; - } else { - status = IrdaStatusError; - } - - return status; -} - -void* irda_decoder_samsung32_alloc(void) { - return irda_common_decoder_alloc(&protocol_samsung32); -} - -IrdaMessage* irda_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration) { - return irda_common_decode(decoder, level, duration); -} - -void irda_decoder_samsung32_free(void* decoder) { - irda_common_decoder_free(decoder); -} - -void irda_decoder_samsung32_reset(void* decoder) { - irda_common_decoder_reset(decoder); -} diff --git a/lib/irda/encoder_decoder/samsung/irda_samsung_spec.c b/lib/irda/encoder_decoder/samsung/irda_samsung_spec.c deleted file mode 100644 index dcc532ca..00000000 --- a/lib/irda/encoder_decoder/samsung/irda_samsung_spec.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" - -static const IrdaProtocolSpecification irda_samsung32_protocol_specification = { - .name = "Samsung32", - .address_length = 8, - .command_length = 8, - .frequency = IRDA_COMMON_CARRIER_FREQUENCY, - .duty_cycle = IRDA_COMMON_DUTY_CYCLE, -}; - -const IrdaProtocolSpecification* irda_samsung32_get_spec(IrdaProtocol protocol) { - if(protocol == IrdaProtocolSamsung32) - return &irda_samsung32_protocol_specification; - else - return NULL; -} diff --git a/lib/irda/encoder_decoder/sirc/irda_decoder_sirc.c b/lib/irda/encoder_decoder/sirc/irda_decoder_sirc.c deleted file mode 100644 index 9bcb6801..00000000 --- a/lib/irda/encoder_decoder/sirc/irda_decoder_sirc.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "common/irda_common_i.h" -#include "irda.h" -#include "irda_protocol_defs_i.h" -#include -#include -#include -#include "../irda_i.h" - -IrdaMessage* irda_decoder_sirc_check_ready(void* ctx) { - return irda_common_decoder_check_ready(ctx); -} - -bool irda_decoder_sirc_interpret(IrdaCommonDecoder* decoder) { - furi_assert(decoder); - - uint32_t* data = (void*)&decoder->data[0]; - uint16_t address = 0; - uint8_t command = 0; - IrdaProtocol protocol = IrdaProtocolUnknown; - - if(decoder->databit_cnt == 12) { - address = (*data >> 7) & 0x1F; - command = *data & 0x7F; - protocol = IrdaProtocolSIRC; - } else if(decoder->databit_cnt == 15) { - address = (*data >> 7) & 0xFF; - command = *data & 0x7F; - protocol = IrdaProtocolSIRC15; - } else if(decoder->databit_cnt == 20) { - address = (*data >> 7) & 0x1FFF; - command = *data & 0x7F; - protocol = IrdaProtocolSIRC20; - } else { - return false; - } - - decoder->message.protocol = protocol; - decoder->message.address = address; - decoder->message.command = command; - /* SIRC doesn't specify repeat detection */ - decoder->message.repeat = false; - - return true; -} - -void* irda_decoder_sirc_alloc(void) { - return irda_common_decoder_alloc(&protocol_sirc); -} - -IrdaMessage* irda_decoder_sirc_decode(void* decoder, bool level, uint32_t duration) { - return irda_common_decode(decoder, level, duration); -} - -void irda_decoder_sirc_free(void* decoder) { - irda_common_decoder_free(decoder); -} - -void irda_decoder_sirc_reset(void* decoder) { - irda_common_decoder_reset(decoder); -} diff --git a/lib/irda/encoder_decoder/sirc/irda_encoder_sirc.c b/lib/irda/encoder_decoder/sirc/irda_encoder_sirc.c deleted file mode 100644 index 73c4f622..00000000 --- a/lib/irda/encoder_decoder/sirc/irda_encoder_sirc.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "furi/check.h" -#include "irda.h" -#include "common/irda_common_i.h" -#include -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" -#include - -void irda_encoder_sirc_reset(void* encoder_ptr, const IrdaMessage* message) { - furi_assert(encoder_ptr); - furi_assert(message); - - IrdaCommonEncoder* encoder = encoder_ptr; - irda_common_encoder_reset(encoder); - - uint32_t* data = (void*)encoder->data; - - if(message->protocol == IrdaProtocolSIRC) { - *data = (message->command & 0x7F); - *data |= (message->address & 0x1F) << 7; - encoder->bits_to_encode = 12; - } else if(message->protocol == IrdaProtocolSIRC15) { - *data = (message->command & 0x7F); - *data |= (message->address & 0xFF) << 7; - encoder->bits_to_encode = 15; - } else if(message->protocol == IrdaProtocolSIRC20) { - *data = (message->command & 0x7F); - *data |= (message->address & 0x1FFF) << 7; - encoder->bits_to_encode = 20; - } else { - furi_assert(0); - } -} - -IrdaStatus - irda_encoder_sirc_encode_repeat(IrdaCommonEncoder* encoder, uint32_t* duration, bool* level) { - furi_assert(encoder); - - furi_assert(encoder->timings_encoded == (1 + 2 + encoder->bits_to_encode * 2 - 1)); - - furi_assert(encoder->timings_sum < IRDA_SIRC_REPEAT_PERIOD); - *duration = IRDA_SIRC_REPEAT_PERIOD - encoder->timings_sum; - *level = false; - - encoder->timings_sum = 0; - encoder->timings_encoded = 1; - encoder->bits_encoded = 0; - encoder->state = IrdaCommonEncoderStatePreamble; - - return IrdaStatusOk; -} - -void* irda_encoder_sirc_alloc(void) { - return irda_common_encoder_alloc(&protocol_sirc); -} - -void irda_encoder_sirc_free(void* encoder_ptr) { - irda_common_encoder_free(encoder_ptr); -} - -IrdaStatus irda_encoder_sirc_encode(void* encoder_ptr, uint32_t* duration, bool* level) { - IrdaCommonEncoder* encoder = encoder_ptr; - - IrdaStatus status = irda_common_encode(encoder, duration, level); - if((status == IrdaStatusOk) && (encoder->bits_encoded == encoder->bits_to_encode)) { - furi_assert(!*level); - status = IrdaStatusDone; - encoder->state = IrdaCommonEncoderStateEncodeRepeat; - } - return status; -} diff --git a/lib/irda/encoder_decoder/sirc/irda_sirc_spec.c b/lib/irda/encoder_decoder/sirc/irda_sirc_spec.c deleted file mode 100644 index fc06d594..00000000 --- a/lib/irda/encoder_decoder/sirc/irda_sirc_spec.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "../irda_i.h" -#include "irda_protocol_defs_i.h" - -static const IrdaProtocolSpecification irda_sirc_protocol_specification = { - .name = "SIRC", - .address_length = 5, - .command_length = 7, - .frequency = IRDA_SIRC_CARRIER_FREQUENCY, - .duty_cycle = IRDA_SIRC_DUTY_CYCLE, -}; - -static const IrdaProtocolSpecification irda_sirc15_protocol_specification = { - .name = "SIRC15", - .address_length = 8, - .command_length = 7, - .frequency = IRDA_SIRC_CARRIER_FREQUENCY, - .duty_cycle = IRDA_SIRC_DUTY_CYCLE, -}; - -static const IrdaProtocolSpecification irda_sirc20_protocol_specification = { - .name = "SIRC20", - .address_length = 13, - .command_length = 7, - .frequency = IRDA_SIRC_CARRIER_FREQUENCY, - .duty_cycle = IRDA_SIRC_DUTY_CYCLE, -}; - -const IrdaProtocolSpecification* irda_sirc_get_spec(IrdaProtocol protocol) { - if(protocol == IrdaProtocolSIRC) - return &irda_sirc_protocol_specification; - else if(protocol == IrdaProtocolSIRC15) - return &irda_sirc15_protocol_specification; - else if(protocol == IrdaProtocolSIRC20) - return &irda_sirc20_protocol_specification; - else - return NULL; -} diff --git a/lib/irda/worker/irda_transmit.c b/lib/irda/worker/irda_transmit.c deleted file mode 100644 index d66e1e38..00000000 --- a/lib/irda/worker/irda_transmit.c +++ /dev/null @@ -1,114 +0,0 @@ -#include "irda.h" -#include -#include -#include -#include -#include -#include - -static uint32_t irda_tx_number_of_transmissions = 0; -static uint32_t irda_tx_raw_timings_index = 0; -static uint32_t irda_tx_raw_timings_number = 0; -static uint32_t irda_tx_raw_start_from_mark = 0; -static bool irda_tx_raw_add_silence = false; - -FuriHalIrdaTxGetDataState - irda_get_raw_data_callback(void* context, uint32_t* duration, bool* level) { - furi_assert(duration); - furi_assert(level); - furi_assert(context); - - FuriHalIrdaTxGetDataState state = FuriHalIrdaTxGetDataStateOk; - const uint32_t* timings = context; - - if(irda_tx_raw_add_silence && (irda_tx_raw_timings_index == 0)) { - irda_tx_raw_add_silence = false; - *level = false; - *duration = IRDA_RAW_TX_TIMING_DELAY_US; - } else { - *level = irda_tx_raw_start_from_mark ^ (irda_tx_raw_timings_index % 2); - *duration = timings[irda_tx_raw_timings_index++]; - } - - if(irda_tx_raw_timings_number == irda_tx_raw_timings_index) { - state = FuriHalIrdaTxGetDataStateLastDone; - } - - return state; -} - -void irda_send_raw_ext( - const uint32_t timings[], - uint32_t timings_cnt, - bool start_from_mark, - uint32_t frequency, - float duty_cycle) { - furi_assert(timings); - - irda_tx_raw_start_from_mark = start_from_mark; - irda_tx_raw_timings_index = 0; - irda_tx_raw_timings_number = timings_cnt; - irda_tx_raw_add_silence = start_from_mark; - furi_hal_irda_async_tx_set_data_isr_callback(irda_get_raw_data_callback, (void*)timings); - furi_hal_irda_async_tx_start(frequency, duty_cycle); - furi_hal_irda_async_tx_wait_termination(); - - furi_assert(!furi_hal_irda_is_busy()); -} - -void irda_send_raw(const uint32_t timings[], uint32_t timings_cnt, bool start_from_mark) { - irda_send_raw_ext( - timings, - timings_cnt, - start_from_mark, - IRDA_COMMON_CARRIER_FREQUENCY, - IRDA_COMMON_DUTY_CYCLE); -} - -FuriHalIrdaTxGetDataState irda_get_data_callback(void* context, uint32_t* duration, bool* level) { - FuriHalIrdaTxGetDataState state = FuriHalIrdaTxGetDataStateLastDone; - IrdaEncoderHandler* handler = context; - IrdaStatus status = IrdaStatusError; - - if(irda_tx_number_of_transmissions > 0) { - status = irda_encode(handler, duration, level); - } - - if(status == IrdaStatusError) { - state = FuriHalIrdaTxGetDataStateLastDone; - *duration = 0; - *level = 0; - } else if(status == IrdaStatusOk) { - state = FuriHalIrdaTxGetDataStateOk; - } else if(status == IrdaStatusDone) { - state = FuriHalIrdaTxGetDataStateDone; - if(--irda_tx_number_of_transmissions == 0) { - state = FuriHalIrdaTxGetDataStateLastDone; - } - } else { - furi_crash(NULL); - } - - return state; -} - -void irda_send(const IrdaMessage* message, int times) { - furi_assert(message); - furi_assert(times); - furi_assert(irda_is_protocol_valid(message->protocol)); - - IrdaEncoderHandler* handler = irda_alloc_encoder(); - irda_reset_encoder(handler, message); - irda_tx_number_of_transmissions = times; - - uint32_t frequency = irda_get_protocol_frequency(message->protocol); - float duty_cycle = irda_get_protocol_duty_cycle(message->protocol); - - furi_hal_irda_async_tx_set_data_isr_callback(irda_get_data_callback, handler); - furi_hal_irda_async_tx_start(frequency, duty_cycle); - furi_hal_irda_async_tx_wait_termination(); - - irda_free_encoder(handler); - - furi_assert(!furi_hal_irda_is_busy()); -} diff --git a/lib/irda/worker/irda_worker.c b/lib/irda/worker/irda_worker.c deleted file mode 100644 index 40636dfd..00000000 --- a/lib/irda/worker/irda_worker.c +++ /dev/null @@ -1,598 +0,0 @@ -#include "furi/check.h" -#include "furi/common_defines.h" -#include "sys/_stdint.h" -#include "irda_worker.h" -#include -#include -#include -#include -#include - -#include -#include - -#define IRDA_WORKER_RX_TIMEOUT IRDA_RAW_RX_TIMING_DELAY_US - -#define IRDA_WORKER_RX_RECEIVED 0x01 -#define IRDA_WORKER_RX_TIMEOUT_RECEIVED 0x02 -#define IRDA_WORKER_OVERRUN 0x04 -#define IRDA_WORKER_EXIT 0x08 -#define IRDA_WORKER_TX_FILL_BUFFER 0x10 -#define IRDA_WORKER_TX_MESSAGE_SENT 0x20 - -#define IRDA_WORKER_ALL_RX_EVENTS \ - (IRDA_WORKER_RX_RECEIVED | IRDA_WORKER_RX_TIMEOUT_RECEIVED | IRDA_WORKER_OVERRUN | \ - IRDA_WORKER_EXIT) - -#define IRDA_WORKER_ALL_TX_EVENTS \ - (IRDA_WORKER_TX_FILL_BUFFER | IRDA_WORKER_TX_MESSAGE_SENT | IRDA_WORKER_EXIT) - -#define IRDA_WORKER_ALL_EVENTS (IRDA_WORKER_ALL_RX_EVENTS | IRDA_WORKER_ALL_TX_EVENTS) - -typedef enum { - IrdaWorkerStateIdle, - IrdaWorkerStateRunRx, - IrdaWorkerStateRunTx, - IrdaWorkerStateWaitTxEnd, - IrdaWorkerStateStopTx, - IrdaWorkerStateStartTx, -} IrdaWorkerState; - -struct IrdaWorkerSignal { - bool decoded; - size_t timings_cnt; - union { - IrdaMessage message; - /* +1 is for pause we add at the beginning */ - uint32_t timings[MAX_TIMINGS_AMOUNT + 1]; - }; -}; - -struct IrdaWorker { - FuriThread* thread; - StreamBufferHandle_t stream; - osEventFlagsId_t events; - - IrdaWorkerSignal signal; - IrdaWorkerState state; - IrdaEncoderHandler* irda_encoder; - IrdaDecoderHandler* irda_decoder; - NotificationApp* notification; - bool blink_enable; - - union { - struct { - IrdaWorkerGetSignalCallback get_signal_callback; - IrdaWorkerMessageSentCallback message_sent_callback; - void* get_signal_context; - void* message_sent_context; - uint32_t frequency; - float duty_cycle; - uint32_t tx_raw_cnt; - bool need_reinitialization; - bool steady_signal_sent; - } tx; - struct { - IrdaWorkerReceivedSignalCallback received_signal_callback; - void* received_signal_context; - bool overrun; - } rx; - }; -}; - -typedef struct { - uint32_t duration; - bool level; - FuriHalIrdaTxGetDataState state; -} IrdaWorkerTiming; - -static int32_t irda_worker_tx_thread(void* context); -static FuriHalIrdaTxGetDataState - irda_worker_furi_hal_data_isr_callback(void* context, uint32_t* duration, bool* level); -static void irda_worker_furi_hal_message_sent_isr_callback(void* context); - -static void irda_worker_rx_timeout_callback(void* context) { - IrdaWorker* instance = context; - uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_RX_TIMEOUT_RECEIVED); - furi_check(flags_set & IRDA_WORKER_RX_TIMEOUT_RECEIVED); -} - -static void irda_worker_rx_callback(void* context, bool level, uint32_t duration) { - IrdaWorker* instance = context; - - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - furi_assert(duration != 0); - LevelDuration level_duration = level_duration_make(level, duration); - - size_t ret = xStreamBufferSendFromISR( - instance->stream, &level_duration, sizeof(LevelDuration), &xHigherPriorityTaskWoken); - uint32_t events = (ret == sizeof(LevelDuration)) ? IRDA_WORKER_RX_RECEIVED : - IRDA_WORKER_OVERRUN; - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - - uint32_t flags_set = osEventFlagsSet(instance->events, events); - furi_check(flags_set & events); -} - -static void irda_worker_process_timeout(IrdaWorker* instance) { - if(instance->signal.timings_cnt < 2) return; - - const IrdaMessage* message_decoded = irda_check_decoder_ready(instance->irda_decoder); - if(message_decoded) { - instance->signal.message = *message_decoded; - instance->signal.timings_cnt = 0; - instance->signal.decoded = true; - } else { - instance->signal.decoded = false; - } - if(instance->rx.received_signal_callback) - instance->rx.received_signal_callback( - instance->rx.received_signal_context, &instance->signal); -} - -static void irda_worker_process_timings(IrdaWorker* instance, uint32_t duration, bool level) { - const IrdaMessage* message_decoded = irda_decode(instance->irda_decoder, level, duration); - if(message_decoded) { - instance->signal.message = *message_decoded; - instance->signal.timings_cnt = 0; - instance->signal.decoded = true; - if(instance->rx.received_signal_callback) - instance->rx.received_signal_callback( - instance->rx.received_signal_context, &instance->signal); - } else { - /* Skip first timing if it starts from Space */ - if((instance->signal.timings_cnt == 0) && !level) { - return; - } - - if(instance->signal.timings_cnt < MAX_TIMINGS_AMOUNT) { - instance->signal.timings[instance->signal.timings_cnt] = duration; - ++instance->signal.timings_cnt; - } else { - uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_OVERRUN); - furi_check(flags_set & IRDA_WORKER_OVERRUN); - instance->rx.overrun = true; - } - } -} - -static int32_t irda_worker_rx_thread(void* thread_context) { - IrdaWorker* instance = thread_context; - uint32_t events = 0; - LevelDuration level_duration; - TickType_t last_blink_time = 0; - - while(1) { - events = osEventFlagsWait(instance->events, IRDA_WORKER_ALL_RX_EVENTS, 0, osWaitForever); - furi_check(events & IRDA_WORKER_ALL_RX_EVENTS); /* at least one caught */ - - if(events & IRDA_WORKER_RX_RECEIVED) { - if(!instance->rx.overrun && instance->blink_enable && - ((xTaskGetTickCount() - last_blink_time) > 80)) { - last_blink_time = xTaskGetTickCount(); - notification_message(instance->notification, &sequence_blink_blue_10); - } - if(instance->signal.timings_cnt == 0) - notification_message(instance->notification, &sequence_display_on); - while(sizeof(LevelDuration) == - xStreamBufferReceive( - instance->stream, &level_duration, sizeof(LevelDuration), 0)) { - if(!instance->rx.overrun) { - bool level = level_duration_get_level(level_duration); - uint32_t duration = level_duration_get_duration(level_duration); - irda_worker_process_timings(instance, duration, level); - } - } - } - if(events & IRDA_WORKER_OVERRUN) { - printf("#"); - irda_reset_decoder(instance->irda_decoder); - instance->signal.timings_cnt = 0; - if(instance->blink_enable) - notification_message(instance->notification, &sequence_set_red_255); - } - if(events & IRDA_WORKER_RX_TIMEOUT_RECEIVED) { - if(instance->rx.overrun) { - printf("\nOVERRUN, max samples: %d\n", MAX_TIMINGS_AMOUNT); - instance->rx.overrun = false; - if(instance->blink_enable) - notification_message(instance->notification, &sequence_reset_red); - } else { - irda_worker_process_timeout(instance); - } - instance->signal.timings_cnt = 0; - } - if(events & IRDA_WORKER_EXIT) break; - } - - return 0; -} - -void irda_worker_rx_set_received_signal_callback( - IrdaWorker* instance, - IrdaWorkerReceivedSignalCallback callback, - void* context) { - furi_assert(instance); - instance->rx.received_signal_callback = callback; - instance->rx.received_signal_context = context; -} - -IrdaWorker* irda_worker_alloc() { - IrdaWorker* instance = malloc(sizeof(IrdaWorker)); - - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "IrdaWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - - size_t buffer_size = - MAX(sizeof(IrdaWorkerTiming) * (MAX_TIMINGS_AMOUNT + 1), - sizeof(LevelDuration) * MAX_TIMINGS_AMOUNT); - instance->stream = xStreamBufferCreate(buffer_size, sizeof(IrdaWorkerTiming)); - instance->irda_decoder = irda_alloc_decoder(); - instance->irda_encoder = irda_alloc_encoder(); - instance->blink_enable = false; - instance->notification = furi_record_open("notification"); - instance->state = IrdaWorkerStateIdle; - instance->events = osEventFlagsNew(NULL); - - return instance; -} - -void irda_worker_free(IrdaWorker* instance) { - furi_assert(instance); - furi_assert(instance->state == IrdaWorkerStateIdle); - - furi_record_close("notification"); - irda_free_decoder(instance->irda_decoder); - irda_free_encoder(instance->irda_encoder); - vStreamBufferDelete(instance->stream); - furi_thread_free(instance->thread); - osEventFlagsDelete(instance->events); - - free(instance); -} - -void irda_worker_rx_start(IrdaWorker* instance) { - furi_assert(instance); - furi_assert(instance->state == IrdaWorkerStateIdle); - - xStreamBufferSetTriggerLevel(instance->stream, sizeof(LevelDuration)); - - osEventFlagsClear(instance->events, IRDA_WORKER_ALL_EVENTS); - furi_thread_set_callback(instance->thread, irda_worker_rx_thread); - furi_thread_start(instance->thread); - - furi_hal_irda_async_rx_set_capture_isr_callback(irda_worker_rx_callback, instance); - furi_hal_irda_async_rx_set_timeout_isr_callback(irda_worker_rx_timeout_callback, instance); - furi_hal_irda_async_rx_start(); - furi_hal_irda_async_rx_set_timeout(IRDA_WORKER_RX_TIMEOUT); - - instance->rx.overrun = false; - instance->state = IrdaWorkerStateRunRx; -} - -void irda_worker_rx_stop(IrdaWorker* instance) { - furi_assert(instance); - furi_assert(instance->state == IrdaWorkerStateRunRx); - - furi_hal_irda_async_rx_set_timeout_isr_callback(NULL, NULL); - furi_hal_irda_async_rx_set_capture_isr_callback(NULL, NULL); - furi_hal_irda_async_rx_stop(); - - osEventFlagsSet(instance->events, IRDA_WORKER_EXIT); - furi_thread_join(instance->thread); - - BaseType_t xReturn = xStreamBufferReset(instance->stream); - furi_assert(xReturn == pdPASS); - (void)xReturn; - - instance->state = IrdaWorkerStateIdle; -} - -bool irda_worker_signal_is_decoded(const IrdaWorkerSignal* signal) { - furi_assert(signal); - return signal->decoded; -} - -void irda_worker_get_raw_signal( - const IrdaWorkerSignal* signal, - const uint32_t** timings, - size_t* timings_cnt) { - furi_assert(signal); - furi_assert(timings); - furi_assert(timings_cnt); - - *timings = signal->timings; - *timings_cnt = signal->timings_cnt; -} - -const IrdaMessage* irda_worker_get_decoded_signal(const IrdaWorkerSignal* signal) { - furi_assert(signal); - return &signal->message; -} - -void irda_worker_rx_enable_blink_on_receiving(IrdaWorker* instance, bool enable) { - furi_assert(instance); - instance->blink_enable = enable; -} - -void irda_worker_tx_start(IrdaWorker* instance) { - furi_assert(instance); - furi_assert(instance->state == IrdaWorkerStateIdle); - furi_assert(instance->tx.get_signal_callback); - - // size have to be greater than api hal irda async tx buffer size - xStreamBufferSetTriggerLevel(instance->stream, sizeof(IrdaWorkerTiming)); - - osEventFlagsClear(instance->events, IRDA_WORKER_ALL_EVENTS); - furi_thread_set_callback(instance->thread, irda_worker_tx_thread); - - instance->tx.steady_signal_sent = false; - instance->tx.need_reinitialization = false; - furi_hal_irda_async_tx_set_data_isr_callback(irda_worker_furi_hal_data_isr_callback, instance); - furi_hal_irda_async_tx_set_signal_sent_isr_callback( - irda_worker_furi_hal_message_sent_isr_callback, instance); - - instance->state = IrdaWorkerStateStartTx; - furi_thread_start(instance->thread); -} - -static void irda_worker_furi_hal_message_sent_isr_callback(void* context) { - IrdaWorker* instance = context; - uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_TX_MESSAGE_SENT); - furi_check(flags_set & IRDA_WORKER_TX_MESSAGE_SENT); -} - -static FuriHalIrdaTxGetDataState - irda_worker_furi_hal_data_isr_callback(void* context, uint32_t* duration, bool* level) { - furi_assert(context); - furi_assert(duration); - furi_assert(level); - - IrdaWorker* instance = context; - IrdaWorkerTiming timing; - FuriHalIrdaTxGetDataState state; - - if(sizeof(IrdaWorkerTiming) == - xStreamBufferReceiveFromISR(instance->stream, &timing, sizeof(IrdaWorkerTiming), 0)) { - *level = timing.level; - *duration = timing.duration; - state = timing.state; - } else { - furi_assert(0); - *level = 0; - *duration = 100; - state = FuriHalIrdaTxGetDataStateDone; - } - - uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_TX_FILL_BUFFER); - furi_check(flags_set & IRDA_WORKER_TX_FILL_BUFFER); - - return state; -} - -static bool irda_get_new_signal(IrdaWorker* instance) { - bool new_signal_obtained = false; - - IrdaWorkerGetSignalResponse response = - instance->tx.get_signal_callback(instance->tx.get_signal_context, instance); - if(response == IrdaWorkerGetSignalResponseNew) { - uint32_t new_tx_frequency = 0; - float new_tx_duty_cycle = 0; - if(instance->signal.decoded) { - new_tx_frequency = irda_get_protocol_frequency(instance->signal.message.protocol); - new_tx_duty_cycle = irda_get_protocol_duty_cycle(instance->signal.message.protocol); - } else { - furi_assert(instance->signal.timings_cnt > 1); - new_tx_frequency = IRDA_COMMON_CARRIER_FREQUENCY; - new_tx_duty_cycle = IRDA_COMMON_DUTY_CYCLE; - } - - instance->tx.tx_raw_cnt = 0; - instance->tx.need_reinitialization = (new_tx_frequency != instance->tx.frequency) || - (new_tx_duty_cycle != instance->tx.duty_cycle); - instance->tx.frequency = new_tx_frequency; - instance->tx.duty_cycle = new_tx_duty_cycle; - if(instance->signal.decoded) { - irda_reset_encoder(instance->irda_encoder, &instance->signal.message); - } - new_signal_obtained = true; - } else if(response == IrdaWorkerGetSignalResponseSame) { - new_signal_obtained = true; - /* no need to reinit */ - } else if(response == IrdaWorkerGetSignalResponseStop) { - new_signal_obtained = false; - } else { - furi_assert(0); - } - - return new_signal_obtained; -} - -static bool irda_worker_tx_fill_buffer(IrdaWorker* instance) { - bool new_data_available = true; - IrdaWorkerTiming timing; - IrdaStatus status = IrdaStatusError; - - while(!xStreamBufferIsFull(instance->stream) && !instance->tx.need_reinitialization && - new_data_available) { - if(instance->signal.decoded) { - status = irda_encode(instance->irda_encoder, &timing.duration, &timing.level); - } else { - timing.duration = instance->signal.timings[instance->tx.tx_raw_cnt]; - /* raw always starts from Mark, but we fill it with space delay at start */ - timing.level = (instance->tx.tx_raw_cnt % 2); - ++instance->tx.tx_raw_cnt; - if(instance->tx.tx_raw_cnt >= instance->signal.timings_cnt) { - instance->tx.tx_raw_cnt = 0; - status = IrdaStatusDone; - } else { - status = IrdaStatusOk; - } - } - - if(status == IrdaStatusError) { - furi_assert(0); - new_data_available = false; - break; - } else if(status == IrdaStatusOk) { - timing.state = FuriHalIrdaTxGetDataStateOk; - } else if(status == IrdaStatusDone) { - timing.state = FuriHalIrdaTxGetDataStateDone; - - new_data_available = irda_get_new_signal(instance); - if(instance->tx.need_reinitialization || !new_data_available) { - timing.state = FuriHalIrdaTxGetDataStateLastDone; - } - } else { - furi_assert(0); - } - uint32_t written_size = - xStreamBufferSend(instance->stream, &timing, sizeof(IrdaWorkerTiming), 0); - furi_assert(sizeof(IrdaWorkerTiming) == written_size); - (void)written_size; - } - - return new_data_available; -} - -static int32_t irda_worker_tx_thread(void* thread_context) { - IrdaWorker* instance = thread_context; - furi_assert(instance->state == IrdaWorkerStateStartTx); - furi_assert(thread_context); - - uint32_t events = 0; - bool new_data_available = true; - bool exit = false; - - exit = !irda_get_new_signal(instance); - furi_assert(!exit); - - while(!exit) { - switch(instance->state) { - case IrdaWorkerStateStartTx: - instance->tx.need_reinitialization = false; - new_data_available = irda_worker_tx_fill_buffer(instance); - furi_hal_irda_async_tx_start(instance->tx.frequency, instance->tx.duty_cycle); - - if(!new_data_available) { - instance->state = IrdaWorkerStateStopTx; - } else if(instance->tx.need_reinitialization) { - instance->state = IrdaWorkerStateWaitTxEnd; - } else { - instance->state = IrdaWorkerStateRunTx; - } - - break; - case IrdaWorkerStateStopTx: - furi_hal_irda_async_tx_stop(); - exit = true; - break; - case IrdaWorkerStateWaitTxEnd: - furi_hal_irda_async_tx_wait_termination(); - instance->state = IrdaWorkerStateStartTx; - - events = osEventFlagsGet(instance->events); - if(events & IRDA_WORKER_EXIT) { - exit = true; - break; - } - - break; - case IrdaWorkerStateRunTx: - events = - osEventFlagsWait(instance->events, IRDA_WORKER_ALL_TX_EVENTS, 0, osWaitForever); - furi_check(events & IRDA_WORKER_ALL_TX_EVENTS); /* at least one caught */ - - if(events & IRDA_WORKER_EXIT) { - instance->state = IrdaWorkerStateStopTx; - break; - } - - if(events & IRDA_WORKER_TX_FILL_BUFFER) { - irda_worker_tx_fill_buffer(instance); - - if(instance->tx.need_reinitialization) { - instance->state = IrdaWorkerStateWaitTxEnd; - } - } - - if(events & IRDA_WORKER_TX_MESSAGE_SENT) { - if(instance->tx.message_sent_callback) - instance->tx.message_sent_callback(instance->tx.message_sent_context); - } - break; - default: - furi_assert(0); - break; - } - } - - return 0; -} - -void irda_worker_tx_set_get_signal_callback( - IrdaWorker* instance, - IrdaWorkerGetSignalCallback callback, - void* context) { - furi_assert(instance); - instance->tx.get_signal_callback = callback; - instance->tx.get_signal_context = context; -} - -void irda_worker_tx_set_signal_sent_callback( - IrdaWorker* instance, - IrdaWorkerMessageSentCallback callback, - void* context) { - furi_assert(instance); - instance->tx.message_sent_callback = callback; - instance->tx.message_sent_context = context; -} - -void irda_worker_tx_stop(IrdaWorker* instance) { - furi_assert(instance); - furi_assert(instance->state != IrdaWorkerStateRunRx); - - osEventFlagsSet(instance->events, IRDA_WORKER_EXIT); - furi_thread_join(instance->thread); - furi_hal_irda_async_tx_set_data_isr_callback(NULL, NULL); - furi_hal_irda_async_tx_set_signal_sent_isr_callback(NULL, NULL); - - instance->signal.timings_cnt = 0; - BaseType_t xReturn = pdFAIL; - xReturn = xStreamBufferReset(instance->stream); - furi_assert(xReturn == pdPASS); - (void)xReturn; - instance->state = IrdaWorkerStateIdle; -} - -void irda_worker_set_decoded_signal(IrdaWorker* instance, const IrdaMessage* message) { - furi_assert(instance); - furi_assert(message); - - instance->signal.decoded = true; - instance->signal.message = *message; -} - -void irda_worker_set_raw_signal(IrdaWorker* instance, const uint32_t* timings, size_t timings_cnt) { - furi_assert(instance); - furi_assert(timings); - furi_assert(timings_cnt > 0); - size_t max_copy_num = COUNT_OF(instance->signal.timings) - 1; - furi_check(timings_cnt <= max_copy_num); - - instance->signal.timings[0] = IRDA_RAW_TX_TIMING_DELAY_US; - memcpy(&instance->signal.timings[1], timings, timings_cnt * sizeof(uint32_t)); - instance->signal.decoded = false; - instance->signal.timings_cnt = timings_cnt + 1; -} - -IrdaWorkerGetSignalResponse - irda_worker_tx_get_signal_steady_callback(void* context, IrdaWorker* instance) { - IrdaWorkerGetSignalResponse response = instance->tx.steady_signal_sent ? - IrdaWorkerGetSignalResponseSame : - IrdaWorkerGetSignalResponseNew; - instance->tx.steady_signal_sent = true; - return response; -} diff --git a/lib/irda/worker/irda_worker.h b/lib/irda/worker/irda_worker.h deleted file mode 100644 index bd204040..00000000 --- a/lib/irda/worker/irda_worker.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define MAX_TIMINGS_AMOUNT 512 - -/** Interface struct of irda worker */ -typedef struct IrdaWorker IrdaWorker; -/** Interface struct of received signal */ -typedef struct IrdaWorkerSignal IrdaWorkerSignal; - -typedef enum { - IrdaWorkerGetSignalResponseNew, /** Signal, provided by callback is new and encoder should be reseted */ - IrdaWorkerGetSignalResponseSame, /** Signal, provided by callback is same. No encoder resetting. */ - IrdaWorkerGetSignalResponseStop, /** No more signals available. */ -} IrdaWorkerGetSignalResponse; - -/** Callback type for providing next signal to send. Should be used with - * irda_worker_make_decoded_signal() or irda_worker_make_raw_signal() - */ -typedef IrdaWorkerGetSignalResponse ( - *IrdaWorkerGetSignalCallback)(void* context, IrdaWorker* instance); - -/** Callback type for 'message is sent' event */ -typedef void (*IrdaWorkerMessageSentCallback)(void* context); - -/** Callback type to call by IrdaWorker thread when new signal is received */ -typedef void (*IrdaWorkerReceivedSignalCallback)(void* context, IrdaWorkerSignal* received_signal); - -/** Allocate IrdaWorker - * - * @return just created instance of IrdaWorker - */ -IrdaWorker* irda_worker_alloc(); - -/** Free IrdaWorker - * - * @param[in] instance - IrdaWorker instance - */ -void irda_worker_free(IrdaWorker* instance); - -/** Start IrdaWorker thread, initialise furi_hal, prepare all work. - * - * @param[in] instance - IrdaWorker instance - */ -void irda_worker_rx_start(IrdaWorker* instance); - -/** Stop IrdaWorker thread, deinitialize furi_hal. - * - * @param[in] instance - IrdaWorker instance - */ -void irda_worker_rx_stop(IrdaWorker* instance); - -/** Set received data callback IrdaWorker - * - * @param[in] instance - IrdaWorker instance - * @param[in] context - context to pass to callbacks - * @param[in] callback - IrdaWorkerReceivedSignalCallback callback - */ -void irda_worker_rx_set_received_signal_callback( - IrdaWorker* instance, - IrdaWorkerReceivedSignalCallback callback, - void* context); - -/** Enable blinking on receiving any signal on IR port. - * - * @param[in] instance - instance of IrdaWorker - * @param[in] enable - true if you want to enable blinking - * false otherwise - */ -void irda_worker_rx_enable_blink_on_receiving(IrdaWorker* instance, bool enable); - -/** Clarify is received signal either decoded or raw - * - * @param[in] signal - received signal - * @return true if signal is decoded, false if signal is raw - */ -bool irda_worker_signal_is_decoded(const IrdaWorkerSignal* signal); - -/** Start transmitting signal. Callback IrdaWorkerGetSignalCallback should be - * set before this function is called, as it calls for it to fill buffer before - * starting transmission. - * - * @param[in] instance - IrdaWorker instance - */ -void irda_worker_tx_start(IrdaWorker* instance); - -/** Stop transmitting signal. Waits for end of current signal and stops transmission. - * - * @param[in] instance - IrdaWorker instance - */ -void irda_worker_tx_stop(IrdaWorker* instance); - -/** Set callback for providing next signal to send - * - * @param[in] instance - IrdaWorker instance - * @param[in] context - context to pass to callbacks - * @param[in] callback - IrdaWorkerGetSignalCallback callback - */ -void irda_worker_tx_set_get_signal_callback( - IrdaWorker* instance, - IrdaWorkerGetSignalCallback callback, - void* context); - -/** Set callback for end of signal transmitting - * - * @param[in] instance - IrdaWorker instance - * @param[in] context - context to pass to callbacks - * @param[in] callback - IrdaWorkerMessageSentCallback callback - */ -void irda_worker_tx_set_signal_sent_callback( - IrdaWorker* instance, - IrdaWorkerMessageSentCallback callback, - void* context); - -/** Callback to pass to irda_worker_tx_set_get_signal_callback() if signal - * is steady and will not be changed between irda_worker start and stop. - * Before starting transmission, desired steady signal must be set with - * irda_worker_make_decoded_signal() or irda_worker_make_raw_signal(). - * - * This function should not be implicitly called. - * - * @param[in] context - context - * @param[out] instance - IrdaWorker instance - */ -IrdaWorkerGetSignalResponse - irda_worker_tx_get_signal_steady_callback(void* context, IrdaWorker* instance); - -/** Acquire raw signal from interface struct 'IrdaWorkerSignal'. - * First, you have to ensure that signal is raw. - * - * @param[in] signal - received signal - * @param[out] timings - pointer to array of timings - * @param[out] timings_cnt - pointer to amount of timings - */ -void irda_worker_get_raw_signal( - const IrdaWorkerSignal* signal, - const uint32_t** timings, - size_t* timings_cnt); - -/** Acquire decoded message from interface struct 'IrdaWorkerSignal'. - * First, you have to ensure that signal is decoded. - * - * @param[in] signal - received signal - * @return decoded IRDA message - */ -const IrdaMessage* irda_worker_get_decoded_signal(const IrdaWorkerSignal* signal); - -/** Set current decoded signal for IrdaWorker instance - * - * @param[out] instance - IrdaWorker instance - * @param[in] message - decoded signal - */ -void irda_worker_set_decoded_signal(IrdaWorker* instance, const IrdaMessage* message); - -/** Set current raw signal for IrdaWorker instance - * - * @param[out] instance - IrdaWorker instance - * @param[in] timings - array of raw timings - * @param[in] timings_cnt - size of array of raw timings - */ -void irda_worker_set_raw_signal(IrdaWorker* instance, const uint32_t* timings, size_t timings_cnt); - -#ifdef __cplusplus -} -#endif diff --git a/lib/lib.mk b/lib/lib.mk index 09f929f1..5216b0ad 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -83,11 +83,11 @@ CFLAGS += -I$(LIB_DIR)/drivers C_SOURCES += $(wildcard $(LIB_DIR)/drivers/*.c) # IR lib -CFLAGS += -I$(LIB_DIR)/irda/encoder_decoder -CFLAGS += -I$(LIB_DIR)/irda/worker -C_SOURCES += $(wildcard $(LIB_DIR)/irda/encoder_decoder/*.c) -C_SOURCES += $(wildcard $(LIB_DIR)/irda/encoder_decoder/*/*.c) -C_SOURCES += $(wildcard $(LIB_DIR)/irda/worker/*.c) +CFLAGS += -I$(LIB_DIR)/infrared/encoder_decoder +CFLAGS += -I$(LIB_DIR)/infrared/worker +C_SOURCES += $(wildcard $(LIB_DIR)/infrared/encoder_decoder/*.c) +C_SOURCES += $(wildcard $(LIB_DIR)/infrared/encoder_decoder/*/*.c) +C_SOURCES += $(wildcard $(LIB_DIR)/infrared/worker/*.c) # SubGhz C_SOURCES += $(wildcard $(LIB_DIR)/subghz/*.c) From 3164184bbcb0a31f6c510fbd0bdab94f6760b4e7 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:48:56 +0400 Subject: [PATCH 03/13] [FL-2230] SubGhz: protocol API refactoring (#969) * SubGhz: protocols library refactoring * SubGhz: new architecture and refactoring * SubGhz: simplify protocol structure, remove unused types * SubGhz: rename Subghz to SubGhz * SubGhz: add environment concept Co-authored-by: Aleksandr Kutuzov Co-authored-by: DrZlo13 --- CODING_STYLE.md | 6 +- Makefile | 5 + applications/ReadMe.md | 2 +- applications/subghz/helpers/subghz_chat.c | 24 +- applications/subghz/helpers/subghz_chat.h | 22 +- .../subghz/helpers/subghz_custom_event.h | 74 +- .../subghz_frequency_analyzer_worker.c | 2 +- .../subghz/scenes/subghz_scene_delete.c | 9 +- .../subghz/scenes/subghz_scene_delete_raw.c | 10 +- .../scenes/subghz_scene_delete_success.c | 6 +- .../scenes/subghz_scene_frequency_analyzer.c | 4 +- .../subghz/scenes/subghz_scene_more_raw.c | 4 +- .../subghz/scenes/subghz_scene_need_saving.c | 10 +- .../subghz/scenes/subghz_scene_read_raw.c | 123 ++-- .../subghz/scenes/subghz_scene_receiver.c | 49 +- .../scenes/subghz_scene_receiver_config.c | 6 +- .../scenes/subghz_scene_receiver_info.c | 50 +- .../subghz/scenes/subghz_scene_save_name.c | 35 +- .../subghz/scenes/subghz_scene_save_success.c | 6 +- .../subghz/scenes/subghz_scene_saved.c | 2 +- .../subghz/scenes/subghz_scene_saved_menu.c | 2 +- .../subghz/scenes/subghz_scene_set_type.c | 145 ++-- .../subghz/scenes/subghz_scene_show_error.c | 20 +- .../scenes/subghz_scene_show_error_sub.c | 6 +- .../subghz/scenes/subghz_scene_show_only_rx.c | 6 +- .../subghz/scenes/subghz_scene_start.c | 2 +- .../subghz/scenes/subghz_scene_test.c | 2 +- .../subghz/scenes/subghz_scene_test_carrier.c | 6 +- .../subghz/scenes/subghz_scene_test_packet.c | 6 +- .../subghz/scenes/subghz_scene_test_static.c | 6 +- .../subghz/scenes/subghz_scene_transmitter.c | 39 +- applications/subghz/subghz.c | 96 +-- applications/subghz/subghz_cli.c | 92 ++- applications/subghz/subghz_history.c | 224 +++--- applications/subghz/subghz_history.h | 22 +- applications/subghz/subghz_i.c | 249 +++---- applications/subghz/subghz_i.h | 69 +- .../views/{subghz_receiver.c => receiver.c} | 105 +-- applications/subghz/views/receiver.h | 36 + .../subghz/views/subghz_frequency_analyzer.c | 42 +- .../subghz/views/subghz_frequency_analyzer.h | 14 +- applications/subghz/views/subghz_read_raw.c | 234 +++--- applications/subghz/views/subghz_read_raw.h | 48 +- applications/subghz/views/subghz_receiver.h | 36 - .../subghz/views/subghz_test_carrier.c | 60 +- .../subghz/views/subghz_test_carrier.h | 18 +- .../subghz/views/subghz_test_packet.c | 112 +-- .../subghz/views/subghz_test_packet.h | 18 +- .../subghz/views/subghz_test_static.c | 62 +- .../subghz/views/subghz_test_static.h | 18 +- .../subghz/views/subghz_transmitter.h | 26 - .../{subghz_transmitter.c => transmitter.c} | 67 +- applications/subghz/views/transmitter.h | 26 + assets/resources/subghz/assets/nice_flor_s_rx | 6 - assets/resources/subghz/assets/nice_flor_s_tx | 6 - assets/resources/subghz/nice_flor_s | 6 + bootloader/Makefile | 5 +- firmware/Makefile | 5 +- lib/ReadMe.md | 2 +- lib/subghz/blocks/const.c | 1 + lib/subghz/blocks/const.h | 12 + lib/subghz/blocks/decoder.c | 17 + lib/subghz/blocks/decoder.h | 17 + lib/subghz/blocks/encoder.c | 3 + lib/subghz/blocks/encoder.h | 16 + lib/subghz/blocks/generic.c | 118 +++ lib/subghz/blocks/generic.h | 30 + lib/subghz/blocks/math.c | 9 + lib/subghz/blocks/math.h | 13 + lib/subghz/environment.c | 67 ++ lib/subghz/environment.h | 28 + lib/subghz/protocols/base.c | 64 ++ lib/subghz/protocols/base.h | 51 ++ lib/subghz/protocols/came.c | 313 ++++++++ lib/subghz/protocols/came.h | 63 ++ lib/subghz/protocols/came_atomo.c | 338 +++++++++ lib/subghz/protocols/came_atomo.h | 24 + lib/subghz/protocols/came_twee.c | 445 ++++++++++++ lib/subghz/protocols/came_twee.h | 30 + lib/subghz/protocols/faac_slh.c | 218 ++++++ lib/subghz/protocols/faac_slh.h | 25 + lib/subghz/protocols/gate_tx.c | 308 ++++++++ lib/subghz/protocols/gate_tx.h | 30 + lib/subghz/protocols/hormann.c | 331 +++++++++ lib/subghz/protocols/hormann.h | 30 + lib/subghz/protocols/ido.c | 218 ++++++ lib/subghz/protocols/ido.h | 25 + lib/subghz/protocols/keeloq.c | 681 ++++++++++++++++++ lib/subghz/protocols/keeloq.h | 39 + ...otocol_keeloq_common.c => keeloq_common.c} | 4 +- ...otocol_keeloq_common.h => keeloq_common.h} | 8 +- lib/subghz/protocols/kia.c | 268 +++++++ lib/subghz/protocols/kia.h | 25 + lib/subghz/protocols/nero_radio.c | 375 ++++++++++ lib/subghz/protocols/nero_radio.h | 30 + lib/subghz/protocols/nero_sketch.c | 366 ++++++++++ lib/subghz/protocols/nero_sketch.h | 30 + lib/subghz/protocols/nice_flo.c | 310 ++++++++ lib/subghz/protocols/nice_flo.h | 30 + lib/subghz/protocols/nice_flor_s.c | 366 ++++++++++ lib/subghz/protocols/nice_flor_s.h | 25 + lib/subghz/protocols/princeton.c | 351 +++++++++ lib/subghz/protocols/princeton.h | 30 + lib/subghz/protocols/princeton_for_testing.c | 287 ++++++++ lib/subghz/protocols/princeton_for_testing.h | 85 +++ lib/subghz/protocols/raw.c | 347 +++++++++ lib/subghz/protocols/raw.h | 40 + lib/subghz/protocols/registry.c | 53 ++ lib/subghz/protocols/registry.h | 9 + lib/subghz/protocols/scher_khan.c | 286 ++++++++ lib/subghz/protocols/scher_khan.h | 25 + lib/subghz/protocols/somfy_keytis.c | 438 +++++++++++ lib/subghz/protocols/somfy_keytis.h | 25 + lib/subghz/protocols/somfy_telis.c | 366 ++++++++++ lib/subghz/protocols/somfy_telis.h | 25 + lib/subghz/protocols/star_line.c | 376 ++++++++++ lib/subghz/protocols/star_line.h | 25 + lib/subghz/protocols/subghz_protocol_came.c | 188 ----- lib/subghz/protocols/subghz_protocol_came.h | 73 -- .../protocols/subghz_protocol_came_atomo.c | 264 ------- .../protocols/subghz_protocol_came_atomo.h | 53 -- .../protocols/subghz_protocol_came_twee.c | 353 --------- .../protocols/subghz_protocol_came_twee.h | 78 -- lib/subghz/protocols/subghz_protocol_cfm.c | 4 - lib/subghz/protocols/subghz_protocol_cfm.h | 0 lib/subghz/protocols/subghz_protocol_common.c | 236 ------ lib/subghz/protocols/subghz_protocol_common.h | 226 ------ .../protocols/subghz_protocol_faac_slh.c | 189 ----- .../protocols/subghz_protocol_faac_slh.h | 62 -- .../protocols/subghz_protocol_gate_tx.c | 195 ----- .../protocols/subghz_protocol_gate_tx.h | 75 -- .../protocols/subghz_protocol_hormann.c | 209 ------ .../protocols/subghz_protocol_hormann.h | 75 -- lib/subghz/protocols/subghz_protocol_ido.c | 189 ----- lib/subghz/protocols/subghz_protocol_ido.h | 62 -- lib/subghz/protocols/subghz_protocol_keeloq.c | 493 ------------- lib/subghz/protocols/subghz_protocol_keeloq.h | 106 --- lib/subghz/protocols/subghz_protocol_kia.c | 188 ----- lib/subghz/protocols/subghz_protocol_kia.h | 56 -- .../protocols/subghz_protocol_nero_radio.c | 232 ------ .../protocols/subghz_protocol_nero_radio.h | 84 --- .../protocols/subghz_protocol_nero_sketch.c | 225 ------ .../protocols/subghz_protocol_nero_sketch.h | 84 --- .../protocols/subghz_protocol_nice_flo.c | 189 ----- .../protocols/subghz_protocol_nice_flo.h | 75 -- .../protocols/subghz_protocol_nice_flor_s.c | 264 ------- .../protocols/subghz_protocol_nice_flor_s.h | 66 -- .../protocols/subghz_protocol_princeton.c | 368 ---------- .../protocols/subghz_protocol_princeton.h | 137 ---- lib/subghz/protocols/subghz_protocol_raw.c | 270 ------- lib/subghz/protocols/subghz_protocol_raw.h | 71 -- .../protocols/subghz_protocol_scher_khan.c | 244 ------- .../protocols/subghz_protocol_scher_khan.h | 65 -- .../protocols/subghz_protocol_somfy_keytis.c | 345 --------- .../protocols/subghz_protocol_somfy_keytis.h | 50 -- .../protocols/subghz_protocol_somfy_telis.c | 293 -------- .../protocols/subghz_protocol_somfy_telis.h | 46 -- .../protocols/subghz_protocol_star_line.c | 341 --------- .../protocols/subghz_protocol_star_line.h | 81 --- lib/subghz/receiver.c | 121 ++++ lib/subghz/receiver.h | 28 + lib/subghz/subghz_file_encoder_worker.c | 2 +- lib/subghz/subghz_keystore.c | 1 + lib/subghz/subghz_parser.c | 336 --------- lib/subghz/subghz_parser.h | 88 --- lib/subghz/subghz_tx_rx_worker.c | 2 +- lib/subghz/subghz_worker.c | 2 +- lib/subghz/transmitter.c | 51 ++ lib/subghz/transmitter.h | 23 + lib/subghz/types.h | 99 +++ make/rules.mk | 48 +- scripts/flipper/app.py | 9 +- scripts/guruguru.py | 46 ++ 173 files changed, 9836 insertions(+), 8486 deletions(-) rename applications/subghz/views/{subghz_receiver.c => receiver.c} (72%) create mode 100644 applications/subghz/views/receiver.h delete mode 100644 applications/subghz/views/subghz_receiver.h delete mode 100644 applications/subghz/views/subghz_transmitter.h rename applications/subghz/views/{subghz_transmitter.c => transmitter.c} (68%) create mode 100644 applications/subghz/views/transmitter.h create mode 100644 assets/resources/subghz/nice_flor_s create mode 100644 lib/subghz/blocks/const.c create mode 100644 lib/subghz/blocks/const.h create mode 100644 lib/subghz/blocks/decoder.c create mode 100644 lib/subghz/blocks/decoder.h create mode 100644 lib/subghz/blocks/encoder.c create mode 100644 lib/subghz/blocks/encoder.h create mode 100644 lib/subghz/blocks/generic.c create mode 100644 lib/subghz/blocks/generic.h create mode 100644 lib/subghz/blocks/math.c create mode 100644 lib/subghz/blocks/math.h create mode 100644 lib/subghz/environment.c create mode 100644 lib/subghz/environment.h create mode 100644 lib/subghz/protocols/base.c create mode 100644 lib/subghz/protocols/base.h create mode 100644 lib/subghz/protocols/came.c create mode 100644 lib/subghz/protocols/came.h create mode 100644 lib/subghz/protocols/came_atomo.c create mode 100644 lib/subghz/protocols/came_atomo.h create mode 100644 lib/subghz/protocols/came_twee.c create mode 100644 lib/subghz/protocols/came_twee.h create mode 100644 lib/subghz/protocols/faac_slh.c create mode 100644 lib/subghz/protocols/faac_slh.h create mode 100644 lib/subghz/protocols/gate_tx.c create mode 100644 lib/subghz/protocols/gate_tx.h create mode 100644 lib/subghz/protocols/hormann.c create mode 100644 lib/subghz/protocols/hormann.h create mode 100644 lib/subghz/protocols/ido.c create mode 100644 lib/subghz/protocols/ido.h create mode 100644 lib/subghz/protocols/keeloq.c create mode 100644 lib/subghz/protocols/keeloq.h rename lib/subghz/protocols/{subghz_protocol_keeloq_common.c => keeloq_common.c} (97%) rename lib/subghz/protocols/{subghz_protocol_keeloq_common.h => keeloq_common.h} (93%) create mode 100644 lib/subghz/protocols/kia.c create mode 100644 lib/subghz/protocols/kia.h create mode 100644 lib/subghz/protocols/nero_radio.c create mode 100644 lib/subghz/protocols/nero_radio.h create mode 100644 lib/subghz/protocols/nero_sketch.c create mode 100644 lib/subghz/protocols/nero_sketch.h create mode 100644 lib/subghz/protocols/nice_flo.c create mode 100644 lib/subghz/protocols/nice_flo.h create mode 100644 lib/subghz/protocols/nice_flor_s.c create mode 100644 lib/subghz/protocols/nice_flor_s.h create mode 100644 lib/subghz/protocols/princeton.c create mode 100644 lib/subghz/protocols/princeton.h create mode 100644 lib/subghz/protocols/princeton_for_testing.c create mode 100644 lib/subghz/protocols/princeton_for_testing.h create mode 100644 lib/subghz/protocols/raw.c create mode 100644 lib/subghz/protocols/raw.h create mode 100644 lib/subghz/protocols/registry.c create mode 100644 lib/subghz/protocols/registry.h create mode 100644 lib/subghz/protocols/scher_khan.c create mode 100644 lib/subghz/protocols/scher_khan.h create mode 100644 lib/subghz/protocols/somfy_keytis.c create mode 100644 lib/subghz/protocols/somfy_keytis.h create mode 100644 lib/subghz/protocols/somfy_telis.c create mode 100644 lib/subghz/protocols/somfy_telis.h create mode 100644 lib/subghz/protocols/star_line.c create mode 100644 lib/subghz/protocols/star_line.h delete mode 100644 lib/subghz/protocols/subghz_protocol_came.c delete mode 100644 lib/subghz/protocols/subghz_protocol_came.h delete mode 100644 lib/subghz/protocols/subghz_protocol_came_atomo.c delete mode 100644 lib/subghz/protocols/subghz_protocol_came_atomo.h delete mode 100644 lib/subghz/protocols/subghz_protocol_came_twee.c delete mode 100644 lib/subghz/protocols/subghz_protocol_came_twee.h delete mode 100644 lib/subghz/protocols/subghz_protocol_cfm.c delete mode 100644 lib/subghz/protocols/subghz_protocol_cfm.h delete mode 100644 lib/subghz/protocols/subghz_protocol_common.c delete mode 100644 lib/subghz/protocols/subghz_protocol_common.h delete mode 100644 lib/subghz/protocols/subghz_protocol_faac_slh.c delete mode 100644 lib/subghz/protocols/subghz_protocol_faac_slh.h delete mode 100644 lib/subghz/protocols/subghz_protocol_gate_tx.c delete mode 100644 lib/subghz/protocols/subghz_protocol_gate_tx.h delete mode 100644 lib/subghz/protocols/subghz_protocol_hormann.c delete mode 100644 lib/subghz/protocols/subghz_protocol_hormann.h delete mode 100644 lib/subghz/protocols/subghz_protocol_ido.c delete mode 100644 lib/subghz/protocols/subghz_protocol_ido.h delete mode 100644 lib/subghz/protocols/subghz_protocol_keeloq.c delete mode 100644 lib/subghz/protocols/subghz_protocol_keeloq.h delete mode 100644 lib/subghz/protocols/subghz_protocol_kia.c delete mode 100644 lib/subghz/protocols/subghz_protocol_kia.h delete mode 100644 lib/subghz/protocols/subghz_protocol_nero_radio.c delete mode 100644 lib/subghz/protocols/subghz_protocol_nero_radio.h delete mode 100644 lib/subghz/protocols/subghz_protocol_nero_sketch.c delete mode 100644 lib/subghz/protocols/subghz_protocol_nero_sketch.h delete mode 100644 lib/subghz/protocols/subghz_protocol_nice_flo.c delete mode 100644 lib/subghz/protocols/subghz_protocol_nice_flo.h delete mode 100644 lib/subghz/protocols/subghz_protocol_nice_flor_s.c delete mode 100644 lib/subghz/protocols/subghz_protocol_nice_flor_s.h delete mode 100644 lib/subghz/protocols/subghz_protocol_princeton.c delete mode 100644 lib/subghz/protocols/subghz_protocol_princeton.h delete mode 100755 lib/subghz/protocols/subghz_protocol_raw.c delete mode 100644 lib/subghz/protocols/subghz_protocol_raw.h delete mode 100644 lib/subghz/protocols/subghz_protocol_scher_khan.c delete mode 100644 lib/subghz/protocols/subghz_protocol_scher_khan.h delete mode 100644 lib/subghz/protocols/subghz_protocol_somfy_keytis.c delete mode 100644 lib/subghz/protocols/subghz_protocol_somfy_keytis.h delete mode 100644 lib/subghz/protocols/subghz_protocol_somfy_telis.c delete mode 100644 lib/subghz/protocols/subghz_protocol_somfy_telis.h delete mode 100644 lib/subghz/protocols/subghz_protocol_star_line.c delete mode 100644 lib/subghz/protocols/subghz_protocol_star_line.h create mode 100644 lib/subghz/receiver.c create mode 100644 lib/subghz/receiver.h delete mode 100644 lib/subghz/subghz_parser.c delete mode 100644 lib/subghz/subghz_parser.h create mode 100644 lib/subghz/transmitter.c create mode 100644 lib/subghz/transmitter.h create mode 100644 lib/subghz/types.h create mode 100755 scripts/guruguru.py diff --git a/CODING_STYLE.md b/CODING_STYLE.md index f32e59fe..ccadbe8e 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -58,7 +58,7 @@ Examples: FuriHalUsb Gui - SubghzKeystore + SubGhzKeystore ### Functions are snake_case @@ -73,8 +73,8 @@ This rule makes easier to locate types, functions and sources. For example: -We have abstraction that we call `Subghz Keystore`, so there will be: -file `subghz_keystore.h` we have type `SubghzKeystore` and function `subghz_keystore_read`. +We have abstraction that we call `SubGhz Keystore`, so there will be: +file `subghz_keystore.h` we have type `SubGhzKeystore` and function `subghz_keystore_read`. ### File names diff --git a/Makefile b/Makefile index 6c01e76e..cd448e2e 100644 --- a/Makefile +++ b/Makefile @@ -121,3 +121,8 @@ lint: format: @echo "Reformating sources code" @$(PROJECT_ROOT)/scripts/lint.py format $(PROJECT_SOURCE_DIRECTORIES) + +.PHONY: guruguru +guruguru: + @echo "ぐるぐる回る" + @$(PROJECT_ROOT)/scripts/guruguru.py $(PROJECT_ROOT) diff --git a/applications/ReadMe.md b/applications/ReadMe.md index a1fbb5d6..4c7066ee 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -30,7 +30,7 @@ - `snake_game` - Snake game application - `storage` - Storage service, internal + sdcard - `storage_settings` - Storage settings app -- `subghz` - Subghz application, 433 fobs and etc +- `subghz` - SubGhz application, 433 fobs and etc - `system` - System settings, tools and API - `tests` - Unit tests and etc - `u2f` - U2F Application diff --git a/applications/subghz/helpers/subghz_chat.c b/applications/subghz/helpers/subghz_chat.c index 38529a10..75896958 100644 --- a/applications/subghz/helpers/subghz_chat.c +++ b/applications/subghz/helpers/subghz_chat.c @@ -23,12 +23,12 @@ static int32_t subghz_chat_worker_thread(void* context) { SubGhzChatWorker* instance = context; FURI_LOG_I(TAG, "Worker start"); char c; - SubghzChatEvent event; - event.event = SubghzChatEventUserEntrance; + SubGhzChatEvent event; + event.event = SubGhzChatEventUserEntrance; osMessageQueuePut(instance->event_queue, &event, 0, 0); while(instance->worker_running) { if(furi_hal_vcp_rx_with_timeout((uint8_t*)&c, 1, 1000) == 1) { - event.event = SubghzChatEventInputData; + event.event = SubGhzChatEventInputData; event.c = c; osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever); } @@ -41,13 +41,13 @@ static int32_t subghz_chat_worker_thread(void* context) { static void subghz_chat_worker_update_rx_event_chat(void* context) { furi_assert(context); SubGhzChatWorker* instance = context; - SubghzChatEvent event; + SubGhzChatEvent event; if((millis() - instance->last_time_rx_data) > SUBGHZ_CHAT_WORKER_TIMEOUT_BETWEEN_MESSAGES) { - event.event = SubghzChatEventNewMessage; + event.event = SubGhzChatEventNewMessage; osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever); } instance->last_time_rx_data = millis(); - event.event = SubghzChatEventRXData; + event.event = SubGhzChatEventRXData; osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever); } @@ -55,12 +55,12 @@ SubGhzChatWorker* subghz_chat_worker_alloc() { SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubghzChat"); + furi_thread_set_name(instance->thread, "SubGhzChat"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_chat_worker_thread); instance->subghz_txrx = subghz_tx_rx_worker_alloc(); - instance->event_queue = osMessageQueueNew(80, sizeof(SubghzChatEvent), NULL); + instance->event_queue = osMessageQueueNew(80, sizeof(SubGhzChatEvent), NULL); return instance; } @@ -109,18 +109,18 @@ bool subghz_chat_worker_is_running(SubGhzChatWorker* instance) { return instance->worker_running; } -SubghzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance) { +SubGhzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance) { furi_assert(instance); - SubghzChatEvent event; + SubGhzChatEvent event; if(osMessageQueueGet(instance->event_queue, &event, NULL, osWaitForever) == osOK) { return event; } else { - event.event = SubghzChatEventNoEvent; + event.event = SubGhzChatEventNoEvent; return event; } } -void subghz_chat_worker_put_event_chat(SubGhzChatWorker* instance, SubghzChatEvent* event) { +void subghz_chat_worker_put_event_chat(SubGhzChatWorker* instance, SubGhzChatEvent* event) { furi_assert(instance); osMessageQueuePut(instance->event_queue, event, 0, osWaitForever); } diff --git a/applications/subghz/helpers/subghz_chat.h b/applications/subghz/helpers/subghz_chat.h index 89f82ef3..6556d0c3 100644 --- a/applications/subghz/helpers/subghz_chat.h +++ b/applications/subghz/helpers/subghz_chat.h @@ -4,26 +4,26 @@ typedef struct SubGhzChatWorker SubGhzChatWorker; typedef enum { - SubghzChatEventNoEvent, - SubghzChatEventUserEntrance, - SubghzChatEventUserExit, - SubghzChatEventInputData, - SubghzChatEventRXData, - SubghzChatEventNewMessage, -} SubghzChatEventType; + SubGhzChatEventNoEvent, + SubGhzChatEventUserEntrance, + SubGhzChatEventUserExit, + SubGhzChatEventInputData, + SubGhzChatEventRXData, + SubGhzChatEventNewMessage, +} SubGhzChatEventType; typedef struct { - SubghzChatEventType event; + SubGhzChatEventType event; char c; -} SubghzChatEvent; +} SubGhzChatEvent; SubGhzChatWorker* subghz_chat_worker_alloc(); void subghz_chat_worker_free(SubGhzChatWorker* instance); bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency); void subghz_chat_worker_stop(SubGhzChatWorker* instance); bool subghz_chat_worker_is_running(SubGhzChatWorker* instance); -SubghzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance); -void subghz_chat_worker_put_event_chat(SubGhzChatWorker* instance, SubghzChatEvent* event); +SubGhzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance); +void subghz_chat_worker_put_event_chat(SubGhzChatWorker* instance, SubGhzChatEvent* event); size_t subghz_chat_worker_available(SubGhzChatWorker* instance); size_t subghz_chat_worker_read(SubGhzChatWorker* instance, uint8_t* data, size_t size); bool subghz_chat_worker_write(SubGhzChatWorker* instance, uint8_t* data, size_t size); diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h index 5fee3541..e9890910 100644 --- a/applications/subghz/helpers/subghz_custom_event.h +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -1,46 +1,46 @@ #pragma once typedef enum { - SubghzCustomEventManagerNoSet = 0, - SubghzCustomEventManagerSet, - SubghzCustomEventManagerSetRAW, + SubGhzCustomEventManagerNoSet = 0, + SubGhzCustomEventManagerSet, + SubGhzCustomEventManagerSetRAW, - SubghzCustomEventSceneDeleteSuccess = 100, - SubghzCustomEventSceneDelete, - SubghzCustomEventSceneDeleteRAW, - SubghzCustomEventSceneDeleteRAWBack, + SubGhzCustomEventSceneDeleteSuccess = 100, + SubGhzCustomEventSceneDelete, + SubGhzCustomEventSceneDeleteRAW, + SubGhzCustomEventSceneDeleteRAWBack, - SubghzCustomEventSceneReceiverInfoTxStart, - SubghzCustomEventSceneReceiverInfoTxStop, - SubghzCustomEventSceneReceiverInfoSave, - SubghzCustomEventSceneSaveName, - SubghzCustomEventSceneSaveSuccess, - SubghzCustomEventSceneShowErrorBack, - SubghzCustomEventSceneShowErrorOk, - SubghzCustomEventSceneShowErrorSub, - SubghzCustomEventSceneShowOnlyRX, + SubGhzCustomEventSceneReceiverInfoTxStart, + SubGhzCustomEventSceneReceiverInfoTxStop, + SubGhzCustomEventSceneReceiverInfoSave, + SubGhzCustomEventSceneSaveName, + SubGhzCustomEventSceneSaveSuccess, + SubGhzCustomEventSceneShowErrorBack, + SubGhzCustomEventSceneShowErrorOk, + SubGhzCustomEventSceneShowErrorSub, + SubGhzCustomEventSceneShowOnlyRX, - SubghzCustomEventSceneExit, - SubghzCustomEventSceneStay, + SubGhzCustomEventSceneExit, + SubGhzCustomEventSceneStay, - SubghzCustomEventViewReceverOK, - SubghzCustomEventViewReceverConfig, - SubghzCustomEventViewReceverBack, + SubGhzCustomEventViewReceverOK, + SubGhzCustomEventViewReceverConfig, + SubGhzCustomEventViewReceverBack, - SubghzCustomEventViewReadRAWBack, - SubghzCustomEventViewReadRAWIDLE, - SubghzCustomEventViewReadRAWREC, - SubghzCustomEventViewReadRAWConfig, - SubghzCustomEventViewReadRAWErase, - SubghzCustomEventViewReadRAWSendStart, - SubghzCustomEventViewReadRAWSendStop, - SubghzCustomEventViewReadRAWSave, - SubghzCustomEventViewReadRAWVibro, - SubghzCustomEventViewReadRAWTXRXStop, - SubghzCustomEventViewReadRAWMore, + SubGhzCustomEventViewReadRAWBack, + SubGhzCustomEventViewReadRAWIDLE, + SubGhzCustomEventViewReadRAWREC, + SubGhzCustomEventViewReadRAWConfig, + SubGhzCustomEventViewReadRAWErase, + SubGhzCustomEventViewReadRAWSendStart, + SubGhzCustomEventViewReadRAWSendStop, + SubGhzCustomEventViewReadRAWSave, + SubGhzCustomEventViewReadRAWVibro, + SubGhzCustomEventViewReadRAWTXRXStop, + SubGhzCustomEventViewReadRAWMore, - SubghzCustomEventViewTransmitterBack, - SubghzCustomEventViewTransmitterSendStart, - SubghzCustomEventViewTransmitterSendStop, - SubghzCustomEventViewTransmitterError, -} SubghzCustomEvent; \ No newline at end of file + SubGhzCustomEventViewTransmitterBack, + SubGhzCustomEventViewTransmitterSendStart, + SubGhzCustomEventViewTransmitterSendStop, + SubGhzCustomEventViewTransmitterError, +} SubGhzCustomEvent; diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index b79cfe04..54b3191d 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -145,7 +145,7 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc() { SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubghzFAWorker"); + furi_thread_set_name(instance->thread, "SubGhzFAWorker"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_frequency_analyzer_worker_thread); diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/subghz/scenes/subghz_scene_delete.c index 46c33c72..fa020648 100644 --- a/applications/subghz/scenes/subghz_scene_delete.c +++ b/applications/subghz/scenes/subghz_scene_delete.c @@ -5,7 +5,7 @@ void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* co furi_assert(context); SubGhz* subghz = context; if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneDelete); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneDelete); } } @@ -31,8 +31,7 @@ void subghz_scene_delete_on_enter(void* context) { AlignTop, FontSecondary, string_get_cstr(modulation_str)); - - subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, text); + subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text); widget_add_string_multiline_element( subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); @@ -43,13 +42,13 @@ void subghz_scene_delete_on_enter(void* context) { widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneDelete) { + if(event.event == SubGhzCustomEventSceneDelete) { strcpy(subghz->file_name_tmp, subghz->file_name); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); diff --git a/applications/subghz/scenes/subghz_scene_delete_raw.c b/applications/subghz/scenes/subghz_scene_delete_raw.c index 5dc293b3..74ad75ce 100644 --- a/applications/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/subghz/scenes/subghz_scene_delete_raw.c @@ -6,10 +6,10 @@ void subghz_scene_delete_raw_callback(GuiButtonType result, InputType type, void SubGhz* subghz = context; if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneDeleteRAW); + subghz->view_dispatcher, SubGhzCustomEventSceneDeleteRAW); } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneDeleteRAWBack); + subghz->view_dispatcher, SubGhzCustomEventSceneDeleteRAWBack); } } @@ -49,13 +49,13 @@ void subghz_scene_delete_raw_on_enter(void* context) { widget_add_button_element( subghz->widget, GuiButtonTypeLeft, "Back", subghz_scene_delete_raw_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneDeleteRAW) { + if(event.event == SubGhzCustomEventSceneDeleteRAW) { strcpy(subghz->file_name_tmp, subghz->file_name); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); @@ -64,7 +64,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart); } return true; - } else if(event.event == SubghzCustomEventSceneDeleteRAWBack) { + } else if(event.event == SubGhzCustomEventSceneDeleteRAWBack) { return scene_manager_previous_scene(subghz->scene_manager); } } diff --git a/applications/subghz/scenes/subghz_scene_delete_success.c b/applications/subghz/scenes/subghz_scene_delete_success.c index 8c67e3c5..d5b3ec2d 100644 --- a/applications/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/subghz/scenes/subghz_scene_delete_success.c @@ -4,7 +4,7 @@ void subghz_scene_delete_success_popup_callback(void* context) { SubGhz* subghz = context; view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneDeleteSuccess); + subghz->view_dispatcher, SubGhzCustomEventSceneDeleteSuccess); } void subghz_scene_delete_success_on_enter(void* context) { @@ -18,14 +18,14 @@ void subghz_scene_delete_success_on_enter(void* context) { popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_delete_success_popup_callback); popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewPopup); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); } bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneDeleteSuccess) { + if(event.event == SubGhzCustomEventSceneDeleteSuccess) { if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneSaved)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); diff --git a/applications/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/subghz/scenes/subghz_scene_frequency_analyzer.c index 554030ad..49143b35 100644 --- a/applications/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -2,7 +2,7 @@ #include "../views/subghz_frequency_analyzer.h" #include -void subghz_scene_frequency_analyzer_callback(SubghzCustomEvent event, void* context) { +void subghz_scene_frequency_analyzer_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); @@ -13,7 +13,7 @@ void subghz_scene_frequency_analyzer_on_enter(void* context) { DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer); subghz_frequency_analyzer_set_callback( subghz->subghz_frequency_analyzer, subghz_scene_frequency_analyzer_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer); } bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/subghz/scenes/subghz_scene_more_raw.c b/applications/subghz/scenes/subghz_scene_more_raw.c index 70ea56db..76f9234a 100644 --- a/applications/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/subghz/scenes/subghz_scene_more_raw.c @@ -30,7 +30,7 @@ void subghz_scene_more_raw_on_enter(void* context) { submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneMoreRAW)); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); } bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { @@ -39,7 +39,7 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexDelete) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerNoSet); + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDelete); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); diff --git a/applications/subghz/scenes/subghz_scene_need_saving.c b/applications/subghz/scenes/subghz_scene_need_saving.c index 3d794bf4..1d17976b 100644 --- a/applications/subghz/scenes/subghz_scene_need_saving.c +++ b/applications/subghz/scenes/subghz_scene_need_saving.c @@ -6,9 +6,9 @@ void subghz_scene_need_saving_callback(GuiButtonType result, InputType type, voi SubGhz* subghz = context; if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneStay); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneStay); } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneExit); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); } } @@ -31,7 +31,7 @@ void subghz_scene_need_saving_on_enter(void* context) { widget_add_button_element( subghz->widget, GuiButtonTypeLeft, "Exit", subghz_scene_need_saving_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) { @@ -41,11 +41,11 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) { scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneStay) { + if(event.event == SubGhzCustomEventSceneStay) { subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; scene_manager_previous_scene(subghz->scene_manager); return true; - } else if(event.event == SubghzCustomEventSceneExit) { + } else if(event.event == SubGhzCustomEventSceneExit) { if(subghz->txrx->rx_key_state == SubGhzRxKeyStateExit) { subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index b8659057..67e4df8f 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -1,35 +1,35 @@ #include "../subghz_i.h" #include "../views/subghz_read_raw.h" #include -#include -#include +#include #include #define RAW_FILE_NAME "Raw_signal_" +#define TAG "SubGhzSceneReadRAW" bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { bool ret = false; //set the path to read the file - if(strcmp( - subghz_protocol_raw_get_last_file_name( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result), - "")) { - string_t temp_str; - string_init_printf( - temp_str, - "%s", - subghz_protocol_raw_get_last_file_name( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result)); + string_t temp_str; + string_init(temp_str); + do { + if(!flipper_format_rewind(subghz->txrx->fff_data)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(subghz->txrx->fff_data, "File_name", temp_str)) { + FURI_LOG_E(TAG, "Missing File_name"); + break; + } + path_extract_filename_no_ext(string_get_cstr(temp_str), temp_str); strcpy(subghz->file_name, string_get_cstr(temp_str)); - string_printf( - temp_str, "%s/%s%s", SUBGHZ_APP_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION); - subghz_protocol_raw_set_last_file_name( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result, string_get_cstr(temp_str)); - string_clear(temp_str); ret = true; - } + } while(false); + + string_clear(temp_str); return ret; } @@ -51,7 +51,7 @@ static void subghz_scene_read_raw_update_statusbar(void* context) { string_clear(modulation_str); } -void subghz_scene_read_raw_callback(SubghzCustomEvent event, void* context) { +void subghz_scene_read_raw_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); @@ -61,7 +61,7 @@ void subghz_scene_read_raw_callback_end_tx(void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventViewReadRAWSendStop); + subghz->view_dispatcher, SubGhzCustomEventViewReadRAWSendStop); } void subghz_scene_read_raw_on_enter(void* context) { @@ -69,46 +69,43 @@ void subghz_scene_read_raw_on_enter(void* context) { switch(subghz->txrx->rx_key_state) { case SubGhzRxKeyStateBack: - subghz_read_raw_set_status(subghz->subghz_read_raw, SubghzReadRAWStatusIDLE, ""); + subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); break; case SubGhzRxKeyStateRAWLoad: subghz_read_raw_set_status( - subghz->subghz_read_raw, SubghzReadRAWStatusLoadKeyTX, subghz->file_name); + subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, subghz->file_name); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; case SubGhzRxKeyStateRAWSave: subghz_read_raw_set_status( - subghz->subghz_read_raw, SubghzReadRAWStatusSaveKey, subghz->file_name); + subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, subghz->file_name); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; default: - subghz_read_raw_set_status(subghz->subghz_read_raw, SubghzReadRAWStatusStart, ""); + subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusStart, ""); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; } subghz_scene_read_raw_update_statusbar(subghz); + + //set callback view raw subghz_read_raw_set_callback(subghz->subghz_read_raw, subghz_scene_read_raw_callback, subghz); - subghz->txrx->protocol_result = subghz_parser_get_by_name(subghz->txrx->parser, "RAW"); - furi_assert(subghz->txrx->protocol_result); + subghz->txrx->decoder_result = + subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, "RAW"); + furi_assert(subghz->txrx->decoder_result); - subghz_worker_set_pair_callback( - subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_raw_parse); - - subghz_protocol_raw_file_encoder_worker_set_callback_end( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result, - subghz_scene_read_raw_callback_end_tx, - subghz); - - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewReadRAW); + //set filter RAW feed + subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_RAW); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReadRAW); } bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SubghzCustomEventViewReadRAWBack: + case SubGhzCustomEventViewReadRAWBack: //Stop TX if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { subghz_tx_stop(subghz); @@ -121,7 +118,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { }; //Stop save file subghz_protocol_raw_save_to_file_stop( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result); + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); subghz->state_notifications = SubGhzNotificationStateIDLE; //needed save? if((subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) || @@ -144,7 +141,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { return true; break; - case SubghzCustomEventViewReadRAWTXRXStop: + case SubGhzCustomEventViewReadRAWTXRXStop: //Stop TX if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { subghz_tx_stop(subghz); @@ -159,27 +156,27 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { return true; break; - case SubghzCustomEventViewReadRAWConfig: + case SubGhzCustomEventViewReadRAWConfig: scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerSet); + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); return true; break; - case SubghzCustomEventViewReadRAWErase: + case SubGhzCustomEventViewReadRAWErase: subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; return true; break; - case SubghzCustomEventViewReadRAWVibro: + case SubGhzCustomEventViewReadRAWVibro: notification_message(subghz->notifications, &sequence_single_vibro); return true; break; - case SubghzCustomEventViewReadRAWMore: + case SubGhzCustomEventViewReadRAWMore: if(subghz_scene_read_raw_update_filename(subghz)) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerSet); + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); return true; @@ -188,7 +185,8 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } break; - case SubghzCustomEventViewReadRAWSendStart: + case SubGhzCustomEventViewReadRAWSendStart: + if(subghz_scene_read_raw_update_filename(subghz)) { //start send subghz->state_notifications = SubGhzNotificationStateIDLE; @@ -197,10 +195,17 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - if(!subghz_tx_start(subghz)) { + //ToDo FIX + + if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); } else { DOLPHIN_DEED(DolphinDeedSubGhzSend); + // set callback end tx + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz->txrx->transmitter->protocol_instance, + subghz_scene_read_raw_callback_end_tx, + subghz); subghz->state_notifications = SubGhzNotificationStateTX; } } @@ -208,7 +213,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { return true; break; - case SubghzCustomEventViewReadRAWSendStop: + case SubGhzCustomEventViewReadRAWSendStop: subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { subghz_tx_stop(subghz); @@ -218,13 +223,14 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { return true; break; - case SubghzCustomEventViewReadRAWIDLE: + case SubGhzCustomEventViewReadRAWIDLE: if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); subghz_sleep(subghz); }; subghz_protocol_raw_save_to_file_stop( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result); + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); + subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, RAW_FILE_NAME); subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; @@ -232,16 +238,16 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { return true; break; - case SubghzCustomEventViewReadRAWREC: + case SubGhzCustomEventViewReadRAWREC: if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); } else { - subghz_get_preset_name(subghz, subghz->error_str); + //subghz_get_preset_name(subghz, subghz->error_str); if(subghz_protocol_raw_save_to_file_init( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result, + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, RAW_FILE_NAME, subghz->txrx->frequency, - string_get_cstr(subghz->error_str))) { + subghz->txrx->preset)) { DOLPHIN_DEED(DolphinDeedSubGhzRawRec); if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { @@ -258,10 +264,10 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { return true; break; - case SubghzCustomEventViewReadRAWSave: + case SubGhzCustomEventViewReadRAWSave: if(subghz_scene_read_raw_update_filename(subghz)) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerSetRAW); + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSetRAW); subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } @@ -278,7 +284,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_read_raw_update_sample_write( subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result)); + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result)); subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi()); break; case SubGhzNotificationStateTX: @@ -302,7 +308,6 @@ void subghz_scene_read_raw_on_exit(void* context) { }; subghz->state_notifications = SubGhzNotificationStateIDLE; - //Сallback restoration - subghz_worker_set_pair_callback( - subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_parse); + //filter restoration + subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); } diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/subghz/scenes/subghz_scene_receiver.c index 4ffa29fc..ac545e31 100644 --- a/applications/subghz/scenes/subghz_scene_receiver.c +++ b/applications/subghz/scenes/subghz_scene_receiver.c @@ -1,5 +1,5 @@ #include "../subghz_i.h" -#include "../views/subghz_receiver.h" +#include "../views/receiver.h" static void subghz_scene_receiver_update_statusbar(void* context) { SubGhz* subghz = context; @@ -14,7 +14,7 @@ static void subghz_scene_receiver_update_statusbar(void* context) { subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); - subghz_receiver_add_data_statusbar( + subghz_view_receiver_add_data_statusbar( subghz->subghz_receiver, string_get_cstr(frequency_str), string_get_cstr(modulation_str), @@ -23,36 +23,41 @@ static void subghz_scene_receiver_update_statusbar(void* context) { string_clear(frequency_str); string_clear(modulation_str); } else { - subghz_receiver_add_data_statusbar( + subghz_view_receiver_add_data_statusbar( subghz->subghz_receiver, string_get_cstr(history_stat_str), "", ""); subghz->state_notifications = SubGhzNotificationStateIDLE; } string_clear(history_stat_str); } -void subghz_scene_receiver_callback(SubghzCustomEvent event, void* context) { +void subghz_scene_receiver_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); } -void subghz_scene_add_to_history_callback(SubGhzProtocolCommon* parser, void* context) { +static void subghz_scene_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { furi_assert(context); SubGhz* subghz = context; string_t str_buff; string_init(str_buff); if(subghz_history_add_to_history( - subghz->txrx->history, parser, subghz->txrx->frequency, subghz->txrx->preset)) { - subghz_parser_reset(subghz->txrx->parser); + subghz->txrx->history, decoder_base, subghz->txrx->frequency, subghz->txrx->preset)) { + subghz_receiver_reset(receiver); string_reset(str_buff); + subghz_history_get_text_item_menu( subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1); - subghz_receiver_add_item_to_menu( + subghz_view_receiver_add_item_to_menu( subghz->subghz_receiver, string_get_cstr(str_buff), subghz_history_get_type_protocol( subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1)); + subghz_scene_receiver_update_statusbar(subghz); } string_clear(str_buff); @@ -70,11 +75,11 @@ void subghz_scene_receiver_on_enter(void* context) { } //Load history to receiver - subghz_receiver_exit(subghz->subghz_receiver); + subghz_view_receiver_exit(subghz->subghz_receiver); for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { string_reset(str_buff); subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i); - subghz_receiver_add_item_to_menu( + subghz_view_receiver_add_item_to_menu( subghz->subghz_receiver, string_get_cstr(str_buff), subghz_history_get_type_protocol(subghz->txrx->history, i)); @@ -82,8 +87,10 @@ void subghz_scene_receiver_on_enter(void* context) { } string_clear(str_buff); subghz_scene_receiver_update_statusbar(subghz); - subghz_receiver_set_callback(subghz->subghz_receiver, subghz_scene_receiver_callback, subghz); - subghz_parser_enable_dump(subghz->txrx->parser, subghz_scene_add_to_history_callback, subghz); + subghz_view_receiver_set_callback( + subghz->subghz_receiver, subghz_scene_receiver_callback, subghz); + subghz_receiver_set_rx_callback( + subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); subghz->state_notifications = SubGhzNotificationStateRX; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { @@ -94,9 +101,9 @@ void subghz_scene_receiver_on_enter(void* context) { subghz_begin(subghz, subghz->txrx->preset); subghz_rx(subghz, subghz->txrx->frequency); } - subghz_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); + subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewReceiver); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); } bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { @@ -104,7 +111,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SubghzCustomEventViewReceverBack: + case SubGhzCustomEventViewReceverBack: // Stop CC1101 Rx subghz->state_notifications = SubGhzNotificationStateIDLE; @@ -116,7 +123,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; subghz->txrx->idx_menu_chosen = 0; - subghz_parser_enable_dump(subghz->txrx->parser, NULL, subghz); + subghz_receiver_set_rx_callback(subghz->txrx->receiver, NULL, subghz); if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) { subghz->txrx->rx_key_state = SubGhzRxKeyStateExit; @@ -127,14 +134,16 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { } return true; break; - case SubghzCustomEventViewReceverOK: - subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver); + case SubGhzCustomEventViewReceverOK: + subghz->txrx->idx_menu_chosen = + subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); return true; break; - case SubghzCustomEventViewReceverConfig: + case SubGhzCustomEventViewReceverConfig: subghz->state_notifications = SubGhzNotificationStateIDLE; - subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver); + subghz->txrx->idx_menu_chosen = + subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); return true; break; diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index 28afc1fb..98fce89a 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/subghz/scenes/subghz_scene_receiver_config.c @@ -127,7 +127,7 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != - SubghzCustomEventManagerSet) { + SubGhzCustomEventManagerSet) { item = variable_item_list_add( subghz->variable_item_list, "Hopping:", @@ -151,7 +151,7 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, preset_text[value_index]); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewVariableItemList); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { @@ -163,5 +163,5 @@ void subghz_scene_receiver_config_on_exit(void* context) { SubGhz* subghz = context; variable_item_list_reset(subghz->variable_item_list); scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerNoSet); + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } diff --git a/applications/subghz/scenes/subghz_scene_receiver_info.c b/applications/subghz/scenes/subghz_scene_receiver_info.c index 179316d1..3cd46558 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/subghz/scenes/subghz_scene_receiver_info.c @@ -8,25 +8,24 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v if((result == GuiButtonTypeCenter) && (type == InputTypePress)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStart); + subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxStart); } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStop); + subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxStop); } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoSave); + subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoSave); } } static bool subghz_scene_receiver_info_update_parser(void* context) { SubGhz* subghz = context; - subghz->txrx->protocol_result = subghz_parser_get_by_name( - subghz->txrx->parser, - subghz_history_get_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); - - if(subghz->txrx->protocol_result->to_load_protocol != NULL) { - subghz->txrx->protocol_result->to_load_protocol( - subghz->txrx->protocol_result, + subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( + subghz->txrx->receiver, + subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); + if(subghz->txrx->decoder_result) { + subghz_protocol_decoder_base_deserialize( + subghz->txrx->decoder_result, subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); subghz->txrx->frequency = subghz_history_get_frequency(subghz->txrx->history, subghz->txrx->idx_menu_chosen); @@ -68,8 +67,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { AlignTop, FontSecondary, string_get_cstr(modulation_str)); - - subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, text); + subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text); widget_add_string_multiline_element( subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); @@ -77,14 +75,19 @@ void subghz_scene_receiver_info_on_enter(void* context) { string_clear(modulation_str); string_clear(text); - if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_file && - strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) { + if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == + SubGhzProtocolFlag_Save) { widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Save", subghz_scene_receiver_info_callback, subghz); + } + if(((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == + SubGhzProtocolFlag_Send) && + subghz->txrx->decoder_result->protocol->encoder->deserialize && + subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeStatic) { widget_add_button_element( subghz->widget, GuiButtonTypeCenter, @@ -92,20 +95,19 @@ void subghz_scene_receiver_info_on_enter(void* context) { subghz_scene_receiver_info_callback, subghz); } - } else { widget_add_icon_element(subghz->widget, 32, 12, &I_DolphinFirstStart7_61x51); widget_add_string_element( subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse."); } - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneReceiverInfoTxStart) { + if(event.event == SubGhzCustomEventSceneReceiverInfoTxStart) { //CC1101 Stop RX -> Start TX if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { subghz->txrx->hopper_state = SubGhzHopperStatePause; @@ -118,14 +120,17 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) } if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE || subghz->txrx->txrx_state == SubGhzTxRxStateSleep) { - if(!subghz_tx_start(subghz)) { + if(!subghz_tx_start( + subghz, + subghz_history_get_raw_data( + subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); } else { subghz->state_notifications = SubGhzNotificationStateTX; } } return true; - } else if(event.event == SubghzCustomEventSceneReceiverInfoTxStop) { + } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { //CC1101 Stop Tx -> Start RX subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { @@ -140,7 +145,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) } subghz->state_notifications = SubGhzNotificationStateRX; return true; - } else if(event.event == SubghzCustomEventSceneReceiverInfoSave) { + } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { //CC1101 Stop RX -> Save subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { @@ -153,8 +158,9 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) if(!subghz_scene_receiver_info_update_parser(subghz)) { return false; } - if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_file && - strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) { + + if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == + SubGhzProtocolFlag_Save) { subghz_file_name_clear(subghz); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/subghz/scenes/subghz_scene_save_name.c index 27db3ca6..6f552ff9 100644 --- a/applications/subghz/scenes/subghz_scene_save_name.c +++ b/applications/subghz/scenes/subghz_scene_save_name.c @@ -1,13 +1,13 @@ #include "../subghz_i.h" #include #include "../helpers/subghz_custom_event.h" -#include +#include #include void subghz_scene_save_name_text_input_callback(void* context) { furi_assert(context); SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneSaveName); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneSaveName); } void subghz_scene_save_name_on_enter(void* context) { @@ -24,10 +24,10 @@ void subghz_scene_save_name_on_enter(void* context) { } else { strcpy(subghz->file_name_tmp, subghz->file_name); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != - SubghzCustomEventManagerNoSet) { + SubGhzCustomEventManagerNoSet) { subghz_get_next_name_file(subghz); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == - SubghzCustomEventManagerSetRAW) { + SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; } } @@ -46,7 +46,7 @@ void subghz_scene_save_name_on_enter(void* context) { validator_is_file_alloc_init(SUBGHZ_APP_FOLDER, SUBGHZ_APP_EXTENSION); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTextInput); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); } bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { @@ -56,22 +56,35 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneSaveName) { + if(event.event == SubGhzCustomEventSceneSaveName) { if(strcmp(subghz->file_name, "")) { if(strcmp(subghz->file_name_tmp, "")) { if(!subghz_rename_file(subghz)) { return false; } } else { - subghz_save_protocol_to_file(subghz, subghz->file_name); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) != + SubGhzCustomEventManagerNoSet) { + subghz_save_protocol_to_file( + subghz, subghz->txrx->fff_data, subghz->file_name); + scene_manager_set_scene_state( + subghz->scene_manager, + SubGhzSceneSetType, + SubGhzCustomEventManagerNoSet); + } else { + subghz_save_protocol_to_file( + subghz, + subghz_history_get_raw_data( + subghz->txrx->history, subghz->txrx->idx_menu_chosen), + subghz->file_name); + } } if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != - SubghzCustomEventManagerNoSet) { - subghz_protocol_raw_set_last_file_name( - (SubGhzProtocolRAW*)subghz->txrx->protocol_result, subghz->file_name); + SubGhzCustomEventManagerNoSet) { + subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, subghz->file_name); scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerNoSet); + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { subghz_file_name_clear(subghz); } diff --git a/applications/subghz/scenes/subghz_scene_save_success.c b/applications/subghz/scenes/subghz_scene_save_success.c index 3d9d28d1..cfd2c0bd 100644 --- a/applications/subghz/scenes/subghz_scene_save_success.c +++ b/applications/subghz/scenes/subghz_scene_save_success.c @@ -5,7 +5,7 @@ void subghz_scene_save_success_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneSaveSuccess); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneSaveSuccess); } void subghz_scene_save_success_on_enter(void* context) { @@ -20,13 +20,13 @@ void subghz_scene_save_success_on_enter(void* context) { popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_save_success_popup_callback); popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewPopup); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); } bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneSaveSuccess) { + if(event.event == SubGhzCustomEventSceneSaveSuccess) { if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneReceiver)) { subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWSave; diff --git a/applications/subghz/scenes/subghz_scene_saved.c b/applications/subghz/scenes/subghz_scene_saved.c index 4dcb103a..ae64b797 100644 --- a/applications/subghz/scenes/subghz_scene_saved.c +++ b/applications/subghz/scenes/subghz_scene_saved.c @@ -4,7 +4,7 @@ void subghz_scene_saved_on_enter(void* context) { SubGhz* subghz = context; if(subghz_load_protocol_from_file(subghz)) { - if((!strcmp(subghz->txrx->protocol_result->name, "RAW"))) { + if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); } else { diff --git a/applications/subghz/scenes/subghz_scene_saved_menu.c b/applications/subghz/scenes/subghz_scene_saved_menu.c index 833bdfd5..a65830f4 100644 --- a/applications/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/subghz/scenes/subghz_scene_saved_menu.c @@ -38,7 +38,7 @@ void subghz_scene_saved_menu_on_enter(void* context) { subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu)); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); } bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index 0d8f0ad2..79d6bcf6 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -1,6 +1,11 @@ #include "../subghz_i.h" -#include "../lib/subghz/protocols/subghz_protocol_keeloq.h" +#include +#include #include +#include +#include + +#define TAG "SubGhzSetType" enum SubmenuIndex { SubmenuIndexPricenton, @@ -15,15 +20,52 @@ enum SubmenuIndex { SubmenuIndexDoorHan, }; -bool subghz_scene_set_type_submenu_to_find_protocol(void* context, const char* protocol_name) { +bool subghz_scene_set_type_submenu_gen_data_protocol( + void* context, + const char* protocol_name, + uint64_t key, + uint32_t bit) { + furi_assert(context); SubGhz* subghz = context; - subghz->txrx->protocol_result = subghz_parser_get_by_name(subghz->txrx->parser, protocol_name); - if(subghz->txrx->protocol_result == NULL) { + + bool res = false; + + subghz->txrx->decoder_result = + subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); + + if(subghz->txrx->decoder_result == NULL) { string_set(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return false; } - return true; + + do { + Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); + stream_clean(fff_data_stream); + if(!subghz_protocol_decoder_base_serialize( + subghz->txrx->decoder_result, + subghz->txrx->fff_data, + subghz_frequencies[subghz_frequencies_433_92], + FuriHalSubGhzPresetOok650Async)) { + FURI_LOG_E(TAG, "Unable to serialize"); + break; + } + if(!flipper_format_update_uint32(subghz->txrx->fff_data, "Bit", &bit, 1)) { + FURI_LOG_E(TAG, "Unable to update Bit"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (key >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(subghz->txrx->fff_data, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Key"); + break; + } + res = true; + } while(false); + return res; } void subghz_scene_set_type_submenu_callback(void* context, uint32_t index) { @@ -90,7 +132,7 @@ void subghz_scene_set_type_on_enter(void* context) { submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType)); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); } bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { @@ -98,54 +140,45 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { bool generated_protocol = false; if(event.type == SceneManagerEventTypeCustom) { + //ToDo Fix uint32_t key = subghz_random_serial(); switch(event.event) { case SubmenuIndexPricenton: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Princeton")) { - subghz->txrx->protocol_result->code_last_count_bit = 24; - key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 - subghz->txrx->protocol_result->code_last_found = key; + key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "Princeton", key, 24)) { + uint32_t te = 400; + flipper_format_update_uint32(subghz->txrx->fff_data, "TE", (uint32_t*)&te, 1); generated_protocol = true; } break; case SubmenuIndexNiceFlo12bit: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Nice FLO")) { - subghz->txrx->protocol_result->code_last_count_bit = 12; - key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 - subghz->txrx->protocol_result->code_last_found = key; + key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "Nice FLO", key, 12)) { generated_protocol = true; } break; case SubmenuIndexNiceFlo24bit: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Nice FLO")) { - subghz->txrx->protocol_result->code_last_count_bit = 24; - key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 - subghz->txrx->protocol_result->code_last_found = key; + key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "Nice FLO", key, 24)) { generated_protocol = true; } break; case SubmenuIndexCAME12bit: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME")) { - subghz->txrx->protocol_result->code_last_count_bit = 12; - key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 - subghz->txrx->protocol_result->code_last_found = key; + key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "CAME", key, 12)) { generated_protocol = true; } break; case SubmenuIndexCAME24bit: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME")) { - subghz->txrx->protocol_result->code_last_count_bit = 24; - key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 - subghz->txrx->protocol_result->code_last_found = key; + key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "CAME", key, 24)) { generated_protocol = true; } break; case SubmenuIndexCAMETwee: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME TWEE")) { - subghz->txrx->protocol_result->code_last_count_bit = 54; - key = (key & 0x0FFFFFF0); - subghz->txrx->protocol_result->code_last_found = 0x003FFF7200000000 | - (key ^ 0xE0E0E0EE); + key = (key & 0x0FFFFFF0); + key = 0x003FFF7200000000 | (key ^ 0xE0E0E0EE); + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "CAME TWEE", key, 54)) { generated_protocol = true; } break; @@ -156,32 +189,34 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { // /* code */ // break; case SubmenuIndexGateTX: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "GateTX")) { - subghz->txrx->protocol_result->code_last_count_bit = 24; - key = (key & 0x00F0FF00) | 0xF << 16 | 0x40; //btn 0xF, 0xC, 0xA, 0x6 (?) - subghz->txrx->protocol_result->code_last_found = - subghz_protocol_common_reverse_key( - key, subghz->txrx->protocol_result->code_last_count_bit); + key = (key & 0x00F0FF00) | 0xF << 16 | 0x40; //btn 0xF, 0xC, 0xA, 0x6 (?) + uint64_t rev_key = subghz_protocol_blocks_reverse_key(key, 24); + if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "GateTX", rev_key, 24)) { generated_protocol = true; } break; case SubmenuIndexDoorHan: - if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "KeeLoq")) { - subghz->txrx->protocol_result->code_last_count_bit = 64; - subghz->txrx->protocol_result->serial = key & 0x0FFFFFFF; - subghz->txrx->protocol_result->btn = 0x2; //btn 0x1, 0x2, 0x4, 0x8 - subghz->txrx->protocol_result->cnt = 0x0003; - if(subghz_protocol_keeloq_set_manufacture_name( - subghz->txrx->protocol_result, "DoorHan")) { - subghz->txrx->protocol_result->code_last_found = - subghz_protocol_keeloq_gen_key(subghz->txrx->protocol_result); - generated_protocol = true; - } else { - generated_protocol = false; - string_set( - subghz->error_str, "Function requires\nan SD card with\nfresh databases."); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); - } + subghz->txrx->transmitter = + subghz_transmitter_alloc_init(subghz->txrx->environment, "KeeLoq"); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz->txrx->transmitter->protocol_instance, + subghz->txrx->fff_data, + key & 0x0FFFFFFF, + 0x2, + 0x0003, + "DoorHan", + subghz_frequencies[subghz_frequencies_433_92], + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; default: @@ -190,10 +225,10 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } if(generated_protocol) { - subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; - subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; subghz_file_name_clear(subghz); DOLPHIN_DEED(DolphinDeedSubGhzAddManually); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; } diff --git a/applications/subghz/scenes/subghz_scene_show_error.c b/applications/subghz/scenes/subghz_scene_show_error.c index 697f6b9f..993da110 100644 --- a/applications/subghz/scenes/subghz_scene_show_error.c +++ b/applications/subghz/scenes/subghz_scene_show_error.c @@ -7,10 +7,10 @@ void subghz_scene_show_error_callback(GuiButtonType result, InputType type, void if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneShowErrorOk); + subghz->view_dispatcher, SubGhzCustomEventSceneShowErrorOk); } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventSceneShowErrorBack); + subghz->view_dispatcher, SubGhzCustomEventSceneShowErrorBack); } } @@ -28,7 +28,7 @@ void subghz_scene_show_error_on_enter(void* context) { FontSecondary, string_get_cstr(subghz->error_str)); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubghzCustomEventManagerSet) { + SubGhzCustomEventManagerSet) { widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Ok", subghz_scene_show_error_callback, subghz); } @@ -36,14 +36,14 @@ void subghz_scene_show_error_on_enter(void* context) { widget_add_button_element( subghz->widget, GuiButtonTypeLeft, "Back", subghz_scene_show_error_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubghzCustomEventManagerSet) { + SubGhzCustomEventManagerSet) { return false; } else { scene_manager_search_and_switch_to_previous_scene( @@ -51,15 +51,15 @@ bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { } return true; } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneShowErrorOk) { + if(event.event == SubGhzCustomEventSceneShowErrorOk) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubghzCustomEventManagerSet) { + SubGhzCustomEventManagerSet) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } return true; - } else if(event.event == SubghzCustomEventSceneShowErrorBack) { + } else if(event.event == SubGhzCustomEventSceneShowErrorBack) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubghzCustomEventManagerSet) { + SubGhzCustomEventManagerSet) { //exit app if(!scene_manager_previous_scene(subghz->scene_manager)) { scene_manager_stop(subghz->scene_manager); @@ -78,7 +78,7 @@ bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { void subghz_scene_show_error_on_exit(void* context) { SubGhz* subghz = context; scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneShowError, SubghzCustomEventManagerNoSet); + subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerNoSet); widget_reset(subghz->widget); string_reset(subghz->error_str); } diff --git a/applications/subghz/scenes/subghz_scene_show_error_sub.c b/applications/subghz/scenes/subghz_scene_show_error_sub.c index 566e6451..00c4c325 100644 --- a/applications/subghz/scenes/subghz_scene_show_error_sub.c +++ b/applications/subghz/scenes/subghz_scene_show_error_sub.c @@ -3,7 +3,7 @@ void subghz_scene_show_error_sub_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneShowErrorSub); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneShowErrorSub); } void subghz_scene_show_error_sub_on_enter(void* context) { @@ -17,13 +17,13 @@ void subghz_scene_show_error_sub_on_enter(void* context) { popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_show_error_sub_popup_callback); popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewPopup); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); } bool subghz_scene_show_error_sub_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneShowErrorSub) { + if(event.event == SubGhzCustomEventSceneShowErrorSub) { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); return true; diff --git a/applications/subghz/scenes/subghz_scene_show_only_rx.c b/applications/subghz/scenes/subghz_scene_show_only_rx.c index a62969d4..d5069940 100644 --- a/applications/subghz/scenes/subghz_scene_show_only_rx.c +++ b/applications/subghz/scenes/subghz_scene_show_only_rx.c @@ -3,7 +3,7 @@ void subghz_scene_show_only_rx_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneShowOnlyRX); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneShowOnlyRX); } void subghz_scene_show_only_rx_on_enter(void* context) { @@ -23,13 +23,13 @@ void subghz_scene_show_only_rx_on_enter(void* context) { popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_show_only_rx_popup_callback); popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewPopup); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); } const bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventSceneShowOnlyRX) { + if(event.event == SubGhzCustomEventSceneShowOnlyRX) { scene_manager_previous_scene(subghz->scene_manager); return true; } diff --git a/applications/subghz/scenes/subghz_scene_start.c b/applications/subghz/scenes/subghz_scene_start.c index 14108e34..53a1e0fa 100644 --- a/applications/subghz/scenes/subghz_scene_start.c +++ b/applications/subghz/scenes/subghz_scene_start.c @@ -48,7 +48,7 @@ void subghz_scene_start_on_enter(void* context) { submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart)); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); } bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/subghz/scenes/subghz_scene_test.c b/applications/subghz/scenes/subghz_scene_test.c index 48c49e0e..65f9bbde 100644 --- a/applications/subghz/scenes/subghz_scene_test.c +++ b/applications/subghz/scenes/subghz_scene_test.c @@ -28,7 +28,7 @@ void subghz_scene_test_on_enter(void* context) { submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneTest)); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); } bool subghz_scene_test_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/subghz/scenes/subghz_scene_test_carrier.c b/applications/subghz/scenes/subghz_scene_test_carrier.c index e2fa6634..c6d336ea 100644 --- a/applications/subghz/scenes/subghz_scene_test_carrier.c +++ b/applications/subghz/scenes/subghz_scene_test_carrier.c @@ -1,7 +1,7 @@ #include "../subghz_i.h" #include "../views/subghz_test_carrier.h" -void subghz_scene_test_carrier_callback(SubghzTestCarrierEvent event, void* context) { +void subghz_scene_test_carrier_callback(SubGhzTestCarrierEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); @@ -11,13 +11,13 @@ void subghz_scene_test_carrier_on_enter(void* context) { SubGhz* subghz = context; subghz_test_carrier_set_callback( subghz->subghz_test_carrier, subghz_scene_test_carrier_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTestCarrier); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); } bool subghz_scene_test_carrier_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzTestCarrierEventOnlyRx) { + if(event.event == SubGhzTestCarrierEventOnlyRx) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); return true; } diff --git a/applications/subghz/scenes/subghz_scene_test_packet.c b/applications/subghz/scenes/subghz_scene_test_packet.c index a49ec480..243dfd55 100644 --- a/applications/subghz/scenes/subghz_scene_test_packet.c +++ b/applications/subghz/scenes/subghz_scene_test_packet.c @@ -1,7 +1,7 @@ #include "../subghz_i.h" #include "../views/subghz_test_packet.h" -void subghz_scene_test_packet_callback(SubghzTestPacketEvent event, void* context) { +void subghz_scene_test_packet_callback(SubGhzTestPacketEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); @@ -11,13 +11,13 @@ void subghz_scene_test_packet_on_enter(void* context) { SubGhz* subghz = context; subghz_test_packet_set_callback( subghz->subghz_test_packet, subghz_scene_test_packet_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTestPacket); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); } bool subghz_scene_test_packet_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzTestPacketEventOnlyRx) { + if(event.event == SubGhzTestPacketEventOnlyRx) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); return true; } diff --git a/applications/subghz/scenes/subghz_scene_test_static.c b/applications/subghz/scenes/subghz_scene_test_static.c index 6c6099ec..2ca47a08 100644 --- a/applications/subghz/scenes/subghz_scene_test_static.c +++ b/applications/subghz/scenes/subghz_scene_test_static.c @@ -1,7 +1,7 @@ #include "../subghz_i.h" #include "../views/subghz_test_static.h" -void subghz_scene_test_static_callback(SubghzTestStaticEvent event, void* context) { +void subghz_scene_test_static_callback(SubGhzTestStaticEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); @@ -11,13 +11,13 @@ void subghz_scene_test_static_on_enter(void* context) { SubGhz* subghz = context; subghz_test_static_set_callback( subghz->subghz_test_static, subghz_scene_test_static_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewStatic); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdStatic); } bool subghz_scene_test_static_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzTestStaticEventOnlyRx) { + if(event.event == SubGhzTestStaticEventOnlyRx) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); return true; } diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/subghz/scenes/subghz_scene_transmitter.c index 48ed0fd8..36d8480c 100644 --- a/applications/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/subghz/scenes/subghz_scene_transmitter.c @@ -1,18 +1,18 @@ #include "../subghz_i.h" -#include "../views/subghz_transmitter.h" -#include +#include "../views/transmitter.h" #include -void subghz_scene_transmitter_callback(SubghzCustomEvent event, void* context) { +void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, event); } bool subghz_scene_transmitter_update_data_show(void* context) { + //ToDo Fix SubGhz* subghz = context; - if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->get_upload_protocol) { + if(subghz->txrx->decoder_result) { string_t key_str; string_t frequency_str; string_t modulation_str; @@ -21,19 +21,18 @@ bool subghz_scene_transmitter_update_data_show(void* context) { string_init(frequency_str); string_init(modulation_str); uint8_t show_button = 0; - subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, key_str); - if((!strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) && - (!strcmp( - subghz_protocol_keeloq_get_manufacture_name(subghz->txrx->protocol_result), - "Unknown"))) { - show_button = 0; - } else { + subghz_protocol_decoder_base_deserialize( + subghz->txrx->decoder_result, subghz->txrx->fff_data); + subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str); + + if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == + SubGhzProtocolFlag_Send) { show_button = 1; } subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); - subghz_transmitter_add_data_to_show( + subghz_view_transmitter_add_data_to_show( subghz->subghz_transmitter, string_get_cstr(key_str), string_get_cstr(frequency_str), @@ -54,27 +53,27 @@ void subghz_scene_transmitter_on_enter(void* context) { DOLPHIN_DEED(DolphinDeedSubGhzSend); if(!subghz_scene_transmitter_update_data_show(subghz)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubghzCustomEventViewTransmitterError); + subghz->view_dispatcher, SubGhzCustomEventViewTransmitterError); } - subghz_transmitter_set_callback( + subghz_view_transmitter_set_callback( subghz->subghz_transmitter, subghz_scene_transmitter_callback, subghz); subghz->state_notifications = SubGhzNotificationStateIDLE; - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTransmitter); } bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzCustomEventViewTransmitterSendStart) { + if(event.event == SubGhzCustomEventViewTransmitterSendStart) { subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); } if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - if(!subghz_tx_start(subghz)) { + if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); } else { subghz->state_notifications = SubGhzNotificationStateTX; @@ -82,19 +81,19 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } } return true; - } else if(event.event == SubghzCustomEventViewTransmitterSendStop) { + } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { subghz_tx_stop(subghz); subghz_sleep(subghz); } return true; - } else if(event.event == SubghzCustomEventViewTransmitterBack) { + } else if(event.event == SubGhzCustomEventViewTransmitterBack) { subghz->state_notifications = SubGhzNotificationStateIDLE; scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); return true; - } else if(event.event == SubghzCustomEventViewTransmitterError) { + } else if(event.event == SubGhzCustomEventViewTransmitterError) { string_set(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index 9a6809ea..c4324e7f 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -85,80 +85,80 @@ SubGhz* subghz_alloc() { // SubMenu subghz->submenu = submenu_alloc(); view_dispatcher_add_view( - subghz->view_dispatcher, SubGhzViewMenu, submenu_get_view(subghz->submenu)); + subghz->view_dispatcher, SubGhzViewIdMenu, submenu_get_view(subghz->submenu)); // Receiver - subghz->subghz_receiver = subghz_receiver_alloc(); + subghz->subghz_receiver = subghz_view_receiver_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewReceiver, - subghz_receiver_get_view(subghz->subghz_receiver)); + SubGhzViewIdReceiver, + subghz_view_receiver_get_view(subghz->subghz_receiver)); // Popup subghz->popup = popup_alloc(); view_dispatcher_add_view( - subghz->view_dispatcher, SubGhzViewPopup, popup_get_view(subghz->popup)); + subghz->view_dispatcher, SubGhzViewIdPopup, popup_get_view(subghz->popup)); // Text Input subghz->text_input = text_input_alloc(); view_dispatcher_add_view( - subghz->view_dispatcher, SubGhzViewTextInput, text_input_get_view(subghz->text_input)); + subghz->view_dispatcher, SubGhzViewIdTextInput, text_input_get_view(subghz->text_input)); // Custom Widget subghz->widget = widget_alloc(); view_dispatcher_add_view( - subghz->view_dispatcher, SubGhzViewWidget, widget_get_view(subghz->widget)); + subghz->view_dispatcher, SubGhzViewIdWidget, widget_get_view(subghz->widget)); //Dialog subghz->dialogs = furi_record_open("dialogs"); // Transmitter - subghz->subghz_transmitter = subghz_transmitter_alloc(); + subghz->subghz_transmitter = subghz_view_transmitter_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewTransmitter, - subghz_transmitter_get_view(subghz->subghz_transmitter)); + SubGhzViewIdTransmitter, + subghz_view_transmitter_get_view(subghz->subghz_transmitter)); // Variable Item List subghz->variable_item_list = variable_item_list_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewVariableItemList, + SubGhzViewIdVariableItemList, variable_item_list_get_view(subghz->variable_item_list)); // Frequency Analyzer subghz->subghz_frequency_analyzer = subghz_frequency_analyzer_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewFrequencyAnalyzer, + SubGhzViewIdFrequencyAnalyzer, subghz_frequency_analyzer_get_view(subghz->subghz_frequency_analyzer)); // Read RAW subghz->subghz_read_raw = subghz_read_raw_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewReadRAW, + SubGhzViewIdReadRAW, subghz_read_raw_get_view(subghz->subghz_read_raw)); // Carrier Test Module subghz->subghz_test_carrier = subghz_test_carrier_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewTestCarrier, + SubGhzViewIdTestCarrier, subghz_test_carrier_get_view(subghz->subghz_test_carrier)); // Packet Test subghz->subghz_test_packet = subghz_test_packet_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewTestPacket, + SubGhzViewIdTestPacket, subghz_test_packet_get_view(subghz->subghz_test_packet)); // Static send subghz->subghz_test_static = subghz_test_static_alloc(); view_dispatcher_add_view( subghz->view_dispatcher, - SubGhzViewStatic, + SubGhzViewIdStatic, subghz_test_static_get_view(subghz->subghz_test_static)); //init Worker & Protocol & History @@ -170,12 +170,21 @@ SubGhz* subghz_alloc() { subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; subghz->txrx->history = subghz_history_alloc(); subghz->txrx->worker = subghz_worker_alloc(); - subghz->txrx->parser = subghz_parser_alloc(); + subghz->txrx->fff_data = flipper_format_string_alloc(); + + subghz->txrx->environment = subghz_environment_alloc(); + subghz_environment_set_came_atomo_rainbow_table_file_name( + subghz->txrx->environment, "/ext/subghz/assets/came_atomo"); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + subghz->txrx->environment, "/ext/subghz/assets/nice_flor_s"); + subghz->txrx->receiver = subghz_receiver_alloc(subghz->txrx->environment); + subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); + subghz_worker_set_overrun_callback( - subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_parser_reset); + subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); subghz_worker_set_pair_callback( - subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_parse); - subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->parser); + subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->receiver); //Init Error_str string_init(subghz->error_str); @@ -187,54 +196,54 @@ void subghz_free(SubGhz* subghz) { furi_assert(subghz); // Packet Test - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTestPacket); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); subghz_test_packet_free(subghz->subghz_test_packet); // Carrier Test - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTestCarrier); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); subghz_test_carrier_free(subghz->subghz_test_carrier); // Static - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewStatic); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdStatic); subghz_test_static_free(subghz->subghz_test_static); // Receiver - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewReceiver); - subghz_receiver_free(subghz->subghz_receiver); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReceiver); + subghz_view_receiver_free(subghz->subghz_receiver); // TextInput - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTextInput); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTextInput); text_input_free(subghz->text_input); // Custom Widget - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewWidget); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdWidget); widget_free(subghz->widget); //Dialog furi_record_close("dialogs"); // Transmitter - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTransmitter); - subghz_transmitter_free(subghz->subghz_transmitter); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTransmitter); + subghz_view_transmitter_free(subghz->subghz_transmitter); // Variable Item List - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewVariableItemList); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); variable_item_list_free(subghz->variable_item_list); // Frequency Analyzer - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer); subghz_frequency_analyzer_free(subghz->subghz_frequency_analyzer); // Read RAW - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewReadRAW); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW); subghz_read_raw_free(subghz->subghz_read_raw); // Submenu - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdMenu); submenu_free(subghz->submenu); // Popup - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewPopup); + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPopup); popup_free(subghz->popup); // Scene manager @@ -248,8 +257,10 @@ void subghz_free(SubGhz* subghz) { subghz->gui = NULL; //Worker & Protocol & History - subghz_parser_free(subghz->txrx->parser); + subghz_receiver_free(subghz->txrx->receiver); + subghz_environment_free(subghz->txrx->environment); subghz_worker_free(subghz->txrx->worker); + flipper_format_free(subghz->txrx->fff_data); subghz_history_free(subghz->txrx->history); free(subghz->txrx); @@ -268,12 +279,10 @@ int32_t subghz_app(void* p) { SubGhz* subghz = subghz_alloc(); //Load database - bool load_database = - subghz_parser_load_keeloq_file(subghz->txrx->parser, "/ext/subghz/assets/keeloq_mfcodes"); - subghz_parser_load_keeloq_file(subghz->txrx->parser, "/ext/subghz/assets/keeloq_mfcodes_user"); - subghz_parser_load_nice_flor_s_file(subghz->txrx->parser, "/ext/subghz/assets/nice_flor_s_rx"); - subghz_parser_load_came_atomo_file(subghz->txrx->parser, "/ext/subghz/assets/came_atomo"); - + bool load_database = subghz_environment_load_keystore( + subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes"); + subghz_environment_load_keystore( + subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user"); // Check argument and run corresponding scene if(p && subghz_key_load(subghz, p)) { string_t filename; @@ -282,8 +291,7 @@ int32_t subghz_app(void* p) { path_extract_filename_no_ext(p, filename); strcpy(subghz->file_name, string_get_cstr(filename)); string_clear(filename); - - if((!strcmp(subghz->txrx->protocol_result->name, "RAW"))) { + if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { //Load Raw TX subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); @@ -296,7 +304,7 @@ int32_t subghz_app(void* p) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } else { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneShowError, SubghzCustomEventManagerSet); + subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerSet); string_set( subghz->error_str, "No SD card or\ndatabase found.\nSome app function\nmay be reduced."); diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index 37946298..7ac73869 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -5,14 +5,15 @@ #include #include -#include #include -#include -#include + +#include +#include #include "helpers/subghz_chat.h" #include +#include #define SUBGHZ_FREQUENCY_RANGE_STR \ "299999755...348000000 or 386999938...464000000 or 778999847...928000000" @@ -134,21 +135,35 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { key, repeat); - SubGhzDecoderPrinceton* protocol = subghz_decoder_princeton_alloc(); - protocol->common.code_last_found = key; - protocol->common.code_last_count_bit = 24; + string_t flipper_format_string; + string_init_printf( + flipper_format_string, + "Protocol: Princeton\n" + "Bit: 24\n" + "Key: 00 00 00 00 00 %X %X %X\n" + "TE: 403\n" + "Repeat: %d\n", + (uint8_t)((key >> 16) & 0xFF), + (uint8_t)((key >> 8) & 0xFF), + (uint8_t)(key & 0xFF), + repeat); + FlipperFormat* flipper_format = flipper_format_string_alloc(); + Stream* stream = flipper_format_get_raw_stream(flipper_format); + stream_clean(stream); + stream_write_cstring(stream, string_get_cstr(flipper_format_string)); - SubGhzProtocolCommonEncoder* encoder = subghz_protocol_encoder_common_alloc(); - encoder->repeat = repeat; + SubGhzEnvironment* environment = subghz_environment_alloc(); + + SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton"); + subghz_transmitter_deserialize(transmitter, flipper_format); - subghz_protocol_princeton_send_key(protocol, encoder); furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_power_suppress_charge_enter(); - furi_hal_subghz_start_async_tx(subghz_protocol_encoder_common_yield, encoder); + furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { printf("."); @@ -160,8 +175,9 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { furi_hal_power_suppress_charge_exit(); - subghz_decoder_princeton_free(protocol); - subghz_protocol_encoder_common_free(encoder); + flipper_format_free(flipper_format); + subghz_transmitter_free(transmitter); + subghz_environment_free(environment); } typedef struct { @@ -170,7 +186,7 @@ typedef struct { size_t packet_count; } SubGhzCliCommandRx; -static void subghz_cli_command_rx_callback(bool level, uint32_t duration, void* context) { +static void subghz_cli_command_rx_capture_callback(bool level, uint32_t duration, void* context) { SubGhzCliCommandRx* instance = context; BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -185,10 +201,19 @@ static void subghz_cli_command_rx_callback(bool level, uint32_t duration, void* portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } -static void subghz_cli_command_rx_text_callback(string_t text, void* context) { +static void subghz_cli_command_rx_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { SubGhzCliCommandRx* instance = context; instance->packet_count++; + + string_t text; + string_init(text); + subghz_protocol_decoder_base_get_string(decoder_base, text); + subghz_receiver_reset(receiver); printf("%s", string_get_cstr(text)); + string_clear(text); } void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { @@ -214,12 +239,16 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { instance->stream = xStreamBufferCreate(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); furi_check(instance->stream); - SubGhzParser* parser = subghz_parser_alloc(); - subghz_parser_load_keeloq_file(parser, "/ext/subghz/assets/keeloq_mfcodes"); - subghz_parser_load_keeloq_file(parser, "/ext/subghz/assets/keeloq_mfcodes_user"); - subghz_parser_load_nice_flor_s_file(parser, "/ext/subghz/assets/nice_flor_s_rx"); - subghz_parser_load_came_atomo_file(parser, "/ext/subghz/assets/came_atomo"); - subghz_parser_enable_dump_text(parser, subghz_cli_command_rx_text_callback, instance); + SubGhzEnvironment* environment = subghz_environment_alloc(); + subghz_environment_load_keystore(environment, "/ext/subghz/assets/keeloq_mfcodes"); + subghz_environment_set_came_atomo_rainbow_table_file_name( + environment, "/ext/subghz/assets/came_atomo"); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + environment, "/ext/subghz/assets/nice_flor_s"); + + SubGhzReceiver* receiver = subghz_receiver_alloc(environment); + subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); + subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); // Configure radio furi_hal_subghz_reset(); @@ -230,7 +259,7 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { furi_hal_power_suppress_charge_enter(); // Prepare and start RX - furi_hal_subghz_start_async_rx(subghz_cli_command_rx_callback, instance); + furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); // Wait for packets to arrive printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); @@ -241,11 +270,11 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { if(ret == sizeof(LevelDuration)) { if(level_duration_is_reset(level_duration)) { printf("."); - subghz_parser_reset(parser); + subghz_receiver_reset(receiver); } else { bool level = level_duration_get_level(level_duration); uint32_t duration = level_duration_get_duration(level_duration); - subghz_parser_parse(parser, level, duration); + subghz_receiver_decode(receiver, level, duration); } } } @@ -259,7 +288,8 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { printf("\r\nPackets recieved %u\r\n", instance->packet_count); // Cleanup - subghz_parser_free(parser); + subghz_receiver_free(receiver); + subghz_environment_free(environment); vStreamBufferDelete(instance->stream); free(instance); } @@ -416,7 +446,7 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { string_t sysmsg; string_init(sysmsg); bool exit = false; - SubghzChatEvent chat_event; + SubGhzChatEvent chat_event; NotificationApp* notification = furi_record_open("notification"); @@ -428,10 +458,10 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { while(!exit) { chat_event = subghz_chat_worker_get_event_chat(subghz_chat); switch(chat_event.event) { - case SubghzChatEventInputData: + case SubGhzChatEventInputData: if(chat_event.c == CliSymbolAsciiETX) { printf("\r\n"); - chat_event.event = SubghzChatEventUserExit; + chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); break; } else if( @@ -478,7 +508,7 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { fflush(stdout); string_push_back(input, chat_event.c); break; - case SubghzChatEventRXData: + case SubGhzChatEventRXData: do { memset(message, 0x00, message_max_len); size_t len = subghz_chat_worker_read(subghz_chat, message, message_max_len); @@ -497,10 +527,10 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { } } while(subghz_chat_worker_available(subghz_chat)); break; - case SubghzChatEventNewMessage: + case SubGhzChatEventNewMessage: notification_message(notification, &sequence_single_vibro); break; - case SubghzChatEventUserEntrance: + case SubGhzChatEventUserEntrance: string_printf( sysmsg, "\033[0;34m%s joined chat.\033[0m\r\n", @@ -510,7 +540,7 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { (uint8_t*)string_get_cstr(sysmsg), strlen(string_get_cstr(sysmsg))); break; - case SubghzChatEventUserExit: + case SubGhzChatEventUserExit: string_printf( sysmsg, "\033[0;31m%s left chat.\033[0m\r\n", furi_hal_version_get_name_ptr()); subghz_chat_worker_write( diff --git a/applications/subghz/subghz_history.c b/applications/subghz/subghz_history.c index bb821e40..dc8d4fc6 100644 --- a/applications/subghz/subghz_history.c +++ b/applications/subghz/subghz_history.c @@ -1,70 +1,83 @@ #include "subghz_history.h" -#include -#include -#include -#include +#include +#include #include #include #define SUBGHZ_HISTORY_MAX 50 +#define TAG "SubGhzHistory" -typedef struct SubGhzHistoryStruct SubGhzHistoryStruct; - -struct SubGhzHistoryStruct { - const char* name; - const char* manufacture_name; - uint8_t type_protocol; - uint8_t code_count_bit; - uint64_t code_found; - uint32_t data1; +typedef struct { + string_t item_str; + FlipperFormat* flipper_string; + uint8_t type; FuriHalSubGhzPreset preset; - uint32_t real_frequency; -}; + uint32_t frequency; +} SubGhzHistoryItem; + +ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST) + +#define M_OPL_SubGhzHistoryItemArray_t() ARRAY_OPLIST(SubGhzHistoryItemArray, M_POD_OPLIST) + +typedef struct { + SubGhzHistoryItemArray_t data; +} SubGhzHistoryStruct; struct SubGhzHistory { uint32_t last_update_timestamp; uint16_t last_index_write; - uint64_t code_last_found; - SubGhzHistoryStruct history[SUBGHZ_HISTORY_MAX]; - SubGhzProtocolCommonLoad data; + uint8_t code_last_hash_data; + string_t tmp_string; + SubGhzHistoryStruct* history; }; SubGhzHistory* subghz_history_alloc(void) { SubGhzHistory* instance = malloc(sizeof(SubGhzHistory)); + string_init(instance->tmp_string); + instance->history = malloc(sizeof(SubGhzHistoryStruct)); + SubGhzHistoryItemArray_init(instance->history->data); return instance; } void subghz_history_free(SubGhzHistory* instance) { furi_assert(instance); + string_clear(instance->tmp_string); + for + M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { + string_clear(item->item_str); + flipper_format_free(item->flipper_string); + item->type = 0; + } + SubGhzHistoryItemArray_clear(instance->history->data); + free(instance->history); free(instance); } -void subghz_history_set_frequency_preset( - SubGhzHistory* instance, - uint16_t idx, - uint32_t frequency, - FuriHalSubGhzPreset preset) { - furi_assert(instance); - if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return; - instance->history[idx].preset = preset; - instance->history[idx].real_frequency = frequency; -} - uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); - return instance->history[idx].real_frequency; + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + return item->frequency; } FuriHalSubGhzPreset subghz_history_get_preset(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); - return instance->history[idx].preset; + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + return item->preset; } void subghz_history_reset(SubGhzHistory* instance) { furi_assert(instance); + string_reset(instance->tmp_string); + for + M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { + string_clear(item->item_str); + flipper_format_free(item->flipper_string); + item->type = 0; + } + SubGhzHistoryItemArray_reset(instance->history->data); instance->last_index_write = 0; - instance->code_last_found = 0; + instance->code_last_hash_data = 0; } uint16_t subghz_history_get_item(SubGhzHistory* instance) { @@ -74,20 +87,29 @@ uint16_t subghz_history_get_item(SubGhzHistory* instance) { uint8_t subghz_history_get_type_protocol(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); - return instance->history[idx].type_protocol; + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + return item->type; } -const char* subghz_history_get_name(SubGhzHistory* instance, uint16_t idx) { +const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); - return instance->history[idx].name; + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + flipper_format_rewind(item->flipper_string); + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + string_reset(instance->tmp_string); + } + return string_get_cstr(instance->tmp_string); } -SubGhzProtocolCommonLoad* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx) { +FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); - instance->data.code_found = instance->history[idx].code_found; - instance->data.code_count_bit = instance->history[idx].code_count_bit; - instance->data.param1 = instance->history[idx].data1; - return &instance->data; + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + if(item->flipper_string) { + return item->flipper_string; + } else { + return NULL; + } } bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output) { furi_assert(instance); @@ -99,34 +121,10 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output string_printf(output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX); return false; } -void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output, uint16_t idx) { - if(instance->history[idx].code_count_bit < 33) { - string_printf( - output, - "%s %lX", - instance->history[idx].name, - (uint32_t)(instance->history[idx].code_found & 0xFFFFFFFF)); - } else { - string_t str_buff; - string_init(str_buff); - if(strcmp(instance->history[idx].name, "KeeLoq") == 0) { - string_set(str_buff, "KL "); - string_cat(str_buff, instance->history[idx].manufacture_name); - } else if(strcmp(instance->history[idx].name, "Star Line") == 0) { - string_set(str_buff, "SL "); - string_cat(str_buff, instance->history[idx].manufacture_name); - } else { - string_set(str_buff, instance->history[idx].name); - } - string_printf( - output, - "%s %lX%08lX", - string_get_cstr(str_buff), - (uint32_t)(instance->history[idx].code_found >> 32), - (uint32_t)(instance->history[idx].code_found & 0xFFFFFFFF)); - string_clear(str_buff); - } +void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output, uint16_t idx) { + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + string_set(output, item->item_str); } bool subghz_history_add_to_history( @@ -136,41 +134,85 @@ bool subghz_history_add_to_history( FuriHalSubGhzPreset preset) { furi_assert(instance); furi_assert(context); - SubGhzProtocolCommon* protocol = context; if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; - if((instance->code_last_found == (protocol->code_last_found & 0xFFFF0FFFFFFFFFFF)) && + + SubGhzProtocolDecoderBase* decoder_base = context; + if((instance->code_last_hash_data == + subghz_protocol_decoder_base_get_hash_data(decoder_base)) && ((millis() - instance->last_update_timestamp) < 500)) { instance->last_update_timestamp = millis(); return false; } - instance->code_last_found = protocol->code_last_found & 0xFFFF0FFFFFFFFFFF; + instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); instance->last_update_timestamp = millis(); - instance->history[instance->last_index_write].real_frequency = frequency; - instance->history[instance->last_index_write].preset = preset; - instance->history[instance->last_index_write].data1 = 0; - instance->history[instance->last_index_write].manufacture_name = NULL; - instance->history[instance->last_index_write].name = protocol->name; - instance->history[instance->last_index_write].code_count_bit = protocol->code_last_count_bit; - instance->history[instance->last_index_write].code_found = protocol->code_last_found; - if(strcmp(protocol->name, "KeeLoq") == 0) { - instance->history[instance->last_index_write].manufacture_name = - subghz_protocol_keeloq_find_and_get_manufacture_name(protocol); - } else if(strcmp(protocol->name, "Star Line") == 0) { - instance->history[instance->last_index_write].manufacture_name = - subghz_protocol_star_line_find_and_get_manufacture_name(protocol); - } else if(strcmp(protocol->name, "Princeton") == 0) { - instance->history[instance->last_index_write].data1 = - subghz_protocol_princeton_get_te(protocol); - } else if(strcmp(protocol->name, "Somfy Keytis") == 0) { - instance->history[instance->last_index_write].data1 = - subghz_protocol_somfy_keytis_get_press_duration(protocol); - } + string_t text; + string_init(text); + SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data); + item->type = decoder_base->protocol->type; + item->frequency = frequency; + item->preset = preset; - instance->history[instance->last_index_write].type_protocol = protocol->type_protocol; + string_init(item->item_str); + item->flipper_string = flipper_format_string_alloc(); + subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, frequency, preset); + do { + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { + string_set(instance->tmp_string, "KL "); + if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + string_cat(instance->tmp_string, text); + } else if(!strcmp(string_get_cstr(instance->tmp_string), "Star Line")) { + string_set(instance->tmp_string, "SL "); + if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + string_cat(instance->tmp_string, text); + } + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(item->flipper_string, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Key"); + break; + } + uint64_t data = 0; + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + data = (data << 8) | key_data[i]; + } + if(!(uint32_t)(data >> 32)) { + string_printf( + item->item_str, + "%s %lX", + string_get_cstr(instance->tmp_string), + (uint32_t)(data & 0xFFFFFFFF)); + } else { + string_printf( + item->item_str, + "%s %lX%08lX", + string_get_cstr(instance->tmp_string), + (uint32_t)(data >> 32), + (uint32_t)(data & 0xFFFFFFFF)); + } + } while(false); + + string_clear(text); instance->last_index_write++; return true; } diff --git a/applications/subghz/subghz_history.h b/applications/subghz/subghz_history.h index 462a9f4e..f90b01d9 100644 --- a/applications/subghz/subghz_history.h +++ b/applications/subghz/subghz_history.h @@ -1,7 +1,10 @@ #pragma once -#include +#include +#include +#include +#include typedef struct SubGhzHistory SubGhzHistory; @@ -23,19 +26,6 @@ void subghz_history_free(SubGhzHistory* instance); */ void subghz_history_reset(SubGhzHistory* instance); -/** Set frequency and preset to history[idx] - * - * @param instance - SubGhzHistory instance - * @param idx - record index - * @param frequency - frequency Hz - * @param preset - FuriHalSubGhzPreset preset - */ -void subghz_history_set_frequency_preset( - SubGhzHistory* instance, - uint16_t idx, - uint32_t frequency, - FuriHalSubGhzPreset preset); - /** Get frequency to history[idx] * * @param instance - SubGhzHistory instance @@ -73,7 +63,7 @@ uint8_t subghz_history_get_type_protocol(SubGhzHistory* instance, uint16_t idx); * @param idx - record index * @return name - const char* name protocol */ -const char* subghz_history_get_name(SubGhzHistory* instance, uint16_t idx); +const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t idx); /** Get string item menu to history[idx] * @@ -111,4 +101,4 @@ bool subghz_history_add_to_history( * @param idx - record index * @return SubGhzProtocolCommonLoad* */ -SubGhzProtocolCommonLoad* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx); +FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx); diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index 5eb99f85..83ff2c8f 100755 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -8,9 +8,16 @@ #include #include #include "../notification/notification.h" -#include "views/subghz_receiver.h" +#include "views/receiver.h" -bool subghz_set_pteset(SubGhz* subghz, const char* preset) { +#include +#include +#include +#include + +#define TAG "SubGhz" + +bool subghz_set_preset(SubGhz* subghz, const char* preset) { if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { subghz->txrx->preset = FuriHalSubGhzPresetOok270Async; } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { @@ -20,36 +27,12 @@ bool subghz_set_pteset(SubGhz* subghz, const char* preset) { } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { subghz->txrx->preset = FuriHalSubGhzPreset2FSKDev476Async; } else { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unknown preset"); + FURI_LOG_E(TAG, "Unknown preset"); return false; } return true; } -bool subghz_get_preset_name(SubGhz* subghz, string_t preset) { - const char* preset_name; - switch(subghz->txrx->preset) { - case FuriHalSubGhzPresetOok270Async: - preset_name = "FuriHalSubGhzPresetOok270Async"; - break; - case FuriHalSubGhzPresetOok650Async: - preset_name = "FuriHalSubGhzPresetOok650Async"; - break; - case FuriHalSubGhzPreset2FSKDev238Async: - preset_name = "FuriHalSubGhzPreset2FSKDev238Async"; - break; - case FuriHalSubGhzPreset2FSKDev476Async: - preset_name = "FuriHalSubGhzPreset2FSKDev476Async"; - break; - default: - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unknown preset"); - return false; - break; - } - string_set(preset, preset_name); - return true; -} - void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation) { furi_assert(subghz); if(frequency != NULL) { @@ -143,38 +126,57 @@ void subghz_sleep(SubGhz* subghz) { subghz->txrx->txrx_state = SubGhzTxRxStateSleep; } -bool subghz_tx_start(SubGhz* subghz) { +bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { furi_assert(subghz); bool ret = false; - subghz->txrx->encoder = subghz_protocol_encoder_common_alloc(); - subghz->txrx->encoder->repeat = 200; //max repeat with the button held down - //get upload - if(subghz->txrx->protocol_result->get_upload_protocol) { - if(subghz->txrx->protocol_result->get_upload_protocol( - subghz->txrx->protocol_result, subghz->txrx->encoder)) { - if(subghz->txrx->preset) { - subghz_begin(subghz, subghz->txrx->preset); - } else { - subghz_begin(subghz, FuriHalSubGhzPresetOok270Async); - } - if(subghz->txrx->frequency) { - ret = subghz_tx(subghz, subghz->txrx->frequency); - } else { - ret = subghz_tx(subghz, 433920000); - } + string_t temp_str; + string_init(temp_str); + uint32_t repeat = 200; + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + //ToDo FIX + if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { + FURI_LOG_E(TAG, "Unable Repeat"); + break; + } - if(ret) { - //Start TX - furi_hal_subghz_start_async_tx( - subghz_protocol_encoder_common_yield, subghz->txrx->encoder); + subghz->txrx->transmitter = + subghz_transmitter_alloc_init(subghz->txrx->environment, string_get_cstr(temp_str)); + + if(subghz->txrx->transmitter) { + if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format)) { + if(subghz->txrx->preset) { + subghz_begin(subghz, subghz->txrx->preset); + } else { + subghz_begin(subghz, FuriHalSubGhzPresetOok270Async); + } + if(subghz->txrx->frequency) { + ret = subghz_tx(subghz, subghz->txrx->frequency); + } else { + ret = subghz_tx(subghz, 433920000); + } + if(ret) { + //Start TX + furi_hal_subghz_start_async_tx( + subghz_transmitter_yield, subghz->txrx->transmitter); + } } } - } - if(!ret) { - subghz_protocol_encoder_common_free(subghz->txrx->encoder); - subghz_idle(subghz); - } + if(!ret) { + subghz_transmitter_free(subghz->txrx->transmitter); + subghz_idle(subghz); + } + + } while(false); + string_clear(temp_str); return ret; } @@ -183,13 +185,15 @@ void subghz_tx_stop(SubGhz* subghz) { furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateTx); //Stop TX furi_hal_subghz_stop_async_tx(); - subghz_protocol_encoder_common_free(subghz->txrx->encoder); - subghz_idle(subghz); + subghz_transmitter_stop(subghz->txrx->transmitter); + subghz_transmitter_free(subghz->txrx->transmitter); + //if protocol dynamic then we save the last upload - if((subghz->txrx->protocol_result->type_protocol == SubGhzProtocolCommonTypeDynamic) && + if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && (strcmp(subghz->file_name, ""))) { - subghz_save_protocol_to_file(subghz, subghz->file_name); + subghz_save_protocol_to_file(subghz, subghz->txrx->fff_data, subghz->file_name); } + subghz_idle(subghz); notification_message(subghz->notifications, &sequence_reset_red); } @@ -198,24 +202,23 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { furi_assert(file_path); Storage* storage = furi_record_open("storage"); - FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); - // Load device data bool loaded = false; - string_t path; - string_init_set_str(path, file_path); string_t temp_str; string_init(temp_str); uint32_t version; do { - if(!flipper_format_file_open_existing(flipper_format, string_get_cstr(path))) { - FURI_LOG_E( - SUBGHZ_PARSER_TAG, "Unable to open file for read: %s", string_get_cstr(path)); + stream_clean(fff_data_stream); + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Error open file %s", file_path); break; } - if(!flipper_format_read_header(flipper_format, temp_str, &version)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing or incorrect header"); + + if(!flipper_format_read_header(fff_data_file, temp_str, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); break; } @@ -223,50 +226,59 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { (!strcmp(string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && version == SUBGHZ_KEY_FILE_VERSION) { } else { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Type or version mismatch"); + FURI_LOG_E(TAG, "Type or version mismatch"); break; } if(!flipper_format_read_uint32( - flipper_format, "Frequency", (uint32_t*)&subghz->txrx->frequency, 1)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Frequency"); + fff_data_file, "Frequency", (uint32_t*)&subghz->txrx->frequency, 1)) { + FURI_LOG_E(TAG, "Missing Frequency"); break; } - if(!flipper_format_read_string(flipper_format, "Preset", temp_str)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Preset"); + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { + FURI_LOG_E(TAG, "Missing Preset"); break; } - if(!subghz_set_pteset(subghz, string_get_cstr(temp_str))) { + if(!subghz_set_preset(subghz, string_get_cstr(temp_str))) { break; } - if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Protocol"); + if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); break; } + if(!strcmp(string_get_cstr(temp_str), "RAW")) { + //if RAW + string_t file_name; + string_init(file_name); + path_extract_filename_no_ext(file_path, file_name); + subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, string_get_cstr(file_name)); + string_clear(file_name); + + } else { + stream_copy_full( + flipper_format_get_raw_stream(fff_data_file), + flipper_format_get_raw_stream(subghz->txrx->fff_data)); + } - subghz->txrx->protocol_result = - subghz_parser_get_by_name(subghz->txrx->parser, string_get_cstr(temp_str)); - if(subghz->txrx->protocol_result == NULL) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "This type of protocol was not found"); - break; - } - if(!subghz->txrx->protocol_result->to_load_protocol_from_file( - flipper_format, subghz->txrx->protocol_result, string_get_cstr(path))) { - break; + subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( + subghz->txrx->receiver, string_get_cstr(temp_str)); + if(subghz->txrx->decoder_result) { + subghz_protocol_decoder_base_deserialize( + subghz->txrx->decoder_result, subghz->txrx->fff_data); } + loaded = true; } while(0); if(!loaded) { dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); } + string_clear(temp_str); - string_clear(path); - - flipper_format_free(flipper_format); - + //string_clear(path); + flipper_format_free(fff_data_file); furi_record_close("storage"); return loaded; @@ -295,24 +307,26 @@ bool subghz_get_next_name_file(SubGhz* subghz) { return res; } -bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) { +bool subghz_save_protocol_to_file( + SubGhz* subghz, + FlipperFormat* flipper_format, + const char* dev_name) { furi_assert(subghz); - furi_assert(subghz->txrx->protocol_result); + furi_assert(flipper_format); + furi_assert(dev_name); Storage* storage = furi_record_open("storage"); - FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); + string_t dev_file_name; string_init(dev_file_name); - string_t temp_str; - string_init(temp_str); bool saved = false; do { - // Checking that this type of people can be saved - if(subghz->txrx->protocol_result->to_save_file == NULL) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "No saving of this type of keys"); - break; - } + //removing additional fields + flipper_format_delete_key(flipper_format, "Repeat"); + flipper_format_delete_key(flipper_format, "Manufacture"); + // Create subghz folder directory if necessary if(!storage_simply_mkdir(storage, SUBGHZ_APP_FOLDER)) { dialog_message_show_storage_error(subghz->dialogs, "Cannot create\nfolder"); @@ -325,47 +339,16 @@ bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) { if(!storage_simply_remove(storage, string_get_cstr(dev_file_name))) { break; } - - // Open file - if(!flipper_format_file_open_always(flipper_format, string_get_cstr(dev_file_name))) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to open file for write: %s", dev_file_name); - break; - } - - if(!flipper_format_write_header_cstr( - flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add header"); - break; - } - - if(!flipper_format_write_uint32(flipper_format, "Frequency", &subghz->txrx->frequency, 1)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Frequency"); - break; - } - - if(!subghz_get_preset_name(subghz, temp_str)) { - break; - } - if(!flipper_format_write_string_cstr(flipper_format, "Preset", string_get_cstr(temp_str))) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Preset"); - break; - } - - if(!subghz->txrx->protocol_result->to_save_file( - subghz->txrx->protocol_result, flipper_format)) { - break; - } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file( + flipper_format_stream, storage, string_get_cstr(dev_file_name), FSOM_CREATE_ALWAYS); saved = true; } while(0); - string_clear(temp_str); string_clear(dev_file_name); - - flipper_format_free(flipper_format); - furi_record_close("storage"); - return saved; } @@ -501,7 +484,7 @@ void subghz_hopper_update(SubGhz* subghz) { subghz_rx_end(subghz); }; if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { - subghz_parser_reset(subghz->txrx->parser); + subghz_receiver_reset(subghz->txrx->receiver); subghz->txrx->frequency = subghz_hopper_frequencies[subghz->txrx->hopper_idx_frequency]; subghz_rx(subghz, subghz->txrx->frequency); } diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 01174dce..98dced1c 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -1,8 +1,8 @@ #pragma once #include "subghz.h" -#include "views/subghz_receiver.h" -#include "views/subghz_transmitter.h" +#include "views/receiver.h" +#include "views/transmitter.h" #include "views/subghz_frequency_analyzer.h" #include "views/subghz_read_raw.h" @@ -26,8 +26,9 @@ #include -#include -#include +#include +#include + #include "subghz_history.h" #include @@ -79,9 +80,13 @@ typedef enum { struct SubGhzTxRx { SubGhzWorker* worker; - SubGhzParser* parser; - SubGhzProtocolCommon* protocol_result; - SubGhzProtocolCommonEncoder* encoder; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + SubGhzProtocolDecoderBase* decoder_result; + FlipperFormat* fff_data; + uint32_t frequency; FuriHalSubGhzPreset preset; SubGhzHistory* history; @@ -113,46 +118,48 @@ struct SubGhz { char file_name_tmp[SUBGHZ_TEXT_STORE_SIZE + 1]; SubGhzNotificationState state_notifications; - SubghzReceiver* subghz_receiver; - SubghzTransmitter* subghz_transmitter; + SubGhzViewReceiver* subghz_receiver; + SubGhzViewTransmitter* subghz_transmitter; VariableItemList* variable_item_list; - SubghzFrequencyAnalyzer* subghz_frequency_analyzer; - SubghzReadRAW* subghz_read_raw; - SubghzTestStatic* subghz_test_static; - SubghzTestCarrier* subghz_test_carrier; - SubghzTestPacket* subghz_test_packet; + SubGhzFrequencyAnalyzer* subghz_frequency_analyzer; + SubGhzReadRAW* subghz_read_raw; + SubGhzTestStatic* subghz_test_static; + SubGhzTestCarrier* subghz_test_carrier; + SubGhzTestPacket* subghz_test_packet; string_t error_str; }; typedef enum { - SubGhzViewMenu, + SubGhzViewIdMenu, + SubGhzViewIdReceiver, + SubGhzViewIdPopup, + SubGhzViewIdTextInput, + SubGhzViewIdWidget, + SubGhzViewIdTransmitter, + SubGhzViewIdVariableItemList, + SubGhzViewIdFrequencyAnalyzer, + SubGhzViewIdReadRAW, - SubGhzViewReceiver, - SubGhzViewPopup, - SubGhzViewTextInput, - SubGhzViewWidget, - SubGhzViewTransmitter, - SubGhzViewVariableItemList, - SubGhzViewFrequencyAnalyzer, - SubGhzViewReadRAW, - SubGhzViewStatic, - SubGhzViewTestCarrier, - SubGhzViewTestPacket, -} SubGhzView; + SubGhzViewIdStatic, + SubGhzViewIdTestCarrier, + SubGhzViewIdTestPacket, +} SubGhzViewId; -bool subghz_set_pteset(SubGhz* subghz, const char* preset); -bool subghz_get_preset_name(SubGhz* subghz, string_t preset); +bool subghz_set_preset(SubGhz* subghz, const char* preset); void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation); void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset); uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency); void subghz_rx_end(SubGhz* subghz); void subghz_sleep(SubGhz* subghz); -bool subghz_tx_start(SubGhz* subghz); +bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); void subghz_tx_stop(SubGhz* subghz); bool subghz_key_load(SubGhz* subghz, const char* file_path); bool subghz_get_next_name_file(SubGhz* subghz); -bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name); +bool subghz_save_protocol_to_file( + SubGhz* subghz, + FlipperFormat* flipper_format, + const char* dev_name); bool subghz_load_protocol_from_file(SubGhz* subghz); bool subghz_rename_file(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); diff --git a/applications/subghz/views/subghz_receiver.c b/applications/subghz/views/receiver.c similarity index 72% rename from applications/subghz/views/subghz_receiver.c rename to applications/subghz/views/receiver.c index 92bfbb53..20ee37d3 100644 --- a/applications/subghz/views/subghz_receiver.c +++ b/applications/subghz/views/receiver.c @@ -1,4 +1,4 @@ -#include "subghz_receiver.h" +#include "receiver.h" #include "../subghz_i.h" #include @@ -29,14 +29,14 @@ struct SubGhzReceiverHistory { typedef struct SubGhzReceiverHistory SubGhzReceiverHistory; static const Icon* ReceiverItemIcons[] = { - [SubGhzProtocolCommonTypeUnknown] = &I_Quest_7x8, - [SubGhzProtocolCommonTypeStatic] = &I_Unlock_7x8, - [SubGhzProtocolCommonTypeDynamic] = &I_Lock_7x8, + [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, + [SubGhzProtocolTypeStatic] = &I_Unlock_7x8, + [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, }; -struct SubghzReceiver { +struct SubGhzViewReceiver { View* view; - SubghzReceiverCallback callback; + SubGhzViewReceiverCallback callback; void* context; }; @@ -48,11 +48,11 @@ typedef struct { uint16_t idx; uint16_t list_offset; uint16_t history_item; -} SubghzReceiverModel; +} SubGhzViewReceiverModel; -void subghz_receiver_set_callback( - SubghzReceiver* subghz_receiver, - SubghzReceiverCallback callback, +void subghz_view_receiver_set_callback( + SubGhzViewReceiver* subghz_receiver, + SubGhzViewReceiverCallback callback, void* context) { furi_assert(subghz_receiver); furi_assert(callback); @@ -60,11 +60,11 @@ void subghz_receiver_set_callback( subghz_receiver->context = context; } -static void subghz_receiver_update_offset(SubghzReceiver* subghz_receiver) { +static void subghz_view_receiver_update_offset(SubGhzViewReceiver* subghz_receiver) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { size_t history_item = model->history_item; uint16_t bounds = history_item > 3 ? 2 : history_item; @@ -79,13 +79,13 @@ static void subghz_receiver_update_offset(SubghzReceiver* subghz_receiver) { }); } -void subghz_receiver_add_item_to_menu( - SubghzReceiver* subghz_receiver, +void subghz_view_receiver_add_item_to_menu( + SubGhzViewReceiver* subghz_receiver, const char* name, uint8_t type) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { SubGhzReceiverMenuItem* item_menu = SubGhzReceiverMenuItemArray_push_raw(model->history->data); string_init_set_str(item_menu->item_str, name); @@ -99,17 +99,17 @@ void subghz_receiver_add_item_to_menu( return true; }); - subghz_receiver_update_offset(subghz_receiver); + subghz_view_receiver_update_offset(subghz_receiver); } -void subghz_receiver_add_data_statusbar( - SubghzReceiver* subghz_receiver, +void subghz_view_receiver_add_data_statusbar( + SubGhzViewReceiver* subghz_receiver, const char* frequency_str, const char* preset_str, const char* history_stat_str) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { string_set(model->frequency_str, frequency_str); string_set(model->preset_str, preset_str); string_set(model->history_stat_str, history_stat_str); @@ -117,7 +117,7 @@ void subghz_receiver_add_data_statusbar( }); } -static void subghz_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { +static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { canvas_set_color(canvas, ColorBlack); canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); @@ -131,7 +131,7 @@ static void subghz_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scroll canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); } -void subghz_receiver_draw(Canvas* canvas, SubghzReceiverModel* model) { +void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); @@ -162,7 +162,7 @@ void subghz_receiver_draw(Canvas* canvas, SubghzReceiverModel* model) { string_set(str_buff, item_menu->item_str); elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); if(model->idx == idx) { - subghz_receiver_draw_frame(canvas, i, scrollbar); + subghz_view_receiver_draw_frame(canvas, i, scrollbar); } else { canvas_set_color(canvas, ColorBlack); } @@ -176,17 +176,17 @@ void subghz_receiver_draw(Canvas* canvas, SubghzReceiverModel* model) { string_clear(str_buff); } -bool subghz_receiver_input(InputEvent* event, void* context) { +bool subghz_view_receiver_input(InputEvent* event, void* context) { furi_assert(context); - SubghzReceiver* subghz_receiver = context; + SubGhzViewReceiver* subghz_receiver = context; if(event->key == InputKeyBack && event->type == InputTypeShort) { - subghz_receiver->callback(SubghzCustomEventViewReceverBack, subghz_receiver->context); + subghz_receiver->callback(SubGhzCustomEventViewReceverBack, subghz_receiver->context); } else if( event->key == InputKeyUp && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { if(model->idx != 0) model->idx--; return true; }); @@ -194,38 +194,38 @@ bool subghz_receiver_input(InputEvent* event, void* context) { event->key == InputKeyDown && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { if(model->idx != model->history_item - 1) model->idx++; return true; }); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { - subghz_receiver->callback(SubghzCustomEventViewReceverConfig, subghz_receiver->context); + subghz_receiver->callback(SubGhzCustomEventViewReceverConfig, subghz_receiver->context); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { if(model->history_item != 0) { subghz_receiver->callback( - SubghzCustomEventViewReceverOK, subghz_receiver->context); + SubGhzCustomEventViewReceverOK, subghz_receiver->context); } return false; }); } - subghz_receiver_update_offset(subghz_receiver); + subghz_view_receiver_update_offset(subghz_receiver); return true; } -void subghz_receiver_enter(void* context) { +void subghz_view_receiver_enter(void* context) { furi_assert(context); - //SubghzReceiver* subghz_receiver = context; + //SubGhzViewReceiver* subghz_receiver = context; } -void subghz_receiver_exit(void* context) { +void subghz_view_receiver_exit(void* context) { furi_assert(context); - SubghzReceiver* subghz_receiver = context; + SubGhzViewReceiver* subghz_receiver = context; with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { string_reset(model->frequency_str); string_reset(model->preset_str); string_reset(model->history_stat_str); @@ -242,20 +242,21 @@ void subghz_receiver_exit(void* context) { }); } -SubghzReceiver* subghz_receiver_alloc() { - SubghzReceiver* subghz_receiver = malloc(sizeof(SubghzReceiver)); +SubGhzViewReceiver* subghz_view_receiver_alloc() { + SubGhzViewReceiver* subghz_receiver = malloc(sizeof(SubGhzViewReceiver)); // View allocation and configuration subghz_receiver->view = view_alloc(); - view_allocate_model(subghz_receiver->view, ViewModelTypeLocking, sizeof(SubghzReceiverModel)); + view_allocate_model( + subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel)); view_set_context(subghz_receiver->view, subghz_receiver); - view_set_draw_callback(subghz_receiver->view, (ViewDrawCallback)subghz_receiver_draw); - view_set_input_callback(subghz_receiver->view, subghz_receiver_input); - view_set_enter_callback(subghz_receiver->view, subghz_receiver_enter); - view_set_exit_callback(subghz_receiver->view, subghz_receiver_exit); + view_set_draw_callback(subghz_receiver->view, (ViewDrawCallback)subghz_view_receiver_draw); + view_set_input_callback(subghz_receiver->view, subghz_view_receiver_input); + view_set_enter_callback(subghz_receiver->view, subghz_view_receiver_enter); + view_set_exit_callback(subghz_receiver->view, subghz_view_receiver_exit); with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { string_init(model->frequency_str); string_init(model->preset_str); string_init(model->history_stat_str); @@ -267,11 +268,11 @@ SubghzReceiver* subghz_receiver_alloc() { return subghz_receiver; } -void subghz_receiver_free(SubghzReceiver* subghz_receiver) { +void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { string_clear(model->frequency_str); string_clear(model->preset_str); string_clear(model->history_stat_str); @@ -288,29 +289,29 @@ void subghz_receiver_free(SubghzReceiver* subghz_receiver) { free(subghz_receiver); } -View* subghz_receiver_get_view(SubghzReceiver* subghz_receiver) { +View* subghz_view_receiver_get_view(SubGhzViewReceiver* subghz_receiver) { furi_assert(subghz_receiver); return subghz_receiver->view; } -uint16_t subghz_receiver_get_idx_menu(SubghzReceiver* subghz_receiver) { +uint16_t subghz_view_receiver_get_idx_menu(SubGhzViewReceiver* subghz_receiver) { furi_assert(subghz_receiver); uint32_t idx = 0; with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { idx = model->idx; return false; }); return idx; } -void subghz_receiver_set_idx_menu(SubghzReceiver* subghz_receiver, uint16_t idx) { +void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint16_t idx) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubghzReceiverModel * model) { + subghz_receiver->view, (SubGhzViewReceiverModel * model) { model->idx = idx; if(model->idx > 2) model->list_offset = idx - 2; return true; }); - subghz_receiver_update_offset(subghz_receiver); + subghz_view_receiver_update_offset(subghz_receiver); } diff --git a/applications/subghz/views/receiver.h b/applications/subghz/views/receiver.h new file mode 100644 index 00000000..87065ef0 --- /dev/null +++ b/applications/subghz/views/receiver.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include "../helpers/subghz_custom_event.h" + +typedef struct SubGhzViewReceiver SubGhzViewReceiver; + +typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context); + +void subghz_view_receiver_set_callback( + SubGhzViewReceiver* subghz_receiver, + SubGhzViewReceiverCallback callback, + void* context); + +SubGhzViewReceiver* subghz_view_receiver_alloc(); + +void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver); + +View* subghz_view_receiver_get_view(SubGhzViewReceiver* subghz_receiver); + +void subghz_view_receiver_add_data_statusbar( + SubGhzViewReceiver* subghz_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str); + +void subghz_view_receiver_add_item_to_menu( + SubGhzViewReceiver* subghz_receiver, + const char* name, + uint8_t type); + +uint16_t subghz_view_receiver_get_idx_menu(SubGhzViewReceiver* subghz_receiver); + +void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint16_t idx); + +void subghz_view_receiver_exit(void* context); diff --git a/applications/subghz/views/subghz_frequency_analyzer.c b/applications/subghz/views/subghz_frequency_analyzer.c index afb7b012..4e645edb 100644 --- a/applications/subghz/views/subghz_frequency_analyzer.c +++ b/applications/subghz/views/subghz_frequency_analyzer.c @@ -6,30 +6,29 @@ #include #include #include -#include #include "../helpers/subghz_frequency_analyzer_worker.h" #include typedef enum { - SubghzFrequencyAnalyzerStatusIDLE, -} SubghzFrequencyAnalyzerStatus; + SubGhzFrequencyAnalyzerStatusIDLE, +} SubGhzFrequencyAnalyzerStatus; -struct SubghzFrequencyAnalyzer { +struct SubGhzFrequencyAnalyzer { View* view; SubGhzFrequencyAnalyzerWorker* worker; - SubghzFrequencyAnalyzerCallback callback; + SubGhzFrequencyAnalyzerCallback callback; void* context; }; typedef struct { uint32_t frequency; float rssi; -} SubghzFrequencyAnalyzerModel; +} SubGhzFrequencyAnalyzerModel; void subghz_frequency_analyzer_set_callback( - SubghzFrequencyAnalyzer* subghz_frequency_analyzer, - SubghzFrequencyAnalyzerCallback callback, + SubGhzFrequencyAnalyzer* subghz_frequency_analyzer, + SubGhzFrequencyAnalyzerCallback callback, void* context) { furi_assert(subghz_frequency_analyzer); furi_assert(callback); @@ -53,7 +52,7 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { } } -void subghz_frequency_analyzer_draw(Canvas* canvas, SubghzFrequencyAnalyzerModel* model) { +void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { char buffer[64]; canvas_set_color(canvas, ColorBlack); @@ -77,7 +76,6 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubghzFrequencyAnalyzerModel bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { furi_assert(context); - //SubghzFrequencyAnalyzer* instance = context; if(event->key == InputKeyBack) { return false; @@ -87,9 +85,9 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { } void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, float rssi) { - SubghzFrequencyAnalyzer* instance = context; + SubGhzFrequencyAnalyzer* instance = context; with_view_model( - instance->view, (SubghzFrequencyAnalyzerModel * model) { + instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi = rssi; model->frequency = frequency; return true; @@ -98,7 +96,7 @@ void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, void subghz_frequency_analyzer_enter(void* context) { furi_assert(context); - SubghzFrequencyAnalyzer* instance = context; + SubGhzFrequencyAnalyzer* instance = context; //Start worker instance->worker = subghz_frequency_analyzer_worker_alloc(); @@ -111,7 +109,7 @@ void subghz_frequency_analyzer_enter(void* context) { subghz_frequency_analyzer_worker_start(instance->worker); with_view_model( - instance->view, (SubghzFrequencyAnalyzerModel * model) { + instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi = 0; model->frequency = 0; return true; @@ -120,7 +118,7 @@ void subghz_frequency_analyzer_enter(void* context) { void subghz_frequency_analyzer_exit(void* context) { furi_assert(context); - SubghzFrequencyAnalyzer* instance = context; + SubGhzFrequencyAnalyzer* instance = context; //Stop worker if(subghz_frequency_analyzer_worker_is_running(instance->worker)) { @@ -129,19 +127,19 @@ void subghz_frequency_analyzer_exit(void* context) { subghz_frequency_analyzer_worker_free(instance->worker); with_view_model( - instance->view, (SubghzFrequencyAnalyzerModel * model) { + instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi = 0; return true; }); } -SubghzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { - SubghzFrequencyAnalyzer* instance = malloc(sizeof(SubghzFrequencyAnalyzer)); +SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { + SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer)); // View allocation and configuration instance->view = view_alloc(); view_allocate_model( - instance->view, ViewModelTypeLocking, sizeof(SubghzFrequencyAnalyzerModel)); + instance->view, ViewModelTypeLocking, sizeof(SubGhzFrequencyAnalyzerModel)); view_set_context(instance->view, instance); view_set_draw_callback(instance->view, (ViewDrawCallback)subghz_frequency_analyzer_draw); view_set_input_callback(instance->view, subghz_frequency_analyzer_input); @@ -149,7 +147,7 @@ SubghzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { view_set_exit_callback(instance->view, subghz_frequency_analyzer_exit); with_view_model( - instance->view, (SubghzFrequencyAnalyzerModel * model) { + instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi = 0; return true; }); @@ -157,14 +155,14 @@ SubghzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { return instance; } -void subghz_frequency_analyzer_free(SubghzFrequencyAnalyzer* instance) { +void subghz_frequency_analyzer_free(SubGhzFrequencyAnalyzer* instance) { furi_assert(instance); view_free(instance->view); free(instance); } -View* subghz_frequency_analyzer_get_view(SubghzFrequencyAnalyzer* instance) { +View* subghz_frequency_analyzer_get_view(SubGhzFrequencyAnalyzer* instance) { furi_assert(instance); return instance->view; } diff --git a/applications/subghz/views/subghz_frequency_analyzer.h b/applications/subghz/views/subghz_frequency_analyzer.h index 78280503..3de003bf 100644 --- a/applications/subghz/views/subghz_frequency_analyzer.h +++ b/applications/subghz/views/subghz_frequency_analyzer.h @@ -3,17 +3,17 @@ #include #include "../helpers/subghz_custom_event.h" -typedef struct SubghzFrequencyAnalyzer SubghzFrequencyAnalyzer; +typedef struct SubGhzFrequencyAnalyzer SubGhzFrequencyAnalyzer; -typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzCustomEvent event, void* context); +typedef void (*SubGhzFrequencyAnalyzerCallback)(SubGhzCustomEvent event, void* context); void subghz_frequency_analyzer_set_callback( - SubghzFrequencyAnalyzer* subghz_frequency_analyzer, - SubghzFrequencyAnalyzerCallback callback, + SubGhzFrequencyAnalyzer* subghz_frequency_analyzer, + SubGhzFrequencyAnalyzerCallback callback, void* context); -SubghzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(); +SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(); -void subghz_frequency_analyzer_free(SubghzFrequencyAnalyzer* subghz_static); +void subghz_frequency_analyzer_free(SubGhzFrequencyAnalyzer* subghz_static); -View* subghz_frequency_analyzer_get_view(SubghzFrequencyAnalyzer* subghz_static); +View* subghz_frequency_analyzer_get_view(SubGhzFrequencyAnalyzer* subghz_static); diff --git a/applications/subghz/views/subghz_read_raw.c b/applications/subghz/views/subghz_read_raw.c index f786b7a0..c5b1f09b 100644 --- a/applications/subghz/views/subghz_read_raw.c +++ b/applications/subghz/views/subghz_read_raw.c @@ -6,15 +6,14 @@ #include #include #include -#include #include #define SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE 100 -#define TAG "SubghzReadRAW" +#define TAG "SubGhzReadRAW" -struct SubghzReadRAW { +struct SubGhzReadRAW { View* view; - SubghzReadRAWCallback callback; + SubGhzReadRAWCallback callback; void* context; }; @@ -27,12 +26,12 @@ typedef struct { bool rssi_history_end; uint8_t ind_write; uint8_t ind_sin; - SubghzReadRAWStatus satus; -} SubghzReadRAWModel; + SubGhzReadRAWStatus satus; +} SubGhzReadRAWModel; void subghz_read_raw_set_callback( - SubghzReadRAW* subghz_read_raw, - SubghzReadRAWCallback callback, + SubGhzReadRAW* subghz_read_raw, + SubGhzReadRAWCallback callback, void* context) { furi_assert(subghz_read_raw); furi_assert(callback); @@ -41,19 +40,19 @@ void subghz_read_raw_set_callback( } void subghz_read_raw_add_data_statusbar( - SubghzReadRAW* instance, + SubGhzReadRAW* instance, const char* frequency_str, const char* preset_str) { furi_assert(instance); with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { string_set(model->frequency_str, frequency_str); string_set(model->preset_str, preset_str); return true; }); } -void subghz_read_raw_add_data_rssi(SubghzReadRAW* instance, float rssi) { +void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi) { furi_assert(instance); uint8_t u_rssi = 0; @@ -62,10 +61,9 @@ void subghz_read_raw_add_data_rssi(SubghzReadRAW* instance, float rssi) { } else { u_rssi = (uint8_t)((rssi + 90) / 2.7); } - //if(u_rssi > 34) u_rssi = 34; with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { model->rssi_history[model->ind_write++] = u_rssi; if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { model->rssi_history_end = true; @@ -75,46 +73,46 @@ void subghz_read_raw_add_data_rssi(SubghzReadRAW* instance, float rssi) { }); } -void subghz_read_raw_update_sample_write(SubghzReadRAW* instance, size_t sample) { +void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample) { furi_assert(instance); with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { string_printf(model->sample_write, "%d spl.", sample); return false; }); } -void subghz_read_raw_stop_send(SubghzReadRAW* instance) { +void subghz_read_raw_stop_send(SubGhzReadRAW* instance) { furi_assert(instance); with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { switch(model->satus) { - case SubghzReadRAWStatusTXRepeat: - case SubghzReadRAWStatusLoadKeyTXRepeat: - instance->callback(SubghzCustomEventViewReadRAWSendStart, instance->context); + case SubGhzReadRAWStatusTXRepeat: + case SubGhzReadRAWStatusLoadKeyTXRepeat: + instance->callback(SubGhzCustomEventViewReadRAWSendStart, instance->context); break; - case SubghzReadRAWStatusTX: - model->satus = SubghzReadRAWStatusIDLE; + case SubGhzReadRAWStatusTX: + model->satus = SubGhzReadRAWStatusIDLE; break; - case SubghzReadRAWStatusLoadKeyTX: - model->satus = SubghzReadRAWStatusLoadKeyIDLE; + case SubGhzReadRAWStatusLoadKeyTX: + model->satus = SubGhzReadRAWStatusLoadKeyIDLE; break; default: FURI_LOG_W(TAG, "unknown status"); - model->satus = SubghzReadRAWStatusIDLE; + model->satus = SubGhzReadRAWStatusIDLE; break; } return true; }); } -void subghz_read_raw_update_sin(SubghzReadRAW* instance) { +void subghz_read_raw_update_sin(SubGhzReadRAW* instance) { furi_assert(instance); with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { if(model->ind_sin++ > 62) { model->ind_sin = 0; } @@ -134,7 +132,7 @@ static int8_t subghz_read_raw_tab_sin(uint8_t x) { return r; } -void subghz_read_raw_draw_sin(Canvas* canvas, SubghzReadRAWModel* model) { +void subghz_read_raw_draw_sin(Canvas* canvas, SubGhzReadRAWModel* model) { #define SUBGHZ_RAW_SIN_AMPLITUDE 11 for(int i = 113; i > 0; i--) { canvas_draw_line( @@ -154,7 +152,7 @@ void subghz_read_raw_draw_sin(Canvas* canvas, SubghzReadRAWModel* model) { } } -void subghz_read_raw_draw_scale(Canvas* canvas, SubghzReadRAWModel* model) { +void subghz_read_raw_draw_scale(Canvas* canvas, SubGhzReadRAWModel* model) { #define SUBGHZ_RAW_TOP_SCALE 14 #define SUBGHZ_RAW_END_SCALE 115 @@ -178,7 +176,7 @@ void subghz_read_raw_draw_scale(Canvas* canvas, SubghzReadRAWModel* model) { } } -void subghz_read_raw_draw_rssi(Canvas* canvas, SubghzReadRAWModel* model) { +void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) { int ind = 0; int base = 0; if(model->rssi_history_end == false) { @@ -214,7 +212,7 @@ void subghz_read_raw_draw_rssi(Canvas* canvas, SubghzReadRAWModel* model) { } } -void subghz_read_raw_draw(Canvas* canvas, SubghzReadRAWModel* model) { +void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { uint8_t graphics_mode = 1; canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); @@ -228,12 +226,12 @@ void subghz_read_raw_draw(Canvas* canvas, SubghzReadRAWModel* model) { canvas_draw_line(canvas, 115, 14, 115, 48); switch(model->satus) { - case SubghzReadRAWStatusIDLE: + case SubGhzReadRAWStatusIDLE: elements_button_left(canvas, "Erase"); elements_button_center(canvas, "Send"); elements_button_right(canvas, "Save"); break; - case SubghzReadRAWStatusLoadKeyIDLE: + case SubGhzReadRAWStatusLoadKeyIDLE: elements_button_left(canvas, "New"); elements_button_center(canvas, "Send"); elements_button_right(canvas, "More"); @@ -241,15 +239,15 @@ void subghz_read_raw_draw(Canvas* canvas, SubghzReadRAWModel* model) { canvas, 4, 12, 110, 44, AlignCenter, AlignCenter, string_get_cstr(model->file_name)); break; - case SubghzReadRAWStatusTX: - case SubghzReadRAWStatusTXRepeat: - case SubghzReadRAWStatusLoadKeyTX: - case SubghzReadRAWStatusLoadKeyTXRepeat: + case SubGhzReadRAWStatusTX: + case SubGhzReadRAWStatusTXRepeat: + case SubGhzReadRAWStatusLoadKeyTX: + case SubGhzReadRAWStatusLoadKeyTXRepeat: graphics_mode = 0; elements_button_center(canvas, "Send"); break; - case SubghzReadRAWStatusStart: + case SubGhzReadRAWStatusStart: elements_button_left(canvas, "Config"); elements_button_center(canvas, "REC"); break; @@ -272,7 +270,7 @@ void subghz_read_raw_draw(Canvas* canvas, SubghzReadRAWModel* model) { bool subghz_read_raw_input(InputEvent* event, void* context) { furi_assert(context); - SubghzReadRAW* instance = context; + SubGhzReadRAW* instance = context; if((event->key == InputKeyOk) && (event->type == InputTypeLong || event->type == InputTypeRepeat)) { @@ -281,30 +279,30 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { return false; } else if(event->key == InputKeyOk && event->type == InputTypePress) { with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { uint8_t ret = false; switch(model->satus) { - case SubghzReadRAWStatusIDLE: + case SubGhzReadRAWStatusIDLE: // Start TX - instance->callback(SubghzCustomEventViewReadRAWSendStart, instance->context); - instance->callback(SubghzCustomEventViewReadRAWVibro, instance->context); - model->satus = SubghzReadRAWStatusTXRepeat; + instance->callback(SubGhzCustomEventViewReadRAWSendStart, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWVibro, instance->context); + model->satus = SubGhzReadRAWStatusTXRepeat; ret = true; break; - case SubghzReadRAWStatusTX: + case SubGhzReadRAWStatusTX: // Start TXRepeat - model->satus = SubghzReadRAWStatusTXRepeat; + model->satus = SubGhzReadRAWStatusTXRepeat; break; - case SubghzReadRAWStatusLoadKeyIDLE: + case SubGhzReadRAWStatusLoadKeyIDLE: // Start Load Key TX - instance->callback(SubghzCustomEventViewReadRAWSendStart, instance->context); - instance->callback(SubghzCustomEventViewReadRAWVibro, instance->context); - model->satus = SubghzReadRAWStatusLoadKeyTXRepeat; + instance->callback(SubGhzCustomEventViewReadRAWSendStart, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWVibro, instance->context); + model->satus = SubGhzReadRAWStatusLoadKeyTXRepeat; ret = true; break; - case SubghzReadRAWStatusLoadKeyTX: + case SubGhzReadRAWStatusLoadKeyTX: // Start Load Key TXRepeat - model->satus = SubghzReadRAWStatusLoadKeyTXRepeat; + model->satus = SubGhzReadRAWStatusLoadKeyTXRepeat; break; default: @@ -314,91 +312,91 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { }); } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { with_view_model( - instance->view, (SubghzReadRAWModel * model) { - if(model->satus == SubghzReadRAWStatusTXRepeat) { + instance->view, (SubGhzReadRAWModel * model) { + if(model->satus == SubGhzReadRAWStatusTXRepeat) { // Stop repeat TX - model->satus = SubghzReadRAWStatusTX; - } else if(model->satus == SubghzReadRAWStatusLoadKeyTXRepeat) { + model->satus = SubGhzReadRAWStatusTX; + } else if(model->satus == SubGhzReadRAWStatusLoadKeyTXRepeat) { // Stop repeat TX - model->satus = SubghzReadRAWStatusLoadKeyTX; + model->satus = SubGhzReadRAWStatusLoadKeyTX; } return false; }); } else if(event->key == InputKeyBack && event->type == InputTypeShort) { with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { switch(model->satus) { - case SubghzReadRAWStatusREC: + case SubGhzReadRAWStatusREC: //Stop REC - instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context); - model->satus = SubghzReadRAWStatusIDLE; + instance->callback(SubGhzCustomEventViewReadRAWIDLE, instance->context); + model->satus = SubGhzReadRAWStatusIDLE; break; - case SubghzReadRAWStatusLoadKeyTX: + case SubGhzReadRAWStatusLoadKeyTX: //Stop TxRx - instance->callback(SubghzCustomEventViewReadRAWTXRXStop, instance->context); - model->satus = SubghzReadRAWStatusLoadKeyIDLE; + instance->callback(SubGhzCustomEventViewReadRAWTXRXStop, instance->context); + model->satus = SubGhzReadRAWStatusLoadKeyIDLE; break; - case SubghzReadRAWStatusTX: + case SubGhzReadRAWStatusTX: //Stop TxRx - instance->callback(SubghzCustomEventViewReadRAWTXRXStop, instance->context); - model->satus = SubghzReadRAWStatusIDLE; + instance->callback(SubGhzCustomEventViewReadRAWTXRXStop, instance->context); + model->satus = SubGhzReadRAWStatusIDLE; break; - case SubghzReadRAWStatusLoadKeyIDLE: + case SubGhzReadRAWStatusLoadKeyIDLE: //Exit - instance->callback(SubghzCustomEventViewReadRAWBack, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWBack, instance->context); break; default: //Exit - instance->callback(SubghzCustomEventViewReadRAWBack, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWBack, instance->context); break; } return true; }); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { with_view_model( - instance->view, (SubghzReadRAWModel * model) { - if(model->satus == SubghzReadRAWStatusStart) { + instance->view, (SubGhzReadRAWModel * model) { + if(model->satus == SubGhzReadRAWStatusStart) { //Config - instance->callback(SubghzCustomEventViewReadRAWConfig, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWConfig, instance->context); } else if( - (model->satus == SubghzReadRAWStatusIDLE) || - (model->satus == SubghzReadRAWStatusLoadKeyIDLE)) { + (model->satus == SubGhzReadRAWStatusIDLE) || + (model->satus == SubGhzReadRAWStatusLoadKeyIDLE)) { //Erase - model->satus = SubghzReadRAWStatusStart; + model->satus = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; string_set(model->sample_write, "0 spl."); string_reset(model->file_name); - instance->callback(SubghzCustomEventViewReadRAWErase, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); } return true; }); } else if(event->key == InputKeyRight && event->type == InputTypeShort) { with_view_model( - instance->view, (SubghzReadRAWModel * model) { - if(model->satus == SubghzReadRAWStatusIDLE) { + instance->view, (SubGhzReadRAWModel * model) { + if(model->satus == SubGhzReadRAWStatusIDLE) { //Save - instance->callback(SubghzCustomEventViewReadRAWSave, instance->context); - } else if(model->satus == SubghzReadRAWStatusLoadKeyIDLE) { + instance->callback(SubGhzCustomEventViewReadRAWSave, instance->context); + } else if(model->satus == SubGhzReadRAWStatusLoadKeyIDLE) { //More - instance->callback(SubghzCustomEventViewReadRAWMore, instance->context); + instance->callback(SubGhzCustomEventViewReadRAWMore, instance->context); } return true; }); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( - instance->view, (SubghzReadRAWModel * model) { - if(model->satus == SubghzReadRAWStatusStart) { + instance->view, (SubGhzReadRAWModel * model) { + if(model->satus == SubGhzReadRAWStatusStart) { //Record - instance->callback(SubghzCustomEventViewReadRAWREC, instance->context); - model->satus = SubghzReadRAWStatusREC; + instance->callback(SubGhzCustomEventViewReadRAWREC, instance->context); + model->satus = SubGhzReadRAWStatusREC; model->ind_write = 0; model->rssi_history_end = false; - } else if(model->satus == SubghzReadRAWStatusREC) { + } else if(model->satus == SubGhzReadRAWStatusREC) { //Stop - instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context); - model->satus = SubghzReadRAWStatusIDLE; + instance->callback(SubGhzCustomEventViewReadRAWIDLE, instance->context); + model->satus = SubGhzReadRAWStatusIDLE; } return true; }); @@ -407,16 +405,16 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { } void subghz_read_raw_set_status( - SubghzReadRAW* instance, - SubghzReadRAWStatus satus, + SubGhzReadRAW* instance, + SubGhzReadRAWStatus satus, const char* file_name) { furi_assert(instance); switch(satus) { - case SubghzReadRAWStatusStart: + case SubGhzReadRAWStatusStart: with_view_model( - instance->view, (SubghzReadRAWModel * model) { - model->satus = SubghzReadRAWStatusStart; + instance->view, (SubGhzReadRAWModel * model) { + model->satus = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; string_reset(model->file_name); @@ -424,17 +422,17 @@ void subghz_read_raw_set_status( return true; }); break; - case SubghzReadRAWStatusIDLE: + case SubGhzReadRAWStatusIDLE: with_view_model( - instance->view, (SubghzReadRAWModel * model) { - model->satus = SubghzReadRAWStatusIDLE; + instance->view, (SubGhzReadRAWModel * model) { + model->satus = SubGhzReadRAWStatusIDLE; return true; }); break; - case SubghzReadRAWStatusLoadKeyTX: + case SubGhzReadRAWStatusLoadKeyTX: with_view_model( - instance->view, (SubghzReadRAWModel * model) { - model->satus = SubghzReadRAWStatusLoadKeyIDLE; + instance->view, (SubGhzReadRAWModel * model) { + model->satus = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; string_set(model->file_name, file_name); @@ -442,10 +440,10 @@ void subghz_read_raw_set_status( return true; }); break; - case SubghzReadRAWStatusSaveKey: + case SubGhzReadRAWStatusSaveKey: with_view_model( - instance->view, (SubghzReadRAWModel * model) { - model->satus = SubghzReadRAWStatusLoadKeyIDLE; + instance->view, (SubGhzReadRAWModel * model) { + model->satus = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { string_set(model->file_name, file_name); string_set(model->sample_write, "RAW"); @@ -464,31 +462,31 @@ void subghz_read_raw_set_status( void subghz_read_raw_enter(void* context) { furi_assert(context); - //SubghzReadRAW* instance = context; + //SubGhzReadRAW* instance = context; } void subghz_read_raw_exit(void* context) { furi_assert(context); - SubghzReadRAW* instance = context; + SubGhzReadRAW* instance = context; with_view_model( - instance->view, (SubghzReadRAWModel * model) { - if(model->satus != SubghzReadRAWStatusIDLE && - model->satus != SubghzReadRAWStatusStart && - model->satus != SubghzReadRAWStatusLoadKeyIDLE) { - instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context); - model->satus = SubghzReadRAWStatusStart; + instance->view, (SubGhzReadRAWModel * model) { + if(model->satus != SubGhzReadRAWStatusIDLE && + model->satus != SubGhzReadRAWStatusStart && + model->satus != SubGhzReadRAWStatusLoadKeyIDLE) { + instance->callback(SubGhzCustomEventViewReadRAWIDLE, instance->context); + model->satus = SubGhzReadRAWStatusStart; } return true; }); } -SubghzReadRAW* subghz_read_raw_alloc() { - SubghzReadRAW* instance = malloc(sizeof(SubghzReadRAW)); +SubGhzReadRAW* subghz_read_raw_alloc() { + SubGhzReadRAW* instance = malloc(sizeof(SubGhzReadRAW)); // View allocation and configuration instance->view = view_alloc(); - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubghzReadRAWModel)); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzReadRAWModel)); view_set_context(instance->view, instance); view_set_draw_callback(instance->view, (ViewDrawCallback)subghz_read_raw_draw); view_set_input_callback(instance->view, subghz_read_raw_input); @@ -496,7 +494,7 @@ SubghzReadRAW* subghz_read_raw_alloc() { view_set_exit_callback(instance->view, subghz_read_raw_exit); with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { string_init(model->frequency_str); string_init(model->preset_str); string_init(model->sample_write); @@ -508,11 +506,11 @@ SubghzReadRAW* subghz_read_raw_alloc() { return instance; } -void subghz_read_raw_free(SubghzReadRAW* instance) { +void subghz_read_raw_free(SubGhzReadRAW* instance) { furi_assert(instance); with_view_model( - instance->view, (SubghzReadRAWModel * model) { + instance->view, (SubGhzReadRAWModel * model) { string_clear(model->frequency_str); string_clear(model->preset_str); string_clear(model->sample_write); @@ -524,7 +522,7 @@ void subghz_read_raw_free(SubghzReadRAW* instance) { free(instance); } -View* subghz_read_raw_get_view(SubghzReadRAW* instance) { +View* subghz_read_raw_get_view(SubGhzReadRAW* instance) { furi_assert(instance); return instance->view; } diff --git a/applications/subghz/views/subghz_read_raw.h b/applications/subghz/views/subghz_read_raw.h index 443d0185..db75f1b4 100644 --- a/applications/subghz/views/subghz_read_raw.h +++ b/applications/subghz/views/subghz_read_raw.h @@ -3,48 +3,48 @@ #include #include "../helpers/subghz_custom_event.h" -typedef struct SubghzReadRAW SubghzReadRAW; +typedef struct SubGhzReadRAW SubGhzReadRAW; -typedef void (*SubghzReadRAWCallback)(SubghzCustomEvent event, void* context); +typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context); typedef enum { - SubghzReadRAWStatusStart, - SubghzReadRAWStatusIDLE, - SubghzReadRAWStatusREC, - SubghzReadRAWStatusTX, - SubghzReadRAWStatusTXRepeat, + SubGhzReadRAWStatusStart, + SubGhzReadRAWStatusIDLE, + SubGhzReadRAWStatusREC, + SubGhzReadRAWStatusTX, + SubGhzReadRAWStatusTXRepeat, - SubghzReadRAWStatusLoadKeyIDLE, - SubghzReadRAWStatusLoadKeyTX, - SubghzReadRAWStatusLoadKeyTXRepeat, - SubghzReadRAWStatusSaveKey, -} SubghzReadRAWStatus; + SubGhzReadRAWStatusLoadKeyIDLE, + SubGhzReadRAWStatusLoadKeyTX, + SubGhzReadRAWStatusLoadKeyTXRepeat, + SubGhzReadRAWStatusSaveKey, +} SubGhzReadRAWStatus; void subghz_read_raw_set_callback( - SubghzReadRAW* subghz_read_raw, - SubghzReadRAWCallback callback, + SubGhzReadRAW* subghz_read_raw, + SubGhzReadRAWCallback callback, void* context); -SubghzReadRAW* subghz_read_raw_alloc(); +SubGhzReadRAW* subghz_read_raw_alloc(); -void subghz_read_raw_free(SubghzReadRAW* subghz_static); +void subghz_read_raw_free(SubGhzReadRAW* subghz_static); void subghz_read_raw_add_data_statusbar( - SubghzReadRAW* instance, + SubGhzReadRAW* instance, const char* frequency_str, const char* preset_str); -void subghz_read_raw_update_sample_write(SubghzReadRAW* instance, size_t sample); +void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample); -void subghz_read_raw_stop_send(SubghzReadRAW* instance); +void subghz_read_raw_stop_send(SubGhzReadRAW* instance); -void subghz_read_raw_update_sin(SubghzReadRAW* instance); +void subghz_read_raw_update_sin(SubGhzReadRAW* instance); -void subghz_read_raw_add_data_rssi(SubghzReadRAW* instance, float rssi); +void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi); void subghz_read_raw_set_status( - SubghzReadRAW* instance, - SubghzReadRAWStatus satus, + SubGhzReadRAW* instance, + SubGhzReadRAWStatus satus, const char* file_name); -View* subghz_read_raw_get_view(SubghzReadRAW* subghz_static); +View* subghz_read_raw_get_view(SubGhzReadRAW* subghz_static); diff --git a/applications/subghz/views/subghz_receiver.h b/applications/subghz/views/subghz_receiver.h deleted file mode 100644 index e26ee2c2..00000000 --- a/applications/subghz/views/subghz_receiver.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include "../helpers/subghz_custom_event.h" - -typedef struct SubghzReceiver SubghzReceiver; - -typedef void (*SubghzReceiverCallback)(SubghzCustomEvent event, void* context); - -void subghz_receiver_set_callback( - SubghzReceiver* subghz_receiver, - SubghzReceiverCallback callback, - void* context); - -SubghzReceiver* subghz_receiver_alloc(); - -void subghz_receiver_free(SubghzReceiver* subghz_receiver); - -View* subghz_receiver_get_view(SubghzReceiver* subghz_receiver); - -void subghz_receiver_add_data_statusbar( - SubghzReceiver* subghz_receiver, - const char* frequency_str, - const char* preset_str, - const char* history_stat_str); - -void subghz_receiver_add_item_to_menu( - SubghzReceiver* subghz_receiver, - const char* name, - uint8_t type); - -uint16_t subghz_receiver_get_idx_menu(SubghzReceiver* subghz_receiver); - -void subghz_receiver_set_idx_menu(SubghzReceiver* subghz_receiver, uint16_t idx); - -void subghz_receiver_exit(void* context); \ No newline at end of file diff --git a/applications/subghz/views/subghz_test_carrier.c b/applications/subghz/views/subghz_test_carrier.c index 82d95bff..6c0a8d16 100644 --- a/applications/subghz/views/subghz_test_carrier.c +++ b/applications/subghz/views/subghz_test_carrier.c @@ -7,29 +7,29 @@ #include #include -struct SubghzTestCarrier { +struct SubGhzTestCarrier { View* view; osTimerId_t timer; - SubghzTestCarrierCallback callback; + SubGhzTestCarrierCallback callback; void* context; }; typedef enum { - SubghzTestCarrierModelStatusRx, - SubghzTestCarrierModelStatusTx, -} SubghzTestCarrierModelStatus; + SubGhzTestCarrierModelStatusRx, + SubGhzTestCarrierModelStatusTx, +} SubGhzTestCarrierModelStatus; typedef struct { uint8_t frequency; uint32_t real_frequency; FuriHalSubGhzPath path; float rssi; - SubghzTestCarrierModelStatus status; -} SubghzTestCarrierModel; + SubGhzTestCarrierModelStatus status; +} SubGhzTestCarrierModel; void subghz_test_carrier_set_callback( - SubghzTestCarrier* subghz_test_carrier, - SubghzTestCarrierCallback callback, + SubGhzTestCarrier* subghz_test_carrier, + SubGhzTestCarrierCallback callback, void* context) { furi_assert(subghz_test_carrier); furi_assert(callback); @@ -37,7 +37,7 @@ void subghz_test_carrier_set_callback( subghz_test_carrier->context = context; } -void subghz_test_carrier_draw(Canvas* canvas, SubghzTestCarrierModel* model) { +void subghz_test_carrier_draw(Canvas* canvas, SubGhzTestCarrierModel* model) { char buffer[64]; canvas_set_color(canvas, ColorBlack); @@ -67,7 +67,7 @@ void subghz_test_carrier_draw(Canvas* canvas, SubghzTestCarrierModel* model) { } snprintf(buffer, sizeof(buffer), "Path: %d - %s", model->path, path_name); canvas_draw_str(canvas, 0, 31, buffer); - if(model->status == SubghzTestCarrierModelStatusRx) { + if(model->status == SubGhzTestCarrierModelStatusRx) { snprintf( buffer, sizeof(buffer), @@ -82,14 +82,14 @@ void subghz_test_carrier_draw(Canvas* canvas, SubghzTestCarrierModel* model) { bool subghz_test_carrier_input(InputEvent* event, void* context) { furi_assert(context); - SubghzTestCarrier* subghz_test_carrier = context; + SubGhzTestCarrier* subghz_test_carrier = context; if(event->key == InputKeyBack || event->type != InputTypeShort) { return false; } with_view_model( - subghz_test_carrier->view, (SubghzTestCarrierModel * model) { + subghz_test_carrier->view, (SubGhzTestCarrierModel * model) { furi_hal_subghz_idle(); if(event->key == InputKeyLeft) { @@ -101,10 +101,10 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { } else if(event->key == InputKeyUp) { if(model->path < FuriHalSubGhzPath868) model->path++; } else if(event->key == InputKeyOk) { - if(model->status == SubghzTestCarrierModelStatusTx) { - model->status = SubghzTestCarrierModelStatusRx; + if(model->status == SubGhzTestCarrierModelStatusTx) { + model->status = SubGhzTestCarrierModelStatusRx; } else { - model->status = SubghzTestCarrierModelStatusTx; + model->status = SubGhzTestCarrierModelStatusTx; } } @@ -112,7 +112,7 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); furi_hal_subghz_set_path(model->path); - if(model->status == SubghzTestCarrierModelStatusRx) { + if(model->status == SubGhzTestCarrierModelStatusRx) { hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_rx(); } else { @@ -121,7 +121,7 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { if(!furi_hal_subghz_tx()) { hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); subghz_test_carrier->callback( - SubghzTestCarrierEventOnlyRx, subghz_test_carrier->context); + SubGhzTestCarrierEventOnlyRx, subghz_test_carrier->context); } } @@ -133,7 +133,7 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { void subghz_test_carrier_enter(void* context) { furi_assert(context); - SubghzTestCarrier* subghz_test_carrier = context; + SubGhzTestCarrier* subghz_test_carrier = context; furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); @@ -141,13 +141,13 @@ void subghz_test_carrier_enter(void* context) { hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); with_view_model( - subghz_test_carrier->view, (SubghzTestCarrierModel * model) { + subghz_test_carrier->view, (SubGhzTestCarrierModel * model) { model->frequency = subghz_frequencies_433_92_testing; // 433 model->real_frequency = furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); model->path = FuriHalSubGhzPathIsolate; // isolate model->rssi = 0.0f; - model->status = SubghzTestCarrierModelStatusRx; + model->status = SubGhzTestCarrierModelStatusRx; return true; }); @@ -158,7 +158,7 @@ void subghz_test_carrier_enter(void* context) { void subghz_test_carrier_exit(void* context) { furi_assert(context); - SubghzTestCarrier* subghz_test_carrier = context; + SubGhzTestCarrier* subghz_test_carrier = context; osTimerStop(subghz_test_carrier->timer); @@ -168,11 +168,11 @@ void subghz_test_carrier_exit(void* context) { void subghz_test_carrier_rssi_timer_callback(void* context) { furi_assert(context); - SubghzTestCarrier* subghz_test_carrier = context; + SubGhzTestCarrier* subghz_test_carrier = context; with_view_model( - subghz_test_carrier->view, (SubghzTestCarrierModel * model) { - if(model->status == SubghzTestCarrierModelStatusRx) { + subghz_test_carrier->view, (SubGhzTestCarrierModel * model) { + if(model->status == SubGhzTestCarrierModelStatusRx) { model->rssi = furi_hal_subghz_get_rssi(); return true; } @@ -180,13 +180,13 @@ void subghz_test_carrier_rssi_timer_callback(void* context) { }); } -SubghzTestCarrier* subghz_test_carrier_alloc() { - SubghzTestCarrier* subghz_test_carrier = malloc(sizeof(SubghzTestCarrier)); +SubGhzTestCarrier* subghz_test_carrier_alloc() { + SubGhzTestCarrier* subghz_test_carrier = malloc(sizeof(SubGhzTestCarrier)); // View allocation and configuration subghz_test_carrier->view = view_alloc(); view_allocate_model( - subghz_test_carrier->view, ViewModelTypeLocking, sizeof(SubghzTestCarrierModel)); + subghz_test_carrier->view, ViewModelTypeLocking, sizeof(SubGhzTestCarrierModel)); view_set_context(subghz_test_carrier->view, subghz_test_carrier); view_set_draw_callback(subghz_test_carrier->view, (ViewDrawCallback)subghz_test_carrier_draw); view_set_input_callback(subghz_test_carrier->view, subghz_test_carrier_input); @@ -199,14 +199,14 @@ SubghzTestCarrier* subghz_test_carrier_alloc() { return subghz_test_carrier; } -void subghz_test_carrier_free(SubghzTestCarrier* subghz_test_carrier) { +void subghz_test_carrier_free(SubGhzTestCarrier* subghz_test_carrier) { furi_assert(subghz_test_carrier); osTimerDelete(subghz_test_carrier->timer); view_free(subghz_test_carrier->view); free(subghz_test_carrier); } -View* subghz_test_carrier_get_view(SubghzTestCarrier* subghz_test_carrier) { +View* subghz_test_carrier_get_view(SubGhzTestCarrier* subghz_test_carrier) { furi_assert(subghz_test_carrier); return subghz_test_carrier->view; } diff --git a/applications/subghz/views/subghz_test_carrier.h b/applications/subghz/views/subghz_test_carrier.h index b4522345..7db3343e 100644 --- a/applications/subghz/views/subghz_test_carrier.h +++ b/applications/subghz/views/subghz_test_carrier.h @@ -3,20 +3,20 @@ #include typedef enum { - SubghzTestCarrierEventOnlyRx, -} SubghzTestCarrierEvent; + SubGhzTestCarrierEventOnlyRx, +} SubGhzTestCarrierEvent; -typedef struct SubghzTestCarrier SubghzTestCarrier; +typedef struct SubGhzTestCarrier SubGhzTestCarrier; -typedef void (*SubghzTestCarrierCallback)(SubghzTestCarrierEvent event, void* context); +typedef void (*SubGhzTestCarrierCallback)(SubGhzTestCarrierEvent event, void* context); void subghz_test_carrier_set_callback( - SubghzTestCarrier* subghz_test_carrier, - SubghzTestCarrierCallback callback, + SubGhzTestCarrier* subghz_test_carrier, + SubGhzTestCarrierCallback callback, void* context); -SubghzTestCarrier* subghz_test_carrier_alloc(); +SubGhzTestCarrier* subghz_test_carrier_alloc(); -void subghz_test_carrier_free(SubghzTestCarrier* subghz_test_carrier); +void subghz_test_carrier_free(SubGhzTestCarrier* subghz_test_carrier); -View* subghz_test_carrier_get_view(SubghzTestCarrier* subghz_test_carrier); +View* subghz_test_carrier_get_view(SubGhzTestCarrier* subghz_test_carrier); diff --git a/applications/subghz/views/subghz_test_packet.c b/applications/subghz/views/subghz_test_packet.c index c856dc74..509d5c0a 100644 --- a/applications/subghz/views/subghz_test_packet.c +++ b/applications/subghz/views/subghz_test_packet.c @@ -7,26 +7,26 @@ #include #include #include -#include +#include #define SUBGHZ_TEST_PACKET_COUNT 500 -struct SubghzTestPacket { +struct SubGhzTestPacket { View* view; osTimerId_t timer; SubGhzDecoderPrinceton* decoder; SubGhzEncoderPrinceton* encoder; volatile size_t packet_rx; - SubghzTestPacketCallback callback; + SubGhzTestPacketCallback callback; void* context; }; typedef enum { - SubghzTestPacketModelStatusRx, - SubghzTestPacketModelStatusOnlyRx, - SubghzTestPacketModelStatusTx, -} SubghzTestPacketModelStatus; + SubGhzTestPacketModelStatusRx, + SubGhzTestPacketModelStatusOnlyRx, + SubGhzTestPacketModelStatusTx, +} SubGhzTestPacketModelStatus; typedef struct { uint8_t frequency; @@ -34,14 +34,14 @@ typedef struct { FuriHalSubGhzPath path; float rssi; size_t packets; - SubghzTestPacketModelStatus status; -} SubghzTestPacketModel; + SubGhzTestPacketModelStatus status; +} SubGhzTestPacketModel; volatile bool subghz_test_packet_overrun = false; void subghz_test_packet_set_callback( - SubghzTestPacket* subghz_test_packet, - SubghzTestPacketCallback callback, + SubGhzTestPacket* subghz_test_packet, + SubGhzTestPacketCallback callback, void* context) { furi_assert(subghz_test_packet); furi_assert(callback); @@ -51,34 +51,36 @@ void subghz_test_packet_set_callback( static void subghz_test_packet_rx_callback(bool level, uint32_t duration, void* context) { furi_assert(context); - SubghzTestPacket* instance = context; - subghz_decoder_princeton_parse(instance->decoder, level, duration); + SubGhzTestPacket* instance = context; + subghz_decoder_princeton_for_testing_parse(instance->decoder, level, duration); } -static void subghz_test_packet_rx_pt_callback(SubGhzProtocolCommon* parser, void* context) { +//todo +static void subghz_test_packet_rx_pt_callback(SubGhzDecoderPrinceton* parser, void* context) { furi_assert(context); - SubghzTestPacket* instance = context; + SubGhzTestPacket* instance = context; instance->packet_rx++; } static void subghz_test_packet_rssi_timer_callback(void* context) { furi_assert(context); - SubghzTestPacket* instance = context; + SubGhzTestPacket* instance = context; with_view_model( - instance->view, (SubghzTestPacketModel * model) { - if(model->status == SubghzTestPacketModelStatusRx) { + instance->view, (SubGhzTestPacketModel * model) { + if(model->status == SubGhzTestPacketModelStatusRx) { model->rssi = furi_hal_subghz_get_rssi(); model->packets = instance->packet_rx; - } else if(model->status == SubghzTestPacketModelStatusTx) { - model->packets = SUBGHZ_TEST_PACKET_COUNT - - subghz_encoder_princeton_get_repeat_left(instance->encoder); + } else if(model->status == SubGhzTestPacketModelStatusTx) { + model->packets = + SUBGHZ_TEST_PACKET_COUNT - + subghz_encoder_princeton_for_testing_get_repeat_left(instance->encoder); } return true; }); } -static void subghz_test_packet_draw(Canvas* canvas, SubghzTestPacketModel* model) { +static void subghz_test_packet_draw(Canvas* canvas, SubGhzTestPacketModel* model) { char buffer[64]; canvas_set_color(canvas, ColorBlack); @@ -112,7 +114,7 @@ static void subghz_test_packet_draw(Canvas* canvas, SubghzTestPacketModel* model snprintf(buffer, sizeof(buffer), "Packets: %d", model->packets); canvas_draw_str(canvas, 0, 42, buffer); - if(model->status == SubghzTestPacketModelStatusRx) { + if(model->status == SubGhzTestPacketModelStatusRx) { snprintf( buffer, sizeof(buffer), @@ -127,18 +129,18 @@ static void subghz_test_packet_draw(Canvas* canvas, SubghzTestPacketModel* model static bool subghz_test_packet_input(InputEvent* event, void* context) { furi_assert(context); - SubghzTestPacket* instance = context; + SubGhzTestPacket* instance = context; if(event->key == InputKeyBack || event->type != InputTypeShort) { return false; } with_view_model( - instance->view, (SubghzTestPacketModel * model) { - if(model->status == SubghzTestPacketModelStatusRx) { + instance->view, (SubGhzTestPacketModel * model) { + if(model->status == SubGhzTestPacketModelStatusRx) { furi_hal_subghz_stop_async_rx(); - } else if(model->status == SubghzTestPacketModelStatusTx) { - subghz_encoder_princeton_stop(instance->encoder, millis()); + } else if(model->status == SubGhzTestPacketModelStatusTx) { + subghz_encoder_princeton_for_testing_stop(instance->encoder, millis()); furi_hal_subghz_stop_async_tx(); } @@ -151,10 +153,10 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) { } else if(event->key == InputKeyUp) { if(model->path < FuriHalSubGhzPath868) model->path++; } else if(event->key == InputKeyOk) { - if(model->status == SubghzTestPacketModelStatusRx) { - model->status = SubghzTestPacketModelStatusTx; + if(model->status == SubGhzTestPacketModelStatusRx) { + model->status = SubGhzTestPacketModelStatusTx; } else { - model->status = SubghzTestPacketModelStatusRx; + model->status = SubGhzTestPacketModelStatusRx; } } @@ -162,18 +164,18 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) { furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); furi_hal_subghz_set_path(model->path); - if(model->status == SubghzTestPacketModelStatusRx) { + if(model->status == SubGhzTestPacketModelStatusRx) { furi_hal_subghz_start_async_rx(subghz_test_packet_rx_callback, instance); } else { - subghz_encoder_princeton_set( + subghz_encoder_princeton_for_testing_set( instance->encoder, 0x00AABBCC, SUBGHZ_TEST_PACKET_COUNT, subghz_frequencies_testing[model->frequency]); if(!furi_hal_subghz_start_async_tx( - subghz_encoder_princeton_yield, instance->encoder)) { - model->status = SubghzTestPacketModelStatusOnlyRx; - instance->callback(SubghzTestPacketEventOnlyRx, instance->context); + subghz_encoder_princeton_for_testing_yield, instance->encoder)) { + model->status = SubGhzTestPacketModelStatusOnlyRx; + instance->callback(SubGhzTestPacketEventOnlyRx, instance->context); } } @@ -185,19 +187,19 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) { void subghz_test_packet_enter(void* context) { furi_assert(context); - SubghzTestPacket* instance = context; + SubGhzTestPacket* instance = context; furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); with_view_model( - instance->view, (SubghzTestPacketModel * model) { + instance->view, (SubGhzTestPacketModel * model) { model->frequency = subghz_frequencies_433_92_testing; model->real_frequency = furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); model->path = FuriHalSubGhzPathIsolate; // isolate model->rssi = 0.0f; - model->status = SubghzTestPacketModelStatusRx; + model->status = SubGhzTestPacketModelStatusRx; return true; }); @@ -208,17 +210,17 @@ void subghz_test_packet_enter(void* context) { void subghz_test_packet_exit(void* context) { furi_assert(context); - SubghzTestPacket* instance = context; + SubGhzTestPacket* instance = context; osTimerStop(instance->timer); // Reinitialize IC to default state with_view_model( - instance->view, (SubghzTestPacketModel * model) { - if(model->status == SubghzTestPacketModelStatusRx) { + instance->view, (SubGhzTestPacketModel * model) { + if(model->status == SubGhzTestPacketModelStatusRx) { furi_hal_subghz_stop_async_rx(); - } else if(model->status == SubghzTestPacketModelStatusTx) { - subghz_encoder_princeton_stop(instance->encoder, millis()); + } else if(model->status == SubGhzTestPacketModelStatusTx) { + subghz_encoder_princeton_for_testing_stop(instance->encoder, millis()); furi_hal_subghz_stop_async_tx(); } return true; @@ -226,12 +228,12 @@ void subghz_test_packet_exit(void* context) { furi_hal_subghz_sleep(); } -SubghzTestPacket* subghz_test_packet_alloc() { - SubghzTestPacket* instance = malloc(sizeof(SubghzTestPacket)); +SubGhzTestPacket* subghz_test_packet_alloc() { + SubGhzTestPacket* instance = malloc(sizeof(SubGhzTestPacket)); // View allocation and configuration instance->view = view_alloc(); - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubghzTestPacketModel)); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzTestPacketModel)); view_set_context(instance->view, instance); view_set_draw_callback(instance->view, (ViewDrawCallback)subghz_test_packet_draw); view_set_input_callback(instance->view, subghz_test_packet_input); @@ -241,26 +243,26 @@ SubghzTestPacket* subghz_test_packet_alloc() { instance->timer = osTimerNew(subghz_test_packet_rssi_timer_callback, osTimerPeriodic, instance, NULL); - instance->decoder = subghz_decoder_princeton_alloc(); - subghz_protocol_common_set_callback( - (SubGhzProtocolCommon*)instance->decoder, subghz_test_packet_rx_pt_callback, instance); - instance->encoder = subghz_encoder_princeton_alloc(); + instance->decoder = subghz_decoder_princeton_for_testing_alloc(); + subghz_decoder_princeton_for_testing_set_callback( + instance->decoder, subghz_test_packet_rx_pt_callback, instance); + instance->encoder = subghz_encoder_princeton_for_testing_alloc(); return instance; } -void subghz_test_packet_free(SubghzTestPacket* instance) { +void subghz_test_packet_free(SubGhzTestPacket* instance) { furi_assert(instance); - subghz_decoder_princeton_free(instance->decoder); - subghz_encoder_princeton_free(instance->encoder); + subghz_decoder_princeton_for_testing_free(instance->decoder); + subghz_encoder_princeton_for_testing_free(instance->encoder); osTimerDelete(instance->timer); view_free(instance->view); free(instance); } -View* subghz_test_packet_get_view(SubghzTestPacket* instance) { +View* subghz_test_packet_get_view(SubGhzTestPacket* instance) { furi_assert(instance); return instance->view; } diff --git a/applications/subghz/views/subghz_test_packet.h b/applications/subghz/views/subghz_test_packet.h index bd861ee9..f384cf4f 100644 --- a/applications/subghz/views/subghz_test_packet.h +++ b/applications/subghz/views/subghz_test_packet.h @@ -3,20 +3,20 @@ #include typedef enum { - SubghzTestPacketEventOnlyRx, -} SubghzTestPacketEvent; + SubGhzTestPacketEventOnlyRx, +} SubGhzTestPacketEvent; -typedef struct SubghzTestPacket SubghzTestPacket; +typedef struct SubGhzTestPacket SubGhzTestPacket; -typedef void (*SubghzTestPacketCallback)(SubghzTestPacketEvent event, void* context); +typedef void (*SubGhzTestPacketCallback)(SubGhzTestPacketEvent event, void* context); void subghz_test_packet_set_callback( - SubghzTestPacket* subghz_test_packet, - SubghzTestPacketCallback callback, + SubGhzTestPacket* subghz_test_packet, + SubGhzTestPacketCallback callback, void* context); -SubghzTestPacket* subghz_test_packet_alloc(); +SubGhzTestPacket* subghz_test_packet_alloc(); -void subghz_test_packet_free(SubghzTestPacket* subghz_test_packet); +void subghz_test_packet_free(SubGhzTestPacket* subghz_test_packet); -View* subghz_test_packet_get_view(SubghzTestPacket* subghz_test_packet); +View* subghz_test_packet_get_view(SubGhzTestPacket* subghz_test_packet); diff --git a/applications/subghz/views/subghz_test_static.c b/applications/subghz/views/subghz_test_static.c index 35001bdc..aa564fd9 100644 --- a/applications/subghz/views/subghz_test_static.c +++ b/applications/subghz/views/subghz_test_static.c @@ -7,14 +7,14 @@ #include #include #include -#include +#include #define TAG "SubGhzTestStatic" typedef enum { - SubghzTestStaticStatusIDLE, - SubghzTestStaticStatusTX, -} SubghzTestStaticStatus; + SubGhzTestStaticStatusIDLE, + SubGhzTestStaticStatusTX, +} SubGhzTestStaticStatus; static const uint32_t subghz_test_static_keys[] = { 0x0074BADE, @@ -23,11 +23,11 @@ static const uint32_t subghz_test_static_keys[] = { 0x00E34A4E, }; -struct SubghzTestStatic { +struct SubGhzTestStatic { View* view; - SubghzTestStaticStatus satus_tx; + SubGhzTestStaticStatus satus_tx; SubGhzEncoderPrinceton* encoder; - SubghzTestStaticCallback callback; + SubGhzTestStaticCallback callback; void* context; }; @@ -35,11 +35,11 @@ typedef struct { uint8_t frequency; uint32_t real_frequency; uint8_t button; -} SubghzTestStaticModel; +} SubGhzTestStaticModel; void subghz_test_static_set_callback( - SubghzTestStatic* subghz_test_static, - SubghzTestStaticCallback callback, + SubGhzTestStatic* subghz_test_static, + SubGhzTestStaticCallback callback, void* context) { furi_assert(subghz_test_static); furi_assert(callback); @@ -47,7 +47,7 @@ void subghz_test_static_set_callback( subghz_test_static->context = context; } -void subghz_test_static_draw(Canvas* canvas, SubghzTestStaticModel* model) { +void subghz_test_static_draw(Canvas* canvas, SubGhzTestStaticModel* model) { char buffer[64]; canvas_set_color(canvas, ColorBlack); @@ -70,14 +70,14 @@ void subghz_test_static_draw(Canvas* canvas, SubghzTestStaticModel* model) { bool subghz_test_static_input(InputEvent* event, void* context) { furi_assert(context); - SubghzTestStatic* instance = context; + SubGhzTestStatic* instance = context; if(event->key == InputKeyBack) { return false; } with_view_model( - instance->view, (SubghzTestStaticModel * model) { + instance->view, (SubGhzTestStaticModel * model) { if(event->type == InputTypeShort) { if(event->key == InputKeyLeft) { if(model->frequency > 0) model->frequency--; @@ -99,31 +99,31 @@ bool subghz_test_static_input(InputEvent* event, void* context) { furi_hal_subghz_set_frequency_and_path( subghz_frequencies_testing[model->frequency]); if(!furi_hal_subghz_tx()) { - instance->callback(SubghzTestStaticEventOnlyRx, instance->context); + instance->callback(SubGhzTestStaticEventOnlyRx, instance->context); } else { notification_message_block(notification, &sequence_set_red_255); FURI_LOG_I(TAG, "TX Start"); - subghz_encoder_princeton_set( + subghz_encoder_princeton_for_testing_set( instance->encoder, subghz_test_static_keys[model->button], 10000, subghz_frequencies_testing[model->frequency]); furi_hal_subghz_start_async_tx( - subghz_encoder_princeton_yield, instance->encoder); - instance->satus_tx = SubghzTestStaticStatusTX; + subghz_encoder_princeton_for_testing_yield, instance->encoder); + instance->satus_tx = SubGhzTestStaticStatusTX; } } else if(event->type == InputTypeRelease) { - if(instance->satus_tx == SubghzTestStaticStatusTX) { + if(instance->satus_tx == SubGhzTestStaticStatusTX) { FURI_LOG_I(TAG, "TX Stop"); - subghz_encoder_princeton_stop(instance->encoder, millis()); - subghz_encoder_princeton_print_log(instance->encoder); + subghz_encoder_princeton_for_testing_stop(instance->encoder, millis()); + subghz_encoder_princeton_for_testing_print_log(instance->encoder); furi_hal_subghz_stop_async_tx(); notification_message(notification, &sequence_reset_red); } - instance->satus_tx = SubghzTestStaticStatusIDLE; + instance->satus_tx = SubGhzTestStaticStatusIDLE; } furi_record_close("notification"); } @@ -136,17 +136,17 @@ bool subghz_test_static_input(InputEvent* event, void* context) { void subghz_test_static_enter(void* context) { furi_assert(context); - SubghzTestStatic* instance = context; + SubGhzTestStatic* instance = context; furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); hal_gpio_write(&gpio_cc1101_g0, false); - instance->satus_tx = SubghzTestStaticStatusIDLE; + instance->satus_tx = SubGhzTestStaticStatusIDLE; with_view_model( - instance->view, (SubghzTestStaticModel * model) { + instance->view, (SubGhzTestStaticModel * model) { model->frequency = subghz_frequencies_433_92_testing; model->real_frequency = subghz_frequencies_testing[model->frequency]; model->button = 0; @@ -160,31 +160,31 @@ void subghz_test_static_exit(void* context) { furi_hal_subghz_sleep(); } -SubghzTestStatic* subghz_test_static_alloc() { - SubghzTestStatic* instance = malloc(sizeof(SubghzTestStatic)); +SubGhzTestStatic* subghz_test_static_alloc() { + SubGhzTestStatic* instance = malloc(sizeof(SubGhzTestStatic)); // View allocation and configuration instance->view = view_alloc(); - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubghzTestStaticModel)); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzTestStaticModel)); view_set_context(instance->view, instance); view_set_draw_callback(instance->view, (ViewDrawCallback)subghz_test_static_draw); view_set_input_callback(instance->view, subghz_test_static_input); view_set_enter_callback(instance->view, subghz_test_static_enter); view_set_exit_callback(instance->view, subghz_test_static_exit); - instance->encoder = subghz_encoder_princeton_alloc(); + instance->encoder = subghz_encoder_princeton_for_testing_alloc(); return instance; } -void subghz_test_static_free(SubghzTestStatic* instance) { +void subghz_test_static_free(SubGhzTestStatic* instance) { furi_assert(instance); - subghz_encoder_princeton_free(instance->encoder); + subghz_encoder_princeton_for_testing_free(instance->encoder); view_free(instance->view); free(instance); } -View* subghz_test_static_get_view(SubghzTestStatic* instance) { +View* subghz_test_static_get_view(SubGhzTestStatic* instance) { furi_assert(instance); return instance->view; } diff --git a/applications/subghz/views/subghz_test_static.h b/applications/subghz/views/subghz_test_static.h index 48346d8a..90203643 100644 --- a/applications/subghz/views/subghz_test_static.h +++ b/applications/subghz/views/subghz_test_static.h @@ -3,20 +3,20 @@ #include typedef enum { - SubghzTestStaticEventOnlyRx, -} SubghzTestStaticEvent; + SubGhzTestStaticEventOnlyRx, +} SubGhzTestStaticEvent; -typedef struct SubghzTestStatic SubghzTestStatic; +typedef struct SubGhzTestStatic SubGhzTestStatic; -typedef void (*SubghzTestStaticCallback)(SubghzTestStaticEvent event, void* context); +typedef void (*SubGhzTestStaticCallback)(SubGhzTestStaticEvent event, void* context); void subghz_test_static_set_callback( - SubghzTestStatic* subghz_test_static, - SubghzTestStaticCallback callback, + SubGhzTestStatic* subghz_test_static, + SubGhzTestStaticCallback callback, void* context); -SubghzTestStatic* subghz_test_static_alloc(); +SubGhzTestStatic* subghz_test_static_alloc(); -void subghz_test_static_free(SubghzTestStatic* subghz_static); +void subghz_test_static_free(SubGhzTestStatic* subghz_static); -View* subghz_test_static_get_view(SubghzTestStatic* subghz_static); +View* subghz_test_static_get_view(SubGhzTestStatic* subghz_static); diff --git a/applications/subghz/views/subghz_transmitter.h b/applications/subghz/views/subghz_transmitter.h deleted file mode 100644 index 995e08f6..00000000 --- a/applications/subghz/views/subghz_transmitter.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "../helpers/subghz_custom_event.h" - -typedef struct SubghzTransmitter SubghzTransmitter; - -typedef void (*SubghzTransmitterCallback)(SubghzCustomEvent event, void* context); - -void subghz_transmitter_set_callback( - SubghzTransmitter* subghz_transmitter, - SubghzTransmitterCallback callback, - void* context); - -SubghzTransmitter* subghz_transmitter_alloc(); - -void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter); - -View* subghz_transmitter_get_view(SubghzTransmitter* subghz_transmitter); - -void subghz_transmitter_add_data_to_show( - SubghzTransmitter* subghz_transmitter, - const char* key_str, - const char* frequency_str, - const char* preset_str, - uint8_t show_button); diff --git a/applications/subghz/views/subghz_transmitter.c b/applications/subghz/views/transmitter.c similarity index 68% rename from applications/subghz/views/subghz_transmitter.c rename to applications/subghz/views/transmitter.c index f1afccfa..3113e31f 100644 --- a/applications/subghz/views/subghz_transmitter.c +++ b/applications/subghz/views/transmitter.c @@ -1,12 +1,12 @@ -#include "subghz_transmitter.h" +#include "transmitter.h" #include "../subghz_i.h" #include #include -struct SubghzTransmitter { +struct SubGhzViewTransmitter { View* view; - SubghzTransmitterCallback callback; + SubGhzViewTransmitterCallback callback; void* context; }; @@ -15,11 +15,11 @@ typedef struct { string_t preset_str; string_t key_str; uint8_t show_button; -} SubghzTransmitterModel; +} SubGhzViewTransmitterModel; -void subghz_transmitter_set_callback( - SubghzTransmitter* subghz_transmitter, - SubghzTransmitterCallback callback, +void subghz_view_transmitter_set_callback( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzViewTransmitterCallback callback, void* context) { furi_assert(subghz_transmitter); @@ -27,15 +27,15 @@ void subghz_transmitter_set_callback( subghz_transmitter->context = context; } -void subghz_transmitter_add_data_to_show( - SubghzTransmitter* subghz_transmitter, +void subghz_view_transmitter_add_data_to_show( + SubGhzViewTransmitter* subghz_transmitter, const char* key_str, const char* frequency_str, const char* preset_str, uint8_t show_button) { furi_assert(subghz_transmitter); with_view_model( - subghz_transmitter->view, (SubghzTransmitterModel * model) { + subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { string_set(model->key_str, key_str); string_set(model->frequency_str, frequency_str); string_set(model->preset_str, preset_str); @@ -44,7 +44,7 @@ void subghz_transmitter_add_data_to_show( }); } -static void subghz_transmitter_button_right(Canvas* canvas, const char* str) { +static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { const uint8_t button_height = 13; const uint8_t vertical_offset = 3; const uint8_t horizontal_offset = 1; @@ -75,24 +75,24 @@ static void subghz_transmitter_button_right(Canvas* canvas, const char* str) { canvas_invert_color(canvas); } -void subghz_transmitter_draw(Canvas* canvas, SubghzTransmitterModel* model) { +void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); elements_multiline_text(canvas, 0, 8, string_get_cstr(model->key_str)); canvas_draw_str(canvas, 78, 8, string_get_cstr(model->frequency_str)); canvas_draw_str(canvas, 113, 8, string_get_cstr(model->preset_str)); - if(model->show_button) subghz_transmitter_button_right(canvas, "Send"); + if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); } -bool subghz_transmitter_input(InputEvent* event, void* context) { +bool subghz_view_transmitter_input(InputEvent* event, void* context) { furi_assert(context); - SubghzTransmitter* subghz_transmitter = context; + SubGhzViewTransmitter* subghz_transmitter = context; bool can_be_sent = false; if(event->key == InputKeyBack && event->type == InputTypeShort) { with_view_model( - subghz_transmitter->view, (SubghzTransmitterModel * model) { + subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { string_reset(model->frequency_str); string_reset(model->preset_str); string_reset(model->key_str); @@ -103,7 +103,7 @@ bool subghz_transmitter_input(InputEvent* event, void* context) { } with_view_model( - subghz_transmitter->view, (SubghzTransmitterModel * model) { + subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { if(model->show_button) { can_be_sent = true; } @@ -112,42 +112,41 @@ bool subghz_transmitter_input(InputEvent* event, void* context) { if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) { subghz_transmitter->callback( - SubghzCustomEventViewTransmitterSendStart, subghz_transmitter->context); + SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; } else if(can_be_sent && event->key == InputKeyOk && event->type == InputTypeRelease) { subghz_transmitter->callback( - SubghzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); return true; } return true; } -void subghz_transmitter_enter(void* context) { +void subghz_view_transmitter_enter(void* context) { furi_assert(context); - // SubghzTransmitter* subghz_transmitter = context; } -void subghz_transmitter_exit(void* context) { +void subghz_view_transmitter_exit(void* context) { furi_assert(context); - // SubghzTransmitter* subghz_transmitter = context; } -SubghzTransmitter* subghz_transmitter_alloc() { - SubghzTransmitter* subghz_transmitter = malloc(sizeof(SubghzTransmitter)); +SubGhzViewTransmitter* subghz_view_transmitter_alloc() { + SubGhzViewTransmitter* subghz_transmitter = malloc(sizeof(SubGhzViewTransmitter)); // View allocation and configuration subghz_transmitter->view = view_alloc(); view_allocate_model( - subghz_transmitter->view, ViewModelTypeLocking, sizeof(SubghzTransmitterModel)); + subghz_transmitter->view, ViewModelTypeLocking, sizeof(SubGhzViewTransmitterModel)); view_set_context(subghz_transmitter->view, subghz_transmitter); - view_set_draw_callback(subghz_transmitter->view, (ViewDrawCallback)subghz_transmitter_draw); - view_set_input_callback(subghz_transmitter->view, subghz_transmitter_input); - view_set_enter_callback(subghz_transmitter->view, subghz_transmitter_enter); - view_set_exit_callback(subghz_transmitter->view, subghz_transmitter_exit); + view_set_draw_callback( + subghz_transmitter->view, (ViewDrawCallback)subghz_view_transmitter_draw); + view_set_input_callback(subghz_transmitter->view, subghz_view_transmitter_input); + view_set_enter_callback(subghz_transmitter->view, subghz_view_transmitter_enter); + view_set_exit_callback(subghz_transmitter->view, subghz_view_transmitter_exit); with_view_model( - subghz_transmitter->view, (SubghzTransmitterModel * model) { + subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { string_init(model->frequency_str); string_init(model->preset_str); string_init(model->key_str); @@ -156,11 +155,11 @@ SubghzTransmitter* subghz_transmitter_alloc() { return subghz_transmitter; } -void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter) { +void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter) { furi_assert(subghz_transmitter); with_view_model( - subghz_transmitter->view, (SubghzTransmitterModel * model) { + subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { string_clear(model->frequency_str); string_clear(model->preset_str); string_clear(model->key_str); @@ -170,7 +169,7 @@ void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter) { free(subghz_transmitter); } -View* subghz_transmitter_get_view(SubghzTransmitter* subghz_transmitter) { +View* subghz_view_transmitter_get_view(SubGhzViewTransmitter* subghz_transmitter) { furi_assert(subghz_transmitter); return subghz_transmitter->view; } diff --git a/applications/subghz/views/transmitter.h b/applications/subghz/views/transmitter.h new file mode 100644 index 00000000..64bcbd1a --- /dev/null +++ b/applications/subghz/views/transmitter.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "../helpers/subghz_custom_event.h" + +typedef struct SubGhzViewTransmitter SubGhzViewTransmitter; + +typedef void (*SubGhzViewTransmitterCallback)(SubGhzCustomEvent event, void* context); + +void subghz_view_transmitter_set_callback( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzViewTransmitterCallback callback, + void* context); + +SubGhzViewTransmitter* subghz_view_transmitter_alloc(); + +void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter); + +View* subghz_view_transmitter_get_view(SubGhzViewTransmitter* subghz_transmitter); + +void subghz_view_transmitter_add_data_to_show( + SubGhzViewTransmitter* subghz_transmitter, + const char* key_str, + const char* frequency_str, + const char* preset_str, + uint8_t show_button); diff --git a/assets/resources/subghz/assets/nice_flor_s_rx b/assets/resources/subghz/assets/nice_flor_s_rx index e63e47b0..e69de29b 100644 --- a/assets/resources/subghz/assets/nice_flor_s_rx +++ b/assets/resources/subghz/assets/nice_flor_s_rx @@ -1,6 +0,0 @@ -Filetype: Flipper SubGhz Keystore RAW File -Version: 0 -Encryption: 1 -IV: 47 69 6D 6D 65 20 74 68 65 63 6F 6F 6B 69 65 73 -Encrypt_data: RAW -7AE3DA5A4EBC89793AEFA8357F83A8577E08EBFE9312BAB9DDA0AF36A85569296DA4AA955D97B2489F84A4AA075ABEFF2A94A5AA315D15778FC003A224096B58AF8FC76FA5B4AFAD1EFA5484CA13835045FE9C1AA0678E690FC61806D731E968637CB7FF2C4D643971B06676B3C9E6CD0D49C4CCB6E4E61BFA1D314297BB8517D8773E0EECF090933AD78DB444BB210388DEEB464034D21DA08248C79A2E39B4472FCE441FB257B574CAEE240E3345F6E11FD4BCD91FD8D8E173DD2E60E545A9991659D551D95FE027561AA5DDE8B3F114DDB00F2D1B97257851D43CE6C2174A11CFF15146F30F82F6DD3E40379B7F33852C78206A521A209B5211471E75A0CA7B49765EDB88F18305D5F511ADFD3471811E71C8681F90C7DF3FD0C787A8226828D54C6F64895DD6E777A7B904406BBB80A05DEA9C7CF0D491F033446808CE4214F4B31C03CC6B7DD6ADF02CD0F9BC1690C9B098720DF786186C9EDE8AE318D2EDD0D2BBFDE2CBEC9FD65C612CAFEEE97CD137AF3B5074FF0B0D134A8A14EE54FCDE569203E3936D7B51B0580FF041676AAB46FBF7F15462BB5DC2ECC323D2E781053DFADC59174784BCD5D34A56E92A8A5C1F2A849FB620024EC0434FCE1D465AFE92440004ED48CD2FF97041803C5E077EAB823BA0A98A3853CD1C6C7AC33A502D69B5B7119EB4AA486B6BEB30470730F8D6B058527FE4DA45287A8E6F076D6AA32DBCE9E127D8E32269ABA1B26E70DF64F3B34E64C92C09E62EB25AB92843D0230F95F9703CA2CFD3E2B3CAAB2E77467D594E9BBCB1CE0E2FE5575AD98E75AA3AE58145F14A90B99B6B70BBAF46162A6774C550722B2A08B8007F6DB4EBE103984996EFC6534AADD0C354881FEB0DD06BEE6547175D4BDE128DFDD2FFFDEF3143F35F282F2B05B3763ABFA2B6E40C109764084DA53469FED9E7354C4DD87090205C4684209755BCCDC75F492D31C47748A18CF9EE60D1A7989918C3095090280283C414BEA5D06D08D25C3B3DCFF283C2936F81DA639BF314D8B0018D62C7AC35E6DA8379754965A72AC81A46AACE8B1C8BF44875EBDB325241760E510AFC5C56860547678F215DC082A099B7202ABE4F2D1F7D78CE89C0463EE4BD6F08BBD79AA1D06B7F76CCFA536E2D0BCD864ADA11F2923A194D679AC5C9B6B4A545EBF352DE4AB9F4758C22687FCA49BE8DAB772010530B389AAAED883D15A8B639930C3B667CF20098ECF8AC695520AA29A2484CEE93C8357B062CE1732A63BF47774397C52503E4B93E33BB2B77F45366A9FD2D9B6F0AD63912BC4881F7D6209B4F0AF10D65293B8CDCCA4FF7DC50F4589D1EB3146D239F4497141C711EAD7CF5DC276864743853E0D54CB535AB26C90819B49B500D04D64E9741527F3CD09D4E5B8E0AFB9F8A92772378D0C16D2E904F2912C3E5C06E8AA73139314595CFB9B331370ED015543F3E2855C8539CF938EA7C68DBE2C8DC3E0DF0CAA6396422A0179FA88F6872048A98BBAB3961A5039162AE3B84BF7316C72F1572FE09951EA35560F7BA164BC43CACF385D58DBAA240E9D260CC167EDE2821A581B6D699B0C6CB54BD69FC6EF2519811B50E01C33CF58FC2D625B8960E5E4A9C8FF7D306EE887F18A8EEE95D94B839BFADE4781860EA8A6A34ACF77BF12EF31993109CF25490D84B0488F35289A253C1EAE00DCB6F006223E4CE22389C6EB0FE540542E57A53E6EB6572CA93FD6BDA47D1CED573D2E2BB58BA2E04FD0D4B1BBC82A2E5CE309039968B3FA1492DBFEA86AAA64C7B3109A8B3BB7124A9C695C797067A78876157AC1AAF27632D036E7D65DE1F6C3B51D9DF7DEB21FD40DE6BA8CEFCC1B082E7FE829451E435F867173944F90D08C0B3B2DD59EDF530F3403B8B676A1738F1DCD0F3DDDEBE0E9E9057ADD974FF293130F0BD896991473E181C63311C6CDDCE75031FC9295BFB5781D28D22A0A17F3BB5A6512E6FB677539DE689E6B709B11470E77616FF61495B67A9BB7B4AE47A610B790DB276F69458D442AD6EA43F2F04819DEFF8B24E1488928F668EBA541E91A02701E375D757708D0E35ACD33C3B89A4D776EDB3962194AB3E2A6521A9A8EFAA7783083FC83BEB586A073A3CE424790B812EB6AC5E0B2E2B153C34352F41A89078BC7E9B976C2520F23E6398EFABB2C13A24113482E178A9BE1CDFD85D3CBC535AAC99D03B9ED324CD9FE5A4922061DCA2257DBABAC80AF9178163661FD11F1BA1D37FDEE95453F3C283B8EAFA22ABC72EE5EBAD0769B31B0FF7234147CDE12AE39DDD203EF172FA9983CDEF145FEFAA90AAB3F1E2F3233D54686A47DCEF2FF1EE98C5E261AB84E3BB9A16026B456B68A1097488A3C929BF99F68B93AEC2479E7C2EEF700AB484811568E8BC5ABA36873D90D9349A613608D028E82DAD79522FB2F60533607F668754F6CE08099173C9BE045BD46DC6D0AAFCEE48C902134579CB12E136FE6136981DD9200F418DF72169291645B5CC03B3C581840F5252F884A6C089BAA9228EA070F94DD9BA6606B1641BD5B9621945A5F9C39C2C107B2FEA1CD1796FB256A5CCAA3A8E86064FA7E9F56C32670F137AE73461A45E4911E17FFFF4C1B4E78043C31B9BA9C69CC8AF54D7F7076C41D0826066B075BA6EFC141520C00AE7F05743DBCB9A9319466641660BAC7E0C591DA133E5A4C778D4169E50A6757DC2B69168698F9A1824DA571C71E28A4141B6B664222FE3D88FF16487A18C3D738AF0DBDDDF66DB9F0A55F55A067D93F4C8C5BFC78E47ED5290BEDA49BFF40E0517AA4334CE36011A22118073E61F94AE34FFB9355625510DE74A2223F0B75CA9236BB514582A00E3A1F00B201CA0107C0D39C91B7AD507FEDDB346F815EEFAC4C807DD0D5DBF11953183BDC8BB77AEF2F580BE1B4C9124938981426B36EF811B21398C34158EA6C7D528BA113DA970818DAB0A0D0187D07A407C5A5D121637896EAB8F14787368C1A4A5FAD2167F804600805D6C8AAC502A1813258B296F508381A18DB2962C8CF8DD32A5E7859A27ED57C2F816C48F944A748B8EA19DC83D467B1A22736F3E1BA2C979371861B945075F464973E50F7AE892411A4C1D2104E3A71F3E9F180B89AA2597F0DE3FE00815DAE9450F385F7C4D3829D85BCC3D71F7CB77D1E0D2DBD69DEEBC92160F67AC82DF50B4DE7D8C7D5E09A01CFB331847951C3F999B8DC667AE358A5DFFE1EE21B42AF0450305FB8F9B2E16E82E169F61E1EBF70262DBD78D1FCD01244B704D65F340124464E51EF8613A6B2FEBA5CF468EDABB72FC652EA9427E849440284CD5885D52387B2829EA7FB073D44AAF4BCF4FBD0E2612195FA4DCF7F73AFFE7743869542108B10E64869B3F486398BB58D5392C74E11FEAB3DD64A7A0FB68ECB628847F26370D49EFAD48D1DA35E2823BA757DF7D165EFE6F883BAFA9244FB0ACD3EC9802FC75841F1A18374644A948ED604AA368829F8A36BCA83AE290211D450CA2D3AE8B41D7EB928666E19C1E7F9EB6D4535A2CE430D40F7F8EF0AF1D9017405BC923AF2CE8E128DB74C9A58458BA706FF4B2FA8BC5E472C4C41DA8393A982AF025AE6F95872042636F540F564BD8831AC4B4F71242F3ED30EBD90ABBC69373A23BE8B6B652D02E2D626269790D95BBCADDBCAB98C1468F441DF98BEA46186B427EF50BFB13CA6491778E1F9DDBB561172D109A6EF7D6D4BFC3DB86EDDCEAEE5DDA7D3EBCD4FAA40B35F5E28CE082576168ECB994879F0F6BB196AE3F9DA56EF04BC6EA659409DFF99045966FCEBE683830C12B8ADF76D55526FC748D8F572686110646EFDBC0217D3E530F56C192D668F2592EC9B28579BC18A7678A7AB28BCEEAB2ECD54FFD6F90C48FE57C230501792065FC7F5CA0333F5BB2173A8BD3A37BF0D236B134F249B983907339F06C81CB8B64338E35429F02FE51DFAD1CE4EC360E855FEEE48A6A7FFA31F0C9B157486D4F313E724CA4B72A14D36CDF1E99CBD72F4153B7C38790E49DDADCD3667865F9DF28B45AB6E29A2C5FDB4A5FA041443C54BB8A0A49DCB8788BC352B59B616AC28E5EBF090B1C1527E2B2EE8026D391BD62C22DA8357A36AC1D95E09CC34C8DA923311D9AC70AF55310638406C072CAC80EFF82D305A91EE3CDE8E95C4B936B2A46E4ADF630B47E40C4FCCDCD54261152B9A7DB0E7444D234228117558968AECB5DD06F16A3C4D00EA52309B1F76789678961B9577CB7AAF81739C9E11F163BD0625568D0C482C64F8F90CA9A1FA9CF4E9010E843142B93650D72ADAF15E3EAB1C4DFE32EC4552112B0EF009D0E85A155BD0CD814388188CC96607379FF5ACA85DF3CB39A11A54A48D95DBA6CCDF145C99E2592E8E4F07EF1BA580654065B7FB0FD79F00A21A32E6E8DA1C475590E86D55F3995FF205DEA925DABE5E28D69411D5C153B3A6271DDAA29B6A9E77812A25FBE56333A28F10309A7F4091CAC0976FCC852BBE51AC30D1227800C8AE15F51CDFCA82488DCB9DA168D8D5868914DA71219A132E9F8FA8E9AB610AA57FB2D52BACA84752D9C856C2BDA2625F5F1FF57FC7341F3088AD4A681B22A50D2A20A5CA6C7471550F99F6A7024404ECECD9CB0FE293A4137A78EDA92C5A8B5F4A7B7643237354D151A0836B7CC5FE79EF5A89C8917BF82FE8AB68A9E74E148D096594ADFBBAE055D6B9CBB6D6BE0A2DDB45247F463AE1A00D247D95A6339B749F4DD4366728DAFD4DFD8DDCE66B79000CCDFB89F9BA9E0372DD81EB2F08B05CCEBC79197E1166C08E8B8F006665464AB7FCCDD35A5885F604D05988E2325D89FEDF7B4411AE4E25FB3F8CADEB82021F79C74FE859783FE5FC140A652420F63B0749240E5070783F1C67642F03989AD87A1465B1D2506DDB7717C619320692A71FEBC9F513622EEF0D9A566E8E0E5E017AEBD6DFE04D912F6707912071843D193BD8E38119181CC092E45D1B8DC7638C7347CD02F6F011F5D6A0CDD4B79C0BB06CE25101EF88E7BDD337FCFEAE0C7DCC15474CB3F000A701D7B78D7108CEB4C1739A0A102E278E0E652C31B605703B73C486BD22FBEB339BD94C758C1D4340A31CB4EA0A5919A10FE2E9103A4971B0A56ED280AE891D14A2E6B51B1D4BB69F0ACFE5EFC70CFD27BF9498901113DE780FE7510A178CA9FC067E5ED3BFBF1A049D3001CCBE64844E5AD043F31FF47F63477ADDABEECCF5ACC3C8316630346172006953A1018C3F8FF707DD39A2A9ED4D696360340C69C6C6FB746E4ADA1AD6373FF20BF000B4FC8E7AF8D75666A590E2C7A66CE6982E5600F5608817A7EAE2331882E9B474B26635784B94AB30252FB2210DD730C3103985B8EDB7D8D39B300C23D019244D962FB2DB46B2048599CF272F45464AF7A26DBC1B4086B6B535F7258F7D9122A593EF0FA781FEF1EAC215AAE3BFC71EA6B4CBD70B4F076AE63D6106D2EBB812E3187CFCD5C9F313F9867E93C7E1455903DB84EE62D489D1281EE7215584E371CC7387C9A41192178927F23597E113DE1D3243E91906681BF5F5AD1B4F36B06610BA17D260B4B0F621FC196CCD7C071088582E7C4DF0D5FCC0FAECF839302BC6F07990A5F504330D77C88B3A1A5CB17813A5088828D17BD91E6D787EC93795EC6E93F1D9F5065BA2271A014692A3A23CAA9A1B7E9DA0A4E881D83F1F06C3E202993D197EB5D10397B77915B7C93DFA289F0E53D6381164B368FAE599BFB212365C00C526F21C76C9F72823ACEB5C78A60754C533E65D4540F29D3F659FD0E43DD59B48CFCE64E5097C08888FBE61A9B0C282D0F4062DE068B9A998B4B8A7BC3635C812EDB569DBFC0D4FEE0EF400F07B6C1021A9DA7A56C9558AC88A675FA3BD2567C6940162955E5D51057973919E01EB9FA5B688A10578F93D07164FB74ED47A38C1179AF60BD0272C074C7718C3C3AB42E707A2EFDF3580109CB246A60F4E120F86C1AB2B2EDCA5D0278A0E4C2CD52F33A3DABBAF8AA81299ECD6CA46F8A60396D17F417D974B277952F95EA8CEBD3D9C57519B95D8C8EEFE9768D3F28E67085B9D447C2E2182F6AC9904ADD970FD387E662A216F8C79E24E9AF3283DBD36ED0363E864DC1BF18BBF40FA38234544C2AB58E32F5D98499F1564298F54C33BF0DCB06E8403F9B6E73DBAFEB10A63CD3BB713C694AF012A28353675C07838103900EC879BEF17EEAC3812EDB26432C71E9F102D83F3ABACDF651923403B0710AA65CF1FB5ED6E8E67464F8307CB8ECBFCCB9EC363D5E76321786027888690A96F44AAF6E1C9D2806B523197523B38818E73777A184375FCD7736E3BBF3549D2921FACA812815E9396AF05F83E1BD7CF2449094253DD0569196E1C18F79036B7355537D3C892A9E926FDF7B19F74A1864F42D26669152874093C8C34390B90CF2C87FA14F7CC7E3E21A1316F024A4622CACE2C93ED20E82DDDC17D82D58B55CF18141B8A18B289ADCFF7D83C72A2073A0CA3919116A38C642DFB9585D85BCF27147B3560A3AD0AEE203CF0E55D59998EE2F212DBFE39158EC3D1372B39B50B9E5766667EED390C9F61CF6F422961F8FA0D39D10A204729F9ADC3B7B792EB7C3950F65C333326C743F264B90AC990217FD3DA95727593363AB0B90128F9FE09F444E3389419EAAFE5D04DFEAA89E7A5B83B832F82C914CC14C1D989CCB728853520270ED3332A9F8F0054CEE80743B98AE309143F5A1CBCFD38FA5889D18011AC05AEB01D82C1966751FAE6734D93FE684878210323ED4C8DE17B61DF69C2EB8655630B19A1F178A330418756E48EFB0FB6E50AF2C202ECDC943E789142A05C63942B79800592F77CC4C4E255BADFB0D330BAB9C96D3EA93204101617319DDA24A7C4BED5F3A427B2DBF2EEC25FBE23147ABCA885D7EC951D69A35B9EF871DD0ADC706FB2E73D90DF21DA05A4CEFBC9F16F4A2C73F151D9F04E45EAA11A1E53C8F8DF5DB4103E9CC99B9EE962E7CB7339082EDAA96FA2673599080FA9334DF3955E631E234603C2B63637E082DEDE1A1BF729302C675FC64A15BAA5DE0AC3CD978E868D495DB72BBF07E2BF99594FFB69D8E8566EAED512229E8059C3620AFD0A4E6C9C004EBE2C9E2502D2B1D9371BFFF9AFE966B153ABEBBCB4144F0C17CDD01AFC53784DE27AE1D51830B79283D1ABAC7A11F512AC56015A6E26B66D271FE912A51D9DCE4FD4B903923C9791895C87272875504875FB2732AF262A6EF7499DD5B9FCD259AF028CC2A5AEE5E263DBA66D348654F5CB9C831F5C09DF42BCE994123E7440FCF2D6DEC509573474B424E91ACE7528470F2FF540271D98D8B8ECA22E7E78A18D57CC766C66D17F77D614C69807606DF06CF489EE3A9EBDD5D4D86C40B76136CDE362304F0814EDB84BFF1F046AD1CEEB7D81923B52A1B7EB759331B09EE1B9860D951FD25C33C88B3FB57C90CE131F27C648A54BCD5208BEA37A25A7BFBC59C4F26EB3975F776EB6428B575CB3EF4EB41F652CC6B3458949C486AB1879C0BF67400232DD9D8A2239C1DF3F895427D827E4F5CBEA8759059D32A4CC993B294649E830DA7F0F760894CD696E2DCD2A72582A5B01768F823C6668666F179007CA11B6E6D5029113413280BE26EBB3F9017A04F2F6EA959AEC1F3A58215464CDDEC51A2C46BAD4FFDF9AB61A58ACC2269D530ECF26243F1103DC40B5FC2F494DE7F1C96A058CA7E819866BFAA89F38BC11DC7995023153EB8144D24A62DA2A54E4090E44FCDCF10FDEBD38E04E211436F0DB22CB3DD532D885ACEBED368B141195D0B34CDA4A139BB26EACDD7E7AF6CC9B55B26EC3680DDD0C325EAB94183387ADA490099D6FA038B364EBD7E3375AE10F36BC99853450207D3416B5AC2313FE0CEBD9A8383D7707991FCD1BE08A9062225036053F9301C5C74650023D6422A5F665C3EA35F8CA67B7487AFFFF2FFECAF3E91AC567D398807F6DA9AC283FBA430171292E958D445CE136FD37B367076806C5B92306165D1B364BBE8DEB1DC75B1563693FF8223622DCBA20B0271996B81317C3070804D23229D9186D61A91D55864AB4D2CD60F7F6DCE1843567172B0BD4DD038A875B99616EA79A5788F371F408BF9DE371D929D16DF0D70FC4893E31D7D4BA86ED5EF897302B7680C2FF8746497A2E4C6C7E1448B1034475E7A43EFE7FD5F9CA16D14E0CD867FEB4DEA7ABAD9E59B2ABDB13158376533CE68F549D4572241CCAEC234887A56517623264F4C0012876EC3F0A7B9F59F3197535A84D608A831ACED8E91F722D18BA9AFA8C4BA70B1E2760C4FFE04D38C9FC6B77CA811E8775D253002E2655BBE162FA9E401CF28BC58017B8993F2C3DD8A57A27CCAFD45C4FDF11EDDFF5B2181817105C7EF4A4A889D2B48B18E0E0910D198737A1400E289F33BF0935D6230C948281B603726A42CE7304181E6C7A3741800680F600D5FDDB6B92D42A273746EABF57E008FD55ACD2F6512CA96117EA583F5D35FFD9E7771C7D1734A684C5D2C3209205C51A6BEA38829A2DA579F9AD4EFCD86534209AC50A0D7C0F85FA93475989ED4CC090FD1DBD00603CCA0F946912A4F773840AA2A78D765FC353A6D1BF5EF11F5F3A6AA2EEFD472FAA20FA33D409E8457ADD5DBD6F24271651719DBF84B2BF8CAC864AF326FBACEA138391FD57B49470837F0932AF6786EF082C9EB202FE7F58D8AB5A6D6C92546D859022B4BDD96D4260AEC0DF1B444A0868F3D5DBA4B5C4B94E34FB3E3E961B0A3E31BF4483D9356B2057E1D6A589A29CCC845BD9E57228EFBCB5D3FB075E5E461D23A73040B7F1BCB356478C53BD54B7354577611916DD0B2DD66450DE53B31AAE9B3D95D75D716D40BBD39EA6B9363BFD3925ACE2F4C9429C7544CC5E7CE23D4CE7CBCB0979C941523311E1DD8D0C3110DB31AA789192FE81F0399527CBC899296A15FA3AB61CC5E4BEF8A02C6612EC405520C0D6740F386A9609F46FD3FCEC5BD7A6D7D415CE21B4EEA36B6604232193EF2D0EF2D9CFFA513FC736A4AD75138C7B514E75BF465584A9F941F39E89787DADF89B402B54E76636F6B469398A6BF21C16D6133BC657332E07F9B6250E8D4A4DA64F132BA807791A46BA11E9BF45C82F974BF4DA871E5ADB7DC34D76DF552831EC995AE0319CC166A1859569FFD87E94F15C66ED0CEA9DB8B451A7D54AD7A1A93D8F42250020CF4E0365AD3733CEB624F6281D182D3E9F4DEE8DF7867D1BDDE756EA75279EA36864F0A442E4CF4063118F524535697F685EDCB17CA294057F9B8BB9443C017230F720302FAF52F72957D4D479BE48D4DC87B9E7F6E2CBEF2555F1E61456781E82744D70CF6DD3897115CCB206232B09349395CAF5BF3600F497FDA857B7ECD122355FB8A5805B452C1D4A5AB01B0609B26C14BFEBD14BCED9F20E618C4299AC5268CC6C8327FDDF02793D060A43505187B10519DCB1CFA05CECCE4CBDAA521C94B5610B9E03FDC3993FC4DC08F11514BEA5D207E2F4D8C57FE671CB40D79FD7205FCD10853E2C4312C4F66AD3F320CAEC301C26E4211277E4FCE0E7C28543D2930AAF7D0AD3B6FB923EC8D8E17734A364C61051448AB12811D34AE982264FC41ED52CDE2662E727EFD77A1CEDC07EA738B9430ED87B19C5A0BEDD3AE992AAF5D70FB9FAF44310C36B1448DC7E38BA87B9A4DC4E7A3A382EED2FFA19B517064DD85A92C7421A62A26190BBB2CF980BAB71FBB1903B904964010A2B70950C7986FED694B01C75DA1A63F4E30692D2A926A0CC19BF954F9792607FE01BEE9522D7DCC8C5FD199FE4F3C33E6D81510B578185EFF75F3D27195C2167AD75A31F12B17F352760514A4827CC5F2AF216054F2CC859C0E8AF2BAB883A49383AAEC370A995FA678AE617D28E66AA482C48C365EF81BB002A0F6EFCF9EBC24BFE9732503F41C18F582C3CAB432216C9D1C12D0F5DF3BD66AF94AAA46C2AA12572FE1599DDD6573DC60E6EE1FAECBF4674B3CB9F1AF1ECFC5F97826EDF4F041E3EF2DDBCCAFCB1310056BDE7C3913979A9A3FE71DD1816D8E1A6EBCAD6498A83C45AAB6196E325ED612C5A686E9051F16CC05BBDD8C0EF5817A1DB22B16EC64726A009474ECDC4F1BBA6F91EDDE117E3BFAC5D7A1ABC70F30CDF25893998AC579CEE2AC652EE97F1D89B021C6714C3F0D712210EB2A1A3C32ED424EDBE3DE6FB2EF3E1FE018A234FEAAF1BEF85FA2112821DDA3D5CA7C010A5BAE6721F7D77D9C96EBE4A09CF6AEB05EBF5637465A5A82BA84EAD67B185F4F1941F123B52C814B8512405912E7269381C005159A4B94C17131C2E782C76183113C8AA5EBFA5A8A861B9B1D30E838DDCD42619A7726624040F3D86A48E1C5F81921D3F17E05643CA01F44E34B0DA33A1F1EB38C15C88B1DBF73304EE2E7ABA959AA35EBC23211ACFC0BB45343B3135671615ED1BC5C3731208DEED4BCB74A5F33EED3C19C0CACA7D44E1EF76861F84A265A4B5C3CB970FB9BB3E4CA2E5DF7396CF0F6E18BFB54D79F718CCD7C841D953224660906FF1B94E102A5B7C3E3F14A6A7FBA5BC14196ECD0AF8BA32B736B3AF63FEEC62DFFB3E3D8AE1ADC30600FDEEAD752197D857356FB4BCBC77A54C515299E801517D1C5EB2C02131F8662311953BB54238329D32A43182372E3BBDF9AE69A79219165E5325819F65DF78FFE77C2C0787172E9CDA91CAA2B721E42E49AE8843722E2881C076CEA985835A347C522223463E0E07F27B5CC040C82A0BE76BFCE78DF1C6A98709C6D8B7338935A55EEA7AF9357CC6E34CEC4A74E06C4E677C5C27C7126B59A808BB5F9DDE67C18A913574786187BE4555EFF887E893A8765347A00C78533AA3921DA02EBAE088380E13102ED71628A14D1BC8C822F1E31906BC227BE636D327D1C9A8F9CC05FD26B282BE514064D27C13C160978A1752957329BF589B6D63565AF5B0BE594555E5609FC5D05FCA5A1F56C319DAF767B7E5D14D5386467F8B20702A64F6D19447E4A4DA52A0887D31FD4678380F7532B6ADF168C6CAA22050331982EE3E1134C7E8BBC6359D5E877DA1272EA26718A70FACE209EA412383E20EEB83AA3DCC3F0BDCF6A78528EA81938E967A6A526D5F138D639B91E53B8B74482A80381A87DC3E0AB19B155167F97DC18968CCE4F99FD5AE63E50DFAED851B7DB48479B091753535B84202E082BC5C72463493357AEFAEC35E164E82BFF534733CD7C924FEEA56A76A4FCD3C2DE3C055AF798685CFC8423470BB871A637B2A1B8501FE72B7D78C397B5E4A8AD197E2AECE1E5B14BB61DAD94C979CD46A8766E41CA02447BC2A7E48EF3ED68B19C3526C6E8BFC17F83668FB698430A74752A2AA29C83AE019A1145208DC0A3AF76FE964C16A3589009EA0C5E32EFF40BD3B8414D1685E20456023C16C8E6DAF4193F8CDB1DF5BF2BC95EF5C98552A18B2DC4FEA9A6C6B99C712A343573210E1B42C9019AF082F34971511BF0E7700BCAEEA3F7D24241A0B598B9B6703697980CD82F244211B58A3C616FFDEB8E8D946C8418B94AFE1F46CB950727F456749AD6982EBA93ADE229A81D01D31CE22725AA1565C608DF9C1ED1BAEB4F1FD1AA8625A921ED6C9DD019B6451433A4FD335841CD36882955642B3344F306E8473A6742C01362F41A7E1978E43FB5AE7529D05859CFCDA665F2B53BCB19CA277CFB8B78639A6D9848FD903D00667E7BBD537939FB908DA8E60331B6F283D9D7BD64908780CF74038837108DD4A9BF095718219BBDB22AA9F6211617031E169ADAB1B0B24A96C3D6C4C2CEEE273A36106F4F1E473CC552697C1B65BAFA5ABFD2C35A0B0D5C410A72BD6D9B177285C9703B235C5E8E9C63DA66B9CF5A543B1B463748D01E873CC3B6CF4A57A6CBAE554138FEE27F7B852F0E6275CD699AE4965F32ABBAE300FBF7591F52E96EF29589711685D71D5401EB0C60F66E6AECBB82ABA67B6063DC060BC4C05841BCA8ACF231AF0D242AE4EFAF0B5D32FABC10F6F33DF3B84F895940643240C80FC826344E93F29FEBB0B62B667A1B13D489E35AA9F1D741C3FCD509D11E0C3E33ABE100066690B2A62769C5C95A2FE1985AC5C7BD53F06716BB58823971118DA6BECD9B5B53471854215FE09CE7A4D9E39A588E32CB734A3E942318E7D35D67C103E311F11ADD4E276793C99ED05EBBC295FFC664C015F604B77DC51D2C64BAA9FF11E0291A4730F65EA1A29898A6585645109D8B6AA2214439ABB3012E5CC46645EE89D454460944FB0761C7E44230CF3BE6D7D61E948674189BE5C46D2E4FA2E4FF92F7A9F2000C9FFDE840B5C54472FEF1043701B05E67D8DF1D63AC5B0AC1F578728D4E788B2E6EBDADCD8DBFF6F084275AF8BBBDE9F80CE0C6C9077CB72577D8DCB3EAAD619AD93EF53837EDE645B192936AC6A4097DC390402B535134CD25D57182FF3572AD82EAB6E5C17DF6D22AB29D58CC6BA486F74ED0B401BB39BFF87B7234FF23BEF469210F3E07D720131A17AEEA05FE15FE4FFC0A4DAAD89A6910187B91BE0CEEC72B5AAD8E7013E6F3C80B4E24591A8A4C91F0784496F12E3F2FE26799244295F6F9BCAC65316B303B66E347BF73ADBCFDB340F63C8B89E0890A45BBF935FC2D11709C91C237C0F70652179AECC95EFC63E35673DC22B4BF64AD8658C9FB44F05F994719CAB283B3BE28010B74406B6042DB0E798F6949D7E657E1B29FE950131CE30517994C09881A6F298081C5C58CA514104595DACACB48B7BF17535B529B465190CA22081DF455E1442BD9B9AE61FBF7B4885BDACDBE4E6998AD6D316334BF405BA773553B69AE6A4F6DCE0CD14E1B07FCB4E6896A09177D97A92BDB7EFCD6CD08856C66A8194629FD03D9A3AD4234DBD9876864CEBDC749A8897F6106CE9849F1951FDFB751243CDCCD87B36F0BD4FFDF985348A976B6F52F03ADB3399282F4C87FE45226880BFF42ADCEAF677079D02E30575561830B3C8670B73B174FBACC0BE4FC174FD2D8EF6883D208FF925571777E1B5A976D3A55336E417F82090F01132C73F7BD6B2D30526244C790B5056B8E48ED08BA9EB604D034836D72D03CE069F75044AA2E20B547B84E5DD2231D44DAAEF82EB8BA1173BBB60D1827A30BC21E088ADFBBE1D0FC10E87C9D4CC4F3FC001CCC08172B1416A20437790EFED4ADBA1A8AB44A9245AEF7BDA762C77B44FC08BF9326B05ACA1D5AF69BF4BF2B955FA36CD15F2053A92801E0445CC0ED524F4EB57F55EA0902B418F24E58622430AF2E6C1DCC75CD1194ACF10F812B2F2B6CF772294E875B27E3573A362FFD392352CDEEB9777805A78927C9BCC97BDE175BF8C411F4734A84CB1EAB7509944ABF896BF080DAC1FB7BA824F2E230B958D935C6BC98A15CBD50F207A83D2181BD950AA005EB82D59283D1CC7DA77292B632A7654BAACD320E4C8A4B6C29CE0FDC5AF141A576920AB6A341186DD142E0C5431DDAED456F9519FD0F451386B5BCA830D1E588A436A713DB4CB71635AA4919D734F04A6719807D01BC591FF00733607E6160799C6406AC7E14AC78BC43B3822674D4944E095881810B637A1FC7335FEA7370DAFFC3B82F8AB6CCE399598F97718BC9C1D6408FBDC90DE30D1C61F45B8B0821C123B37D08334C5B607857197953B566F165E71B22AD9AD206D08564E2E5C60B64AB6619CA933A439BBB0F7C7A8982D776BE2A5391F91EA155F0A3198EADC1A68FB64E94F42F406DFE50C3AF4E8435A1C38578FFACABD56174CD1B35072482D728A69228EE6EF661F4DBE878B51C5BD9579DFA1D787DACB7A64E3ABB0015E46CDAC0885BA4A2B8F58CDF5555EE35AA51E5C6E4504D853905ABBDB0377DF380580B9B3FADEA6D592CF6D24240B29A70DBF5CDCB67F3EDE9C8B1324EAD0557B33485D64BA0A9755A64043156E39882B941CCE406D8977BB2052B4D6D3CBCDAFF6B1C2EC84AB80DBFE6F4C5A4ACA0C9D7D9C89C20C1EC6067AB08D34526A5FC73BA3A127A0DC4726E924E669EA67DA2023FE166540A6F9AEA9F569F136DCF69C5C44E0FDCFD87A08F69B3EC0365116C13AE5F704808BB637ACD9283C303E4D814C331FA6843787937FA49FCD1BA4D697E70FED790E8B2459C4259D94432BEBDD43F5508C072CB887F9BD7A6AED8A6066E18A85DDB7A9161F8F70887022CF1CBA653DF931D3BBD69D86DD846D9D140D97FF49F5B80D5F8CFE394D1E8A8DE1C05C0E2AB2DC043461A352F0DB1FF14DBA1BDBF5A9C6AADCB51127D7AA873C82BF01BADFEB360877689768CF9EBEC66F213E1F7A43C3809450E8854B30EFF5506809468E3543B029FF2A581A5950F6F360D1A82665BB8091369B9E2C5BE93A71DCB6995A527F47E52D1EA66F60297EF3B5D7B19F1E2C27FF2853D28CEF90A01D3242CE097D738E513CEC849B906AE1BEACDA221336C140255864E360E059E660212C7612F84AFC2A1CF44DD8C47AA8E60E083EFFA94731EED8C42AD9AD460530B8FE36C21355130B9D014150681BA2261EE4157208B982AAD7A36EF2643B5476DB89F1B83CFD65EDC7996740F2003C4D33153429A5C5E47AECC59268E45D44A2638269340126E11201D58BB7BCBC8F861CF7B237075DDD4682880CBE439200C8CADDB4F96BF4BDE6B844C0D28859666DAE0479B4CAB05D43E5C1B06FFB29351232B420655F80B38BBA2C555994C7B2A1D953178131AA6A9677B04350861F157016D771E582F8400F3793A38C8D4F7F610904E95CC331563323E2A501F93EA0902A11D806E4A74CCF4B9A98C533C170E36247B99DFF790648B29DF4799E86BFCDB68268BAAD292DA29BB4F8238C00FDC8E4A69A447BB82958E1DB3AAB8EC88D4BBF67762398D3D6579D7B2AACA4FEEB6BF56C4C9ED52E5B63A7EE8D25134209BAAB61D17AF0C1B0277C9FDF42EFF65D0FCFDBC2033B40D401C2D3697F1492F19249514B8049EC585BCBB30CA3AC6B8FA946D235271A7EF5687424F4286515A4E162681FDA9AD2CFE29E62539F514FBE001DA93436B823EE8CC28534DB9FC84A55DF11231449900A4B6ED9C0AEEFB545C4E0EE13022ED517CCA6AC23BBB5699E42B4CAC45085C067141BA066CAD4954C4C96FADA9A3FF0FF90767D4BFB58326ABCC3CE2316B1415E1AB663753D1709F92DF00E64B7CC20B0803E81569FF1B377A929ECE8B8C80E81FCC89CB435CFDB199227162CE307A5B148698D9BC0463D15DE7A668284BED8A719563B1BD2D3127F11BD72B173D5F5756B82F16337F5E34DA73236F6FCE9397B054AC51DCA8D9DB3AB9E44900FF6921873D937F8CAF90BC743DA5347218788887EBF90344E4B2B82A738BA937FFAADA49C4F963F2875984B3734A5D475DE56F22A1BE3E3DC097DFAE0B657561BB0ACCDB16C0A0F4E6C0C99DE6C21AFB6BDEFAD4E0950151BF32E3FCD758CC4ED94AD18988A9F02B90EF8BC7B8CE83115FB930312062349397E7DC4DDD9BA7304A442F10EA4AC000A0EE8F703175A6E7127F08289030930462FA723C53762F2A914A987A76916B399D42E7FE4568C51D230F539BF3574FE573E2C6A317FBC25219996092A2518CB25A7A9C0D7C4C39D73F7395823C24188919F919449A940CC37AB07CA6D1B5B6983D47872E665B1C1553E6709EA273ED109B4565EDFEF99AC1A618DD5D96CD4403BD6676CDA19BAD86342F7029375B5F772860ABAF5A00916D2DD5C967B457248A4B1E0357AAC592100468DA42AE53B3B1A9B6185AF1533A187A1EC66D5CCFAE033BA4C00B7257A5D5C6C22769C1F2B7CF4F8EA9E774DF6338C968CF7B3EB704041A61FCB08A29D2BC6F2DFA17D8D519D69CDA4C843873278655FBAD0916947878E00E85F5A78DC5C0CAC181208162605659BEEF616E9DA189FC1D7250D4E873093F6325062823D383A42397F87E3B52B9876331F4A4F88CB78DC003F29B823DC697682E69C4ED6CA41D45CBB929FC19481F4B80B0D4EC5D2BBC4FBF93515C97B01210A39E69E47F9CD9CCAA8419A7B62DA8F9C823966B9E5E6FD838C61E6DFC4E28ECB5EF646DB450A4F211349333A70575C4CC3F2B7BACC74D3D1475FBE2AF1E3C4E9B03328CEC1371A451F65499C8D2DABF86050543D379363335CBC6E7C5448D2D39B1EEC0302FD3B719E6AE95914284F43BB4B4BBB6467B0C26F7CF39369BDF79D9F14B919136EF8345A4225E5C7D3EC22B1B55232E1E983D8CECFFF420DEAD90D9381C2DE688D5C853BC02E49338CE8B40DCA9518CAAFA071C5A0C02E21F2818D660AF4863859BC32EE01E707DDA4BE9D6DCD3DC565D4ED09386BF9FF2168B89EFBD5BA1AD638CA0F13A3EF40AF2D4FBC7016174E6FC39D94D4AB9BFA109D764E9229E7CA26F09796A8512BAD353F1EF27F4AB3A982780A0CFBA4F307F9BEFA47249619A1CC2D7690BDF4315D236BFDFA08D493F24CB4C78BCAEA1D7654367D94866C0703715D3B1F427F4AD13F88B5EC34F9513D786850720A7CE4363FC31F2D5F7F76E707A0BC873A6314D44A8667AB7C9235A9F8819342621A974D156BD39BB54CD36822F616EAD364F05952154DCE6C2938AED116F198BDC207770576845DD3879EDBD579CE6E04864465F282AB8C2492A6BE15C740690A170EE736D8CDD3464DDD8E55E995A77C90ED2252AAB9AFC9A7B418D986AEE4933DD29DA7938009B4E9C402B657B0495512FEA68D3F1B0911CBB9E2259669E647623CE7A13D02D144E8E8AA28396C467DB6020ECD6C5806E196329CEF6D14F52D6BB5AAA04A6E982C80072430F6C9267CA42E54DE5BFD573D2AEBD0DF65EE07CFE80A2AB8BE771D58EA2FE6B6193E8FB5E40811528BB5E7FFD095AB0A860E61C1C5DD3CA4839CF29F3E60472EB197FEEBF43851F42553CA87F24DC342164329E7A4AD5EB11D5BD2E9801C0A8C5C4B46315D84A4D813E9B5602D19FAF7715092C7F148DB6C12A6A7CFA9C305665F7AA0A05B0C944CDF12D1DA0353FFC76108DB2E2D38B50835C4DA938348708E2283D86D9FDDDED96B89DA04367662726CB58C0E67FC45A594A5041035CA7EDFC24CCCC2CDABC8492D48D8F735CFA198C07229AC2FD8DC6104DB982C3330C8A9DAB224FABD8AD798052A903A1AF6E362402EACD49E1104B903FEF4C8C492E1FCBFD03AFB462C9AE8293D50AE7B36F08E974B91BA1B9FEC9B427A20CDA21332091B1EAEE6614E88E240F6697AEB9EE0AE6CCA1EE902B446DC4D61D2CFF21D7C69BEB248CBD33ADEE0B6E78C2D230CFA28941D1BDEDD7CC6B1F813287F2A0C006C59FBFE8A4FD256250B558AF31CB0FE587B92441BB25100CFF11CE9EE0073280EA6DD1EE63897FDA4829E6202735B6071F2711E1994E8D040E5A9D3D337AEC3DBB6EC3CDCE22216BA137F6FD56CE0DA2EAEAC2907646D1DCFD7AE95D8CD879ABBFD23A1F1942B0F92548ACB1FF6FC15AAAA5CF36CB8D6A7FE505D7AC2E178CA29396CCDF5ECAA2E9FF9AFFDD2CD470B3671840553DE151768D9DDA605E4E81EB72FB155C8BFBEDA1A026C4CBA7F74616AB34E4B385B5921511B0A18B736C3AA05BDEB5FDE0BB84D061B0D039402DAAEA2B5F315E5FC8F4D10F8F2F3C11E7F5D672046A56FCB113761139240D64A8B3E0ADC83793B306F778988B3224A2492AB2764B74140BCD0F78C8EED86118B3C11B89CEAD23C4AEE21D62E4CB1BD03E0B8697A5EA9031FCB2F1036ED4EC61E224B43E1A4EC7AB71E6C4951D90A14F352204DA0043DB745FC8CBD34BF892AB2AE6380AE7086F2904525D70493C317529D206816D61ABE59F240366FD86DAF23E40EE8749A84FCDCB58F3A23251D09397160DA491768698B19DAFB78AAE1C446DF78AAB7A90E3310DE69045CE5DF8429F052D348ACD185B6ED93EFEAE68A5A2F0CC2E537DFABD033F157BA74DB1E2BAF2509128520037BE694E1DA590A160ECA3CC6B5B96F7723A344F758B4856C44B46BD15B8414D4DF40880B55C8AED8DB07067465E86F95790C091F1A6DE411D602AC28BF9FEB96258634C6568B1AAFED19574C535CD7A16A69876EDBFB0AE06DEB4426B0C40650A374E272E82BE33198EF95B5582353BE8431701D3B382FA9590C650718C9D415E92C555B9708A52DBF0AE414911C8C6E10624E55C531BBCB6B8B1CD1BD7FB7B567088EF5AB0B430F36157A1AD3E5AA0044723AC4D7E575BFAF417882FC7276FC3B082AD00BF14A7884A6D45651BE8CB8DFFEECA0C7CD13602CBBBA545E2F6E3A895B005FDDF58F31A6A46C016CDA306D8C3E8403BF6AE608A3AE56C368E9CD8B99E824D1822C7DB086D71BC610B9FBC9FB5F75546DA16C4E90DBAF13EE7FABCD775864F90E1F04B75B1A7AE62E6A0AEADBC66D0BBAB605F162448B3A70D512B235D639BC19770A6E407346E6871E62BA586DE60ADD6DAE6FB2A2BB510D833B6E7DB77E86CCC31EBECB32072A96EEE867A1B8B391D88E7E55A52C5B60558F0D075B11995241571B77EBECB2C66AF66B0F9C207B8A90F23FB01D3A5DABCDB3BD295836F97DAB2AD2DA8C372027440EB40CD24C683AB95C50CB452FE5B2755E614896FAD8BFDF1DFBCF5BEAB745DF61C21FD69A8BA8D54EA600B1954DC7341DF64809EEC0D2CB1FA7EB68EEB3A44B9320CFEEBC7E4A63C1CB96F33CD898FD410ED179D202470A867453F4BA9B655207520C4E2F75240B2AD22B82165F73C5D9BB098F9F8197337691641AA42AD5C17B6691DD637E77F8297BB3A50672FAC99607D05DFF6307A2271605AB4740A2A22B7EEC1A375EE0ACAA28061B08880E6957E2689D4EA16260528644CE17BBD13FF6E6B1D4CC02612105E9633CAFC2A05C366C1F44F564863E4773983C993BD8F6644C897B54935CC06A699D8579C9FF31AFBAE1A35E6698DB1FD59F60F457FCAEF3038CC4F1862AEF538EA41EBA4BEED9FC2D1D23DFDEB1ABDB44530A120A9D9F0C79D4E039C39DA04F36DF3B634B0C9547B55561D62FF039FCF31AE5088ED61B2412D80155EEE779A369A91E24CAAEE50638A2AB8607093D20C8CD705A97E3C0BB0B1DB01EE5C33A501B89826072B6BDF5AE9BE52AABA6B44FFF673F747F4D929D6580D4F9E4BDFCBF3FAE406B05A99D3D77F653C671E8B64174B8AFD53174EDF0C2E3B37CC7102441DF770076AED0929573308F8E03FD4AD8726FE3CA6AF653105C0B8F3C31DD1FCAAB2F437D35E4B75D16C9173A4A00D46DC2FC2E904D82E7D102DFC07E882C9805B7346590D9004908394E8CBFAB5F3CC1780113203E8F3ED33BBCF60D46F673955931F4D29F7576D6C84E36D4E56BC3390736D3B067CD2D840FA7D07EA8918D58831EFA8FD4A014994619B7319B291705681135368103BE3C05D572C6683C0C085EEDE98110FD5FBDD7FCAC46D296B059FEAC9F7AA7AB6AB7C31C965553370DA955D6A1D5A3A233931AB3F11EAE83483BB5BA96522ABA5801B8FD7CE4B35FF63A9083CAE094D0255A8B40CF19FE5F77E4044397C22BE7AA10DE601D5F326F470434DA9296C15A647632B41D829A50830B1EFDB755E0052AD79A6F122A38E6EB148C3D1221B491514554C537B7DDA5A7B7DEC9A23A70AA06DACDCA6633418B83913FBAD809B064432B683FD05153ED878637AA74C118DED9969612E8F42A12E4C8FE3484D7724A069092A2E90E30DE75E1E90620D389946A145BA8563C5AEC27CD1AD29C7C0040DD037717A1E4119D430699BF318A19DE4246A9F62502BB04C78B2533F226E08712A99307A50B0380F1075F4A0E4B28C7EBCDBA7BE154357ECDBAC9F5E375EC8D816FEAEFCB6584AFAF35FA1C8D0C13E6FC6788C81282E7D9FF322FE380826C8F90065D0F7B7BBECAA8DCFFEC30014143F645281825915C35CEE858E4D1100BE84B829B9D6386ECDBA5B197DB4BDF66834AA6C7EC5535ADB8C52D95532F982A17033CCB51957E82C1B2F9342E8AA418B483C48BE6C2296D468EE0964B75D483D942E32F743C5CFD1C4D8B1454A98A2BA70F6E67FE3117E0C81940A2125EEB0F4F89C92B70FD93707195D5A669DF2CF3B5BEFB3CFCB6511EBDD957170CD32C7F5C4C60CB3B63C697654A4BF6E0F8A93DF789A1311660B52D5EB6F163AC250CBD905A478D22F3DF7E82FD2810A07AD2F9092A0E362B35DFB9C09AC6749F87E995110F8F32F800BD30BF68E5C44F78D1E479A2A242A52513A2BD7722973784004CD32533E47340D69E7E2881808A3C019CCDDC1ED1E694D6477691A9052BC59DA79324E456411304B3F3C47E2FA7BF76DBA691E5C01C5EDD3CC7AD8B118078CA4D789891C9C1BABF1B8047D4CC68986B75CB01788848FA1E3D5A5521AE44F39F015F2B1E2226383102803155E6159479FB51875B809809D399B4482C29AD3CA8EFE0D8F3F236C2D7353D3832F00C23990BADDF1C0AF89E9841CD1D3ED5A45A99EB0ED970AABD5A4545A4F1E593ECE3D2C12348291748CF0C7576BF6D0EE530473CF46F4A43854A791FA59CA195F9D11F4F962AA7BC86C37D1C1CA524EA8C5C9C82757346CA4F61AA17064C327DC37CB025BB59234A3B2C7D3DBE71D4B27783E733A9576701E47864311A207EA8924DA15206D9C4A6D06BB762F0065BB8E98D4F7AF0E162AC9B7B08ADB314E2B68D4555653E3557B9BA8F14F2BC314A395F3DA7EF38DE9245C8B98FD22F2918120A7C634D52159A670FFBDDD03142734AB61BEC165566DA67F5F0E0490977868330A5FAC65A78CF7B0BB2DEAE5F2386F90507A3E1BE10DDEAFFFC16A4484838ABED210B1832E93108BA63BBCB690E4BDAC093C92D5CA3D4CE791359C7214B33CCF5F2E47B9F61A732E6BF819F66B5B81DCB647E0E6D47EC0098AD831EC83081C2BA29CA60E03BD26D4E04BB58CA4C36BA4AA9B048B8F6F9C7D6AE980C15D85F78848058CB1D2680598B50DAAE8A47932113BA3F4F8480373864C0475EDA357F7CA005C53154056FE989605F7565DD7049FC090620090282C17B8349D18B8F1B0BBA96182DF36BC4DC381BDA681AA569EBCCF2A2FB6872EF5A58683E4BE6E487854CA7387F0D8DD358E5129AF174FDD19C5E0348743A8CE4911F63E78D58E0CCA713443BA80F793CCEF49FEE424CE309E60F681F7C3E1402E762E6EC15260EEB6CD362F1B16B4CB3D1189EB4AB6220B4623AA02B80BB3BF2BFB64E9AF881B5170B841B1AF36B68BEAB3F86780BF1745715EC0C8B2F0677702A44C7CF25CA15F310BFE8F122889633BBC43A92169B3E5DE969A19630B48F2707010EC0BBA15D65B69585FBF91C4F1A17552BA6F84359002486BBB75A5F3C5B235E51C49804C2F89683515AFFC6FC454D7AE9BB045C263F8F27FA60BD21BED9DDB9612FD22A07FBB694EC444ED5F66167B1EEBD82E28CF06C02535FEEE2870FFB4A4099017735026861CE229558EBD103E836ABD5BA9BDF51E050A4BBA60A2E2E7F7E8FC99B120947327BDCD3924E5FEEC5A0CE1FB757DCD4A75B41422BAFBE55A0DD1AC033651C1FF20D5EAF78575A5DE3D3B693D33627716F07AE901D644B61DF6C2568829CB622AECE310ACAFA6AE2E1CAABD3FA918B14446D5EF82307F317CEB1ADEC56647451A24BAE277F947D964371B0D83B5A4F634CCB859BC71A1539D0323C029BE26A4A00789EC51FD185244B371D9FB655D57DF603367B5680E57217AEA791DD1A82C6B7118E9E5D418CF6448412E13504E60197F5D5FE159488FA8DF8B8AF6D736489992CE16AD52C1B4BBD8691C61D88A3758EDEB4C3B80FFBCD000CFA99B94102211CD985CF4DF6679816BA38AE285EEBCF42BC800104113A5F9536F428404668A9A1B0A19C036C2CECC27204B1E3E0E0373F5F98CC0D29DD04252EF6956F24A70C8D671DCB10452881FD0B4C65A0C6160C0EA073E9ADDF4930196D14CE3CA9692541EAC625F809D42FBE533356963E6CC274E6073AF9BB9C7719F053A60BB44ABC2040890671511E69FF76D9956C3781FE9AB32418476139A0D1F73B0D817BA7318AA84F38C0BA4BCBB3AF378AD98B12338B7920CE5789F4FC6FF029C90F891858321A47B57B0C90A4365EA71DD69F2385F6A9523BEEEE8ADFA594E8EC4C15DE3A1FF837BDB3667E64FC9ABBAB8FB4258510009F413809DC52EE3831D511906906CB8A54E1117330A458D0FC8A52BA5661A0258F62C3DF4C11D4B6EC76F74770334CA0723FAE02C689A93510278C2738002574F13A60834B51FA617F9A41DBB75B6811BA25D4EBFCAB00C297ACBD1D29503731F9AB191AC8FB7C9BCE80F9135DAB7C94FB0E4E261144DAA71A1B940E0233B24306FABDCF1681BDB3F1536459751CB5BD326132F6685FD74C5A8B257BC4E7E632785DDE6CC3C657300A1A7BFD1D0004B1B85891D0F47F24E45D780CAECDBA2EB3AD446CA949BE01B4B28F44D9174ECFC7F42B70B73CAE3FC5BC3FF36DA9982509CE717CE4E35ED4297FBB820AEBC5FD07C2469C21B42A9FA77F53DA6128DE22B5D8EB98E5F6C63601BB6A0A47286653175BD18A71DA3759E94639F1EBD97276F6A6B4228D84A458C56D0157B76CF573A1E8442B2A8BA6582FD0CD8B0C617D387F77E2627D4016F9A71E3082FC6E2DAFF1EA2326DD1E8D09C18C0C286111E67E922024AEBE81BC97371CDA78D978035EED4037D774784AD2296F71B3F53BAF74AC6028F7CA9BC02A50EA8DE22FAE4B0ED2A844D463523D74737BC15711A23C83D8A8BD16BEFCC91C6F37551FC97BDD233A3A0967EB6EAE0183F23F3D8DE25DAA4D8E9306FB695E86D7016E895B31C4186F11D9BA4BA35234F7DF4E1864814E45943FD05D78EF05F024D43B5545CA95E60E001D78438181539716F471A78BA7E53D9F01DB4BB28EFE58EE4C15869E0E1673D0486E1657C6753519BE248FAD7944EFC464905875F0E55E4CB501E459C66F844A9449073B593E972BD9F5D9C0C0214C4D7A16A818E349AFFF9CA0AFB7A5B86FE425ED3E07AB89B475A94CDC06781CC2CBD7E2C09D971DDAF937DCA2061BDDF24F16DDFF045AC7A82E70AD7155E6F90B1769BC67254BEF12333AE2C6FDC732A70760417D7D3955EC5EA0E8F917C853699D244B4EC7B3E02B33CC1159ECDDB2972358447705522E35C4A6F37FBA3FE37E1F40AB350F395CA26543FF5314B8371CC4FC1F470AACE33D188B06D898A511FC9CCB43BBE6A033FCAA1DD7E4564BC5257C2712011D7EA37018910F1E30FCB7696749BADA2A66BE7ACC9EA30E07D755F8E32024FDC1D29F8A997BFB370F2BC223B7C870A75EBD75DE3FE0FE08971D275EB7D55DA86B803615DD59D475E0E75C586D1A6BFE1A8423EA77DBAEA4854D5169846DDEFF74C8AC91991816D0862386D9C7D38DDE7F50CAC64B79D7E415076A475F40DC94D79F1CB95FCAA479A07A051D5E744A01699CAE94D82556A1020597BB8C956591A5432C6FC2EA5E4DFAEC1A8EF1EBCAE01A5D83307E9E16C6743AB41D56C1F923FFB94448709E344AF69D302857813388FD72D99D3A596638EAAAA1DCAE05A8F8214AAAEE5937299493C5B114C5C8BA6389E1AB5543432179D6A9C1A457FAA25197B18780415454907F43F8C634E264FD10D8DA172ABC4CE49D5B78BF00E22813A5C385EBC9CAD39057C26D795C5960E0BC725975C0BAC80CCA59E6B05AD861C887001AB1CE1634196135A3289C4924747242DFD0BB95D8A58B3B13C9C91F9EB285368734CCAEB3E0FB7EA94EC928CE5907F26BA5A44BE9D8AD9F839CE251B3C2C9F145A7B1975C36266A287B2C660061763A58410BA215EB8790477441169CC48B44D0E6E0FF66DF1E03B2977CC87B9C2202DEA89294F56E720F569A7D5EF5A9C804481006B886D4660325B53725DC5C1404BE3161DD84A5888F78A74FBE0CE3557D07A65DEE5647B9862E89C849ACE64089BF7ABA08A971A1C7D6829F9868A78CAD90232849EA5E2F70BFAAD769892B40B662D3493B33CF5FE3D40877B4CD4105BF54AFF3CAEEF9130ECC47CBFF5F9F026D8BBFA16ADFA79A718023CDFCD8CFA081AEC5D37620A3810B9BC55C0A13BFF632AB5816059D6A569F57F408ED883994F2C105E045458390A6ADF24090414A45138E72D90C209A8FCF3DE54189D5234649482C575BF2E570BD4088C8B737C1776613A76CF477F073E1D2F8F9A5F24D76F7E368DD0D513B852F492D5A327A2D9BCCD6BF9966A2BA1D956FDEB3E452B3D91B68E29DBC14DA5E8E2EC139FA23133EEA5942F17ADC3B93F052A28F5DF7C1E956FD190C5A986450E4A131029D8A5DA75B329824C62369F5C6BCC1478A1EFE28E8DF0A5F7A1272FA4D8FB4B0C3FDEB8C33BD5D28C9676CD7F8812F37F1F9052E1681BB36012EB36CC01F5F1BCE36B6C2295A8DF4F4C9673B06549CCC3264A0177529D21C627695D4480737AA281791BE727803616BCCFC04B00AD3E316EEC2BB5D61BF8E4EC4BED2C31370ACA1CEC038A9764F5DCF8CE9E1C013DB3846CACFD28AE21363FED8CDCF280FBFAA754E7A00BAA18EEDB5D94EB6465C16E00CFC78F0517C93E7E4E1DBC993EC206EF0B9D37B4405370AABF7F859380A542C85AA61D3E433F3453F625CA2111F00B48884CAD5BDF64BB5E2A1B4BB2D74BEEACC06FECC461010F94B73D6744E5A5F3B1F39F0DC3CB0BDDE4230643EF9EB50BF1417D899D862D310837BEA1D81EBF7BCB3F0DF67A4799527FBBB74B48C06316BA66ADABEF1A4B16D27192C0E2D4E666FAEF9B58A74EE762FF12CE76A197DE2F85FFE55EA8C2DFCCBC58D0C83F22F718CF0621D9A1CD7CB6B27E8A06DC6B6DFB48521EC8D8B7DEC2C3117AC0C59CBCCF52E32A1644185DE558254DAFCBCB67AC8C5C2EF9430299225CCBC2430F64C434912FABC40325CD87A9B5D296191B9BD75FF189213EA13B7079200524DC4BD7154D1BA5FBF8D2797BE23CCA47CD546A248C55CB934B025E364760CB60E457391D23149F6776E0E985F2EA8F595FE1D20ADFBB1CD4D803DB20FD66B4F57D208FCCB1A06C494F0952DCBFB503276B75D2807D0F2BAD54F15F1099FF802FB390238A13DA807B061B749046787ACC049FC0FE3E8718A95E7870E2A36DEDF395D71581F854C29870FB63B3B5F770DAF7326A5997FD482D62635904E85BDD3022F727B6BAD6A5943BE8591B7D57AE5E0248BB56870D662F9C57375D0F69DCDF43F46E64164AAC3FFC691C01B586238FD96B826A24AAD1D75F8EBF791998FA7BEDE1C119906974DF100E1F6AFA1F6B4FD1D41F60A61A3CA5F52A70F05662F083CBFACF6ACB61985898F666202805AEEDBA481DD25E1E819B710887E345D5B8941F47322D8EDD1EC5EAAD551A8ED5884F9A2C000D93D28BBC1D0CE2D1851D914552DD329CFB4404391C3155AB35EF66DA241C85E6EA29C8A9376171B2F877421A641AF13AFB7B754938FCF442C2A62428CFEF71075410B8610AB6C5E1921879A5D4C5B6F54F38589850092C5354734881F0731EC1D01D8D7DFE36DC706133ADCB3EBF07B7B5F6F4E9A436FDCD80E8CD531AF9B24F4B476EFFE1D7ABB98E18BAEBA491908FBC84BCFF6F65DE0371AE9641FF498449777C0D24262F65C1395BC1C1B3849B14F7F354FCA47CBEEA7E5E7CDFD4CBAE0F237F37FE1CF1345B0041F7091028DCEC1010D3D23E035F100AD9FEEBAF530996E471BEBFE9F1B1D04C84AD3C3512FF468E04AA7322D7662EB116F74E90A265C72990F2601702610710BBD0B9511E2BBB4BFAFB3C82B34FC5EBFE790DC55CC6DC7DDCD17AF38F6BB225A755BE34976C0BFFDDBD5AD36F59BCAECFCFC3D529E99EDD36EDB2B269229FD30C505A333679F79DDCDB4C6BAF54F7C55AE6831ACBCE8147E549FBDC0C605794E1B4EC6C9E489CED94AB53831D262AADEAC53E5935446AB5482A90794E0865196524B4F650AFBF93FAA5E78231BA801F7593AA1A471DD1612F24E3EE917A282D461B8A5403FF2AE6F087CE8E6E346E41E76C03A5CFC03DDB8220E78EEEAD51CF71B79E47E2161936ABB6EC5B552DEB57FC3FB13602E1884143F834F89B372CFB21A093F5BF146E036DE4B46D327195267684D046DB42D1133E59596DC4FDC2B6A0C036B7453F297A293D000CCA258A0E2A278B3ED183EDE0DE785BB2F3B5F97378817BA7020ACCA6B191144710ABFFDE29F66AE03348DA1470EB20338C572386B56E67074B9E64A0E52EEE522491C3FA14B7E751E07EF5C3906396C71B6BAA6C55E2819D3F1557F050F8C41F851D8D43167AD703972AAED427A8E0FB750F7A98CE663C69CB77B016A9EB77DBD2F97233403AFC3EEB6AACB3262D69FFE84F112BAF7A78D0C31B830DE27C6FDFDE869BB3F69BAEB1DBC3982BA410848C7788A9A43A2BCABD434E93C80AB4A24EBA5A8497D28C85E93C69243E20015EF7941F59CB2A3E1A69985511A16E4E82AF2DE027F91F9DC000AD4A27036373B1633AF1D3F21C3D2DB28DB3783C97905B6E73B734ACEFC2CFC0650EA3EA4CA99557649359AE05BF3C0C190CD8E74F5062F20638F77536DFDB3EE7439842341B4F11B74436338F0CF004B1FE0D100207D97F90B58125D151BA8799E8C8BB48958AF17E27B983CD11FEB8380D82C4A8EA0E4C8AC528E01E4C87737D7BBC84A694E95BE782F6B70884227929EDCC2AE21A0D3AFCAE60669EA4FB79B95AEDDD12038E867A1C1EBF386B62916B1FF180469C308CB275488CEAC739CBA11161C72B771A7846FE95478DE8235BB3378F45F77AC26A61C7A8BF889F3F78C270C5FB47D4E6F2F946DF1828BD9F94495C4BB406C6E8DC6E0D2A0B3D7400E3E90668E90E3DD55AC2556C8EE13B7F77E6B0C4E05031F01D73ED870D0F6CE6F26891F3D94EED97B2D5F66C67C7E101F0E9E6DAAA1DE7C0428CB9FBD2F0E88E0ECBB38F6F0A9B744F97168BDDC18E45CD34D201662E65FF7F22AE39FC598B4C1359D37C396E526BC8F80A4CBAAE811255EED1D7D8958FEE2DD3277FCA830E5EE0E4660ADFC926948750118CBD441F7448C9FF355A53CDA8210818F27A2D8B0EF8F6497BD7675739C11ABC3F8631415DA5A9A9DE0704B9242DA5E32827E7E042DCD933B6745F91736231E5306D1EE112D4B707FCF80C8304685808ED08D9EB21489932A7C6C635017C00EAB8A6CEEB83FECD722FFFABCAE7D4CD25B1D3FCD0FEAD728C7BDECEBB111EE80BAF1FA7D512564F5AF1F89379D82DB037C5B9191686CF8EABD9D674317EFA9A28B3A3B292D12B666AF3F00161625156D9FD2DFAB42A5AFD7D9717A3F6457892081CD9031BA28C331CD155FA34FE981A89295FC69861E9EC921BCD3C8AF1CBAAD9EBF4FFC7EA628A467AE5CBB9546C009412FFFD3834376D0C525952CD846E572EBE4490D809EB2784EDE4FC478AFC304BF68167DC1A9B7C686BAC400D6F79903D0FCA521C79E9C94A927B2E129718F84F65EFC5C3EC486530EADEA2EB797613E11DF0E1E114864031DE0273CBE6B85F2CDF778BE559959A7C3954879445B7805DC51BF38EA0068875680E99E94E61801AA1442CE5E7C9E56603FE82E5D88F59E39A81C97E2C5A45E47E8160953F0E57B8735F4B4F1F1A8EB875E8CED1B3B844B37031E815AAEDB1F502E0CC77594C5C73B10414EB40E9C74EAB006A7D5920E88EA5696C7258D583A73030A444020E4D80B38FE35DCFA3EB280BED403C967995E6D5B541F1374DE72900892AC655C58F52716006A42389E58BC0F6F25095C1409CDBD41F1DE27EF3A33D930FEC06AE8DB18A3D83CC0DDF8A63973743AD76E55B44735BCC5F01F17C3B7E1463AEDA920C166F73CC811FAC80D87C405FF3108E856F9F66E228789112C045B35308205B4B7C576CED73B78614EBACB8B50B18571A0B70887DBE53BC80CD1C98D7F087CA0F41791A619D6886A449069CDDD1D20792931CD5ABB085764C7610B6C7BE303FB63A14229A34829A2B80ACF1449CA883B6B4EE58DC095E5264FB470E12529AC39D66EE66E2F03A3AA9C30C4F00B99BA55C79D972F5D0E87AB70647C2F78735E70CFF8EBDD280A5630B4F7C75A4FACD44459EB9C0524DDDC4769E2FC34CC1B4220CF6EC9BF1B490C959183E55D769A905B9E026F7C049735D34CEE062E6BD886980AC673AE25C5C68DAD2ED3D80402866DECFAD1AD088D2C782BED205684848E0AD9FAE58C9BAFD4534FA15C2E9FBFBE334799CC979F44A1020BBA43120DC06952F90982EC8E5FADA21FA06B9404F50A9F29EBCAAF20AE716FD357D79649D484BC24DDA536162FC20A2CD7605F27C8649F895A8BEC53A67C7C5189AAB9473423769B685FAC9B8D876A1A1336D5A757DA7B3547DFD6ED0AB8E8EB79A4A5836B5A00A17A60EA315747D3F702851BACAF6A596FAADA3776E61051F5815286B7CF2D2F5EA32EA8A57E21791DAE025B90CE5C16E4CC6068A1EAA53560F76EFA52026A45AC30669962171D15681AD3BBB59DE704D16700041D38B0287AE4A033AE28A1FDE30BFA4A325FCEB00C664263FEEE7430137359463DE3132F553AFD5296C5E81A6E2D40F35A06E6F46FAAC3DA3E6D7D8AC6DD09F2D0270E6E97374DD2466F168363471DFE26BA426F18D5510F69E9D6B972A30C520013669828DEF096969FBF67CC86110A5F1E1DA556CBCC79EC670B7BFDEB2F6D00B5CF62F03608BEB65BF8F058C4991877C7F78321276230CB832010C577A80A102C8BA08AB2A19B29BAB88B22EF51CAF112EB28BA198424B818AF5F598786B50AFEF0B6F63F906E0C20CA5438207DC416C571BE97D9DBB874512BD2B200D6173C1C5CD5D3548D88487F0FE37BEE3F1415B937389EECABE889ED0E062A0C4472F91229699EAD02A0303900D8DD343291168A96C68FAA165CB635CB61AFA3A7DA9D142025BA536A4EC9A925CBF27AF572D22D04BBD5E0875B2051C9ED89A6978EEA01B317E9531AA82CBFF19EEE3942FEE9C21BC9E045B108D596CEE0A070E7F3ABFB838A635300D161689B136AA27877F81B8358E6C7FC78EEB11B890E73A21D6D7D075F87070DF761DBEAD7E022CCFFD93FC33937F8221DC9DD916ECD40A9DD6F952469274939CDE4C4C7D0AD4BAD782E63FB5DE6C2B4E5DFD68C0C11BCF8C64BFD6B9062303FAADDA3C2C3E5C275069AE3A70ED6859AD0F4B66F13538FF3A3D47851A03CE9E956EE20C8ADE99DBB12AA2B7610C58E7EF9E0330ABFEA16089CF01650E42ACE98ED0941DAB1FD9D7C6815FD22179BCC5EAF5E88A2FF0AA349B745CCAED0BAD16D1C9B8FDCA37E115DBC858434D3ADD8E44FDD9077E201DF9817B653406E33338B37103DEBA68097DA0EFFDE985C872C86E0E3101759A711247E044B21D8607D9FBAF15C1FB53C8B0E8C0747356C2F96C38BE2293C3A47C99E8014C719EB4BC342F685A591DFF88FBF0E746E6187B253A7F3A824DBCC6ACC76484C268ACA22DF6670F265DFA39759BAEFC3AF5ED0ADCE50C9B6BC5213F8A54C1070B26DE7FD95E3687ADC1924957F7A135CA0582AB08031CC6C43C8A8623EB8A6D2C111F732F78EE1480560A66481DC9B8A919D453518153F4CEA84A1DB6A5717902E098A0380FEBE06C3F37ECE7B4C095986ED5BDB4EA038D1D2C27CC72E56F5576AE14A1760935F14F1915AC41B6B93D5660D2F240466DAC6F9D6B2341FFE1955E5ED32B9384F5F5C2644BF3A34DA144DEB7E111C3EC5994A2173E50B2A5C3BF71A83327A7CFFE836C54C22D99D8EF11A130EEC439C51DC2DB558479FC5FE9E8B6B547CB8278E15A3FDFD1AE771E6CB44CBF728A0BF0D6F32E813FD93AFC2509F99337F565525A5805144918CABDBE8AE5AD93F66918E2A881FBD538246A1362A1013DE4B09CC85FEAC1D725F62EA51E43ADDFAE5D874017107EED53F1699CFEBD8CBBAD9548420DEBC8F2D1A6EEFC0F830BB2F716F091581EB4C65C93E93C86F7A3D550119E494C0F41EF80D805716228C8E61E6A1FD14F421AFEB39077A6EB54F1E51BC5234B4EEFDA822A57009F3A709D8705013107C2E1F4E1D157C8A9A5E0CD3C15A968E7FF0B3462C4A5D784347F5F342E1F3F5A4C973372959974998EC3E7B0EAE56049DC2CD96700897E7BD58DF85FA3092B97542EAF1797A021EB2C644794362D1FE2C7DD25794115F9C23A33A01F011B4A95868653D8FE1A73FCA7A12BA15143F16C35424B6831A4A397F8F6F3A1EEC26F038811EE41C1EBCB633CD46985199C8C3BDB0F6C03E5682B65AF011769E48198752891D287C677A41AE1B638AE5BC0D0504848C9784D8A693F081D0F09CA14A7D567E25500B5AD97D0214D7D52F08FBAEC21DA33D801BD60A9EA5C7B60C6E2C967270CAF36970AF351122C74DBB56857A1E952EC9444A5FB0B0F5A56D9E17671135A38682367381E9DDC8A4F23378EAD5DBA9C2DFA5A5952A7B97920B38460BBB9305A4C20365E420FB7DD8697C266B78130561241ED23CAD7A32E6B928F49ECA640A790DD0728F33E751E9AF2113AE250CCB1D17174FB6734EA354ED8FF2D2F62DE648D52E6A113F9BFE58A6C5BECAF406DF3F0D28F221D29E40F93CBE08AFEFF3F960258BFA7B35E65357FEB86EE6ACFDB2D0ADD23DD4F138EF854B61C7EC36B112E6DD4AC8890F6F5CBF449B09CA762CD28019AD0DFE1466C11CEE53678ACD75234800DB30A0703304AAEB3CF7CD3DA34B5CC84DE8922F7AFB774A931335232963803E266486C34AAF56FD2FA20263456EC17D17E57DA3D5282EB7C7307D6A70E3591A2C0CA68A0BF0BFDFE4E0EE7A98F3DEC391E519F5700A0CC32024AC3CFA737A1C102412912D6EDE69B1D0A45057DC280BA17435A1CF7E16F3687A6A4DCDADE4370DEC954DAECD1B03896BBBDFC2CCDBDB598A945EE09821E9007D92EEF159509E345750C0214BAD6A5D7E811EC12D8E558C9A154CF74AEA8070F1620FF2D2E588DBDFDB8CADF907CC0C96B831C4ABE7A431A7D580CB63967880A9C1CFE912F8D881C611FFD434A8514D9D592873FDAE5C8E8556A563B9D7AF48EC09DFA5A1CF0F3A785F0FE6BB1BD4EC8AB712931126F1D34D7ABE00887E36EF73D28F3B87A0BD092D3D4194E39EE9A586E8318807B716DBFEA88243165BAE25D9EC74989A21971C7672525DFE820A39560C5D913A28A0BBE495F0CAC4F07585AC43CF5BF54A842FF6BC8592024D521B5CC748C296E4C3B645289228013B90A9275632C5482E066FF5BB69919D7CF3B51B6B8AFF18A0AF215699C6252EDABCA8723EC0BF2A8B5B45DC6F8099A8176D1F697E9751F27BD38FA7FA7E4AAD1FAB4B1715072E4641CE6074F674ADC81E39B0CEDB2A02D12B923F679FC55512D961ABA440F735DC756720E0256AE3A5B7FE6F3A56FB2BB618705F2A5FEBB4F8391A72024ECFACBA73F5C7091964FC3E88B0FADC6E92D4272ACADC7C693289EF8A41B45B4D77CCC27FAE58B18267EAC08C562450D728D7E8554F56171872E9E11CB2272A1E6C05F70BA07C270EC9C0F2C2031DF8E4BA9FA6F68A6D9CD35CE88D23D2C564231A80173EBDD3AB377E9BEA53FDC909994DAD76E388CB0491D01A64E7038BF19F9A6BEBD146B1C3369C6F7643364400D2BE7A43F367CDD8F0880123E981B0B387461B401C50D18CDC6D46A2E7233D7C6D41EF7F294E75338D749378C4AE87E5E64064834451E39B2FEEBA7DBB7E21670A6A05E8853CA31686DF03D9E0C09BB922A53E7A4567D645E57D9C6D3BA18EC36113E6316135A30CC2738AC772FBBA734D1448028B0A2120DB9516F05C3DA1358A8FD587B21A1E12930B8004F63AC8A9A1484E3BB7EC313D287CBE35ADEBB054E704816D7EB4378FE5D968DC244F8369CB1A6C31F3F106C87B476D2C6C14A4E308845C2564CD70FDC1AFC59A7803475DFE2C63791EC7D447092AFB5FBB489734EE12DB7C37B3AA0F45A04FCD5B50E62CD71037B73672545C8420DD67968C9EA56D6D490741D534D9C29F90BFDF37F6C669A3FA38535DFF1D06566076E6CCF91E060960DE939B0B62D61DF30ABCD70B6C5843982A60CA36BDD5A73FB6C50CB2C57384903A77A8EC1AE3E6FF90B105432E45713A2F4B09B11E2857D767025109A5045E808ADAEFC74770F3E0C20A724398796421B31A952742533A8EED5270E97C9B992DFEF48ADF73D3A591F7FFD65A11FA93F0B87F1ED93389B29B6F9242B05F26639A871275AB6194A14FA7FD2C00BF57C3ABF9DD2897183A29F7BC80D8FC018D11FF26AF3754139B66053062DE085631398992E279D9CF0590508EA275F2B44B4B30127BCC633A33792B83F3A5F12E14292B79903B2A5A0C7852B54FC4416AD38FB589D5C167DE556594693C72AC4E024215FE4967FECA9BD6BCD46E33C379E30C1780CF066A8CD8666D38A6F750F46BE5161EF7FF80EFA1EF706FF70DB2BA6ED829456E81B85B559FB785D8124983D1551C01FCD5B00EF899A8ED65191D8E7614F5EF2ADCA91BFA55E0B7F5929E9254069440D5E31B290491C20CDBE01676EF6C21F7911BAA6A689BA8F4B845A40ED76D2D50ABE4B47B03F73B678E363C971D86865D052539D64D3E762B6A0F5A0D1A917EBA6481E8A5EF78FDA2CEEDB3D8723DC7E492888EA4AB80DE20B8A7DDE86463047473E56564FF3D6DB4323E4C25176E3AD762504EEAE092E6A380CED61D8A26B4336D3FF1C4E1A010EECA9D1796D7850CE63F17BAC3404E3DA0072C0D337BCBF335D64A25165BEDE02F82FF11961347B253CFFC857BDA3766DA2A52C5C9340FC0925A4850AEB0EF9FC711954A117607CB586E2760FB0C425E719886A202DA56D3F60D95C085B449ACFB01C8D5648974F93603B5D04E327C52BE05CC03F05C52DE28EBEB705A0EE8A74B75A5B06D3BADACDD053F061D7163541242FEB8F1EEE604F224BA3F82B9FB88BF4944B6FC8E69A984C94FD07F27D5ED6DACB981702BDF6870DD8DA558B77529CB57B66F20A8D8401E993C5CB39B84D1C679F359063CE78BFEC344A0F2E8A3B25891C69B66B9417FD781838E42DD49D590EDCE6C2534311ADB4117CCBAB29D407DC64002622A86E1C0B3B088FB1598A74F2B04378A4A700829F371FA6F0F1DD2E8754E13152972C5FFCED19FE5BCABD839D0AEEB037534B4F687F219CFCE8F25AA26A381D380B2D20EB30F02E8344DDC74E76DC6FA9B802398A47E0B425E717D5F6CD2A356078F75F532D50E6864C774B3BA0B98E4E16743D106E9F3D2E5FCE3B97C5F5341D481D2C02B91223C33ED4F7F836D3AD9F4F7A45DB44CCCF394CFE4B6C943516C5D80AA8DD19008137FF515C24C7923D18A1872918870C43F45F7FBF512EC8B4D933F9EED0CE1702228EC3C40F1DC58746438A644DFB9FEA4D1D4D85186B9BF7C487371CA54CE4F4A7B0D562519BBE44FAA5F1E53C59D0ACB6514A8FB02A081AA209A0127F774E7C4A99CB8299248C160765E4CC82F564B8B490EB36101E25A8C41F5686F280CBCB04721C92A8C01235C4C8FEA6C4305E1FA9C72FA7759C88E7A4507B931EB1219831D4D740928EAAE9DE16623A88DA2F5F7B99C1F760AB86FBA586D1703E77CB351228DA8A424671AFF47D9485080A6200512931634375D98C2DD04A124D617C2C7F6285772AB20A205EA6BDDA3638E4EBE171D1C0C77246535D2D4CC921CE27CF1B3A54C80CAA5551130E4C1D061088FC2C20D51B206BFF437C5137E77B4FA9F55EDA51C37A178F4AF8D4EA3547F10F3A0322E6F9AC67F5D552C020D84CC6D32F7A68203FE2BA9A2EDB5B065D87B0ADD1EF8F3DC26DB8130891CA39D01AA6992727C8C5C2A5C438FFDFDCD38298D886D0BBED2081D1641FB09443E0E1AF3E6EDF9E131BC0CFBB0182DBD237303267C5C5C2285C09F30654558141FBD09EB0CF608443C09ABE5D9514603F6ADB9A5203C33BC4D1FED82C3B70D4EC9A26F9DEEE3A9363EBBBD87D8F01B4D8B0E6181197D90F78ADCA38FDED2465B5F3536136A6CEC397C520ABE3A9FF658445BC265A543021DB698BC7232C535DD13352BE3501D0700010A60A9F99C301AFB7C26C5CAB29CBB2CAD106C46AD6C0D4B528614DD9861210978F22A10A2E98299184D818D7BF479110F5A1D01B15FFA13CC5712031CD2DE65E5293D40933B06D35DFE7A0B5E4AD757EB8537A05FAD369F7CF968F98F011FCD0C9E9BECC44A99BFC6D577AE0A63212125C0A9A77432ED27E5275B86A0A632161D22AA9685C582826140F9ED3C86BAAC0AD554C39BDA53F77CF33D23C73E2CABC69CE53BAE2285E33BF251377A8EDA7F03832891146DFAF8BEAF898A269B90471C68249EC2441BA1842EA615D3FEC750F0887D58DA0A96238E437EAF7C857FC10E421B885635AEEE75ACFFFE18E828B850715DEAD14FA89ABEA095653323704E2032EC97CDB2AC0BFF98A0AFB74344EE2001C2FED78428D3D0FCB09EBF896EC678A29EFC5C5FF9A06AE0B14629C5F6D5D42490E2DD1D8E2F06D3264D1830CEB95CCE7AABFC787E60283F930C1F5E2B78F7EC81AC52C4004F80EA814CCCD8547E6ABC4BD8239DA70AE74F46A06368CEF88876BF9F29910EB3F910B76396BC12F1929CAEF4013EE0CA9587B250405D904B96353C73D4A15E21BD82910F4AAC9DD348AAA9A98B3319E7D659DE266C4C10DB580EC474611DA71312C38F94A48F2226CDC6C4B63338643814F185DE5018727672C8AF794B8CEF5B6BBD64D12EDB3D05BBC49B9EC7706D525DD5A8C3B7E6088D10B44BDD9B9381A1301473FB651B0569EFF1F435574AC5A6C36C4F2828BFA87C9ED1FBDE71966536F1C64F34069AF3B933E6C2489AFE7C72A96729B1359EB8432D3C87E17AD8209CAEEACE324563BE26B4940AE017F1BFC089E39D5B05C782D9B08BD60E9E74A5BD8F2439A8E68C068C275B4BA5D2E4F2490BCEC9EF698EBD9A48B84CF3178AC3D0BE797E74E8C1DCA6C068CBC4FF94A81689B3C23046C69A5C34B6F5AA19B79AA79C8CB629E1052D58D003D54AF5F7AED5CC4382CB4C1512F7687D97AE9FDAF5F89AE7B6C7E30A50591C516888732C94EB4837DF7D3CA10D6298B72356E26DD4B3361732416D838FF16F7E5FC9E87441C773FF273B8F75E70CFC4FEFED9E12584AD7D501C8D6B69FC119DD8789E93DFF80DBC2F5586407DB14ECD76FAE8848B62168702C5AE1259475E2AE4744CBCD3D2B8502D11555737C7E1C8FAFEA3333687BC335472E3B20D63A8AE74A338722AE395F823962AE17EFC748A1191EB694391C79AA5970AFC0B5B21B8BB6BB3101D5B95C300E7F26AB39F1A1BB279203411E5BD7B4AF422084C158D3E633A964E6E470DC17A5EFF9867D67246E3DF584CF78EAF0F06D67A69A9DD22165E5619A5BCE317295879CDCFDD919B70A234404718A1CF9B2D3C279551DBBDBAD011D42A7E198DA38DB084A33EEC81C0910508897D4E3D61FC6C1D4D71DC3C8B92DCC46C8EF6B41AF192A13188A3C3766C27C098DD2D0EBF0F3D1CBBF8C8FB8824CA708DF8CCD4BCDB8620A87174349F57663212F443C8DE0C8BEBD26A90560B4EA1F159968B5F0B983F6C74E6CF2511D9557B36F95B460FBB0E50D51A7D06539AD3203C0C5C15B57E9BB1FBCA4E5959F1860AE92CEAA97FFD47D9C2B211DA5292113EC546558FF229669CC9C09BD8B1381EAEB9D9B98661618D5E81F6A73DB6FF539549DA02206A9E2DC626D3505D0B2F08DEC90BAF49E10A942653A7847F85BF891EF81CB02219A8258B737607DB73EA91A5E84D69950E33FF5A96844D4E8A55E7B18DFD45FF4E0AE4BF631F422E1A6678D386B7CBDC8911861A163FB7BDB9B627A569C91A566CCEA8FCE3770F8704BCA049B5778DD9AD23A0D0BD54E07C44CBD67BB8A96733988BF2215E3CC339B392C58D89797888784E84E57302B3A4855781118E16FFB83B44CF8B9B99320F22C74652E4401828D4A082BFADD82FB4BB00FFB14B0E8752D707A9BF2FB5A719EC6968E5C6CC19F0B14D4C1611FC44795A0EAB7EE95F68ADD5D25228B113642CF0718875A20FBD7059F83521504F095FB3DFC20A01FC3D668E0C79B67F5EEBC57760EE2355A01F65BB0000293672118C9E28C215B6BBE30000289CAB8E6447BB5695E8D80BD2467E69E2F18B0BEBDD938022DB19D1F4DDC83D800A27668EE78FA5A24F1EAD457A2340B9CE91B9961046E7AA5C690EE6B26EB19B35248753F991A157CB65EE5CE9BD9752AA8E2B9F7212FA4687A6839EF8AE3E44B77C4F930841E3EF313A413DE12204D376FC53B0DCFA305027B302AA6429FFB39DDA1E7097E8E2649C8C3A1739106DF7C095512CD0D9084333685A266274A222F412CDB80DB4505F219C187C554A33FC319AAF4675684A7836A09C2116BD0BAA6684C3B37052C2E8F7B3AE878F8184BF04E2F085FC3EDC4709172B778B1D1C7219B993508615F03A9FC340E1A9E24588EDA8D1E202C43D96EDA28688283CB5A9B43A3308E776B72CA8BC1F252DFD9D38EFDFBDF6F727CF994DA73AD88F32789EA709A8F4D1427926C60E6E0FBE79EA4559F1FA0B9721BC536D3EECBE6275268A8647B35FC77F10E76E4DC2527E92983CDBB206F92B3F49BFC66B92B61760A6799B98317947D2171225F0127186CAF2C0EB7AB52B3BC95D680C220C13C9DE3F2D61E2D97F0C9167833F69CD964EC4580CD3BC6FB92A928C77D013BF07AE1B4123903F7980B610437038549F2D85B82AD1E7D1F2BA9252E7BB1A352845B67B3617F87D86F5E154A209C2D3FF774638EAF88960C03538DD0E86577CD09BBCD548F2AD07B2D10DA4F6882C7AA9A72FDB5517B08CF74A530B04ADE4E8701B770EDC3131D71342EAD27B2ED085B1C98B9776E32E7F6C44FECF7B04930F122A3C16E336078333AD8558FD71696D5B5D777D9C3F92D009156AECF14AE7DBF9A8F9A5E087FBCCDAB9CD2A2C7EA6C29480C99A93F66A87A964C37188ECDAE3DFD8025B362D22EA84A6D97AC32E284648C01B7EF9F07C6B8512B77F23A9BF121F7CD5B285634332FE797274783132233F7695FA5C67D94003DD0DCBF57CFDFF993E6D4080ED2FB52FEA0E04E57334DEF416EF1CB59005154D54D57F8B629B1B550450C3AF4BEA617A65803DF4CC6EE79B136A6DA4B71068A9B848B3B009BEB9BF9C5508778072B7F8FE8C8F3408D3F285128363E3C9B7C8B9D54C35C8EDB68E45C2FAD1500E5AE382A387D9B12F57044088DADC2CF9935E064BAE2C93120C4E63796E835E643D69D55CF4D10E9609F38A679F9FB3CA698C1530513A548116EEA52D1D609726149C22DA8D327F36898E427226D0DA205BC2920D81F086F77B7AC08862C30E7F8BCF7C510D7D82B122A79ACE6E29EC1181D59EE0260CFC6A1090AFE7D48FA29C50C408E6155EAC3A793A86B30E8F6352C3F25228BBC09FC34A4FDF1B36C62B9873EE271648D4B1DF718206DA07293C7879EFD6AC0110BE96C0C18F4A555AD6B6B1971310BA863E38B457E7A72D05257C53ADB1A2847C217464B9FFA8AAB3B75B59EDCE3341C851555837E645F002F9C91EFFCADF5EAFF053153F5B6DEFDD2BB960C8BBDC85D7F716B15F21CD52C1A9AE8BCDBF758FAE6CCB3F6C74EDFC2381F3404A8C7E666E8FA382D9C0AD67DF1D3A108DD6883A3C4333950276519BE6F39DEF23261268866234A1F65CF374291CF70C9D1B4580563C6BD29FCC6C625516879D9182454456655728E4E78FC220EB0935FA5073DE701887D3C025D4A76210CDB2FDE2F5645B42B393DBC743B69E3AA79A125102466782CBC55A0BF434D7E6220AAF54C6F018BC3DD58E84F5C72238DA645E87EA0442F8B0C4117D475395961272A1F0C5FC6CC75A23603201CEE4D735F27C4CC7CB8CA2852F986A691CECA426E823B1E33043593BE4E3F53B761FDEA06A61770F747CC94A7DE06F9103C0BD1544DCE777ECF7D319B51F60BAC0A2F3369D6B01303A2F6113760A677496DD312A5D7B40C9BA470333CB5B01254C702FB8217C0BF6F45139D9BB8B03EB08899B84C53C82950F1FC105D1F89FB141E3A04DC1895297C813CDEB39BD3F725CD7B00874E7D29B648D3E963B82C2211C9B60688A8281C8A3BFF6240AF77F7B75C87B6D7FBB6096DC8E5FDAF2048EB95C5C2C08CF83A366701757017CCF9D59B83198C5CC2C99B073DDC85D5158E4B85AD4E6977CA0E19ED16674CB72B4855F5E42DF31F9984BFD1F8042C4B5D615F7A932FD2C47D8C485A8277E607F4F9500E496733A014B5D5F87B1A7AC2047FC868DB14394C85ECEECFE4004B4EF0D00C8BD57E86B97179619D297FB881396326926788D89E811F240A0D484F605BC9D0CE66CADBCDDEAB4787F2351D4D7886EE1F84EFE4141C2576910DF77400D964FBD3583CED6E6D0B3D92E42960BCF50125B25547F59ED6AC86E45D9999E7DC100EA13FB78F815AD5EB1AFDA08D2086285734EC4431A71D4C8AE35A53C8CE1DAD8B3BAEFC2EFF1D5645071755C3A41B852C47B1842FD7DBA9D31EC3C93476E497196A02CB8CE0C757C7AB1F87E8820E9C4C8BD4A9023865029DA17AF601EAA4A86628AB861F41B9F5B7050B5EE00B26E4AA71CAC84653997793CF1A9A16131B5019042ECBD70FD801E46EC3F18CE6378919B77D09A948510840764070D9CC30A6DBC4F65D48FD0C3A3826E261E9C51995181C98234077DCADB7561252D9714CD80558877079A27DDA604CC34DCC1BA52E598DA8F41F60A80A37102F1584173F523284D51F1CA5E704FB02B791A90434EA3A1F97A2D13AF9B958167539777C937A1A15A6F0C50BA740C0B4019C81AF4E98BE0031878533AB543DC5E3290A8FB9662648FACBCBBA5AFCFA4FE590C3134F003BAF2D9FCD44A17EEC867F11EA936345D76E8F48F64D4DA15533F5B924E240E7012796AFB2D1FFE0A5A3970AAC4FCD591A017C6A996A958F3D93EAD40DEF3F2D869E52615345E06F77DB53F9D508A53D3ED650C0D0BECE34002B6A83E566E4A067FFE4E66BE86E754DD149F68CF0D8C9A7281574407AB119BA4947FAA134DB253C5AAF35C4F17F4C0C041EDCC6D0B2A24D7177295E2E86C9912EECFF73E70CBC643B775CEA4C9C6345B07BB431568E28046E2E111A76F202C891F63FC5E8100509CB9528E25856957DCB69F06105D94A4D67BDD43D679422BC27BDEEECC4A4EB7F39320B8B521164EA421FBFA82E067AC08565C812810602FD0D3F98CF13A87161118C2088B1090471B59EAFE87BE767458AB24ADBFF30CBBBF7315F0AD116AD627050830392BEBA9519026098EEF33C4B020725D288FBA7FC6B25F1F976047AA6D90019715448781381A58AD60358843F1329E278341CF67C996CFFA10DA30444788866AD35D80D1CFD199E39D04AA89E1E2613B2C9FAAB537AB9CA264BAF220A0EC9E64404A242193D2F1B8AD2A070A7FA75DD2912FBE381CADC1949BB443A22ACD6C7A8EDDF05871E6C4E6ABE9656A9B9986442711CED0883030F96DDF8242C44FFF60E154D8015DE2AE248192AF7ED0BD0C567A5F0F8ACEC1C6A48D39CCA3ACEE70F394FB8882255A4AA91516B4FC46E8634E1EC354844770EAEA8A0F429D13F24A512225FF479175400D7963333B685285FA10F383BDE96025E691FF737B574C612EF98A9F6D54A9A6B7F1C7FDCED64F5C2F882CAD58A1A673104C180A1ADC27EB6CB75A4464723E37D65C8BE3B38B02C1E5C716A98EE6C6B94A3B199C2256933F257EE51BC54298810C152C0B8DF126C7A249908A5AC0CE41B382F06A3229C2B58A4722861D78B30DE549337C9540FAB5BEE07F064D48D79853AED8591645438FC3D4F014423F26EDD7FE13961A7B95624A80045F3A49A6AC4B014F7A97984F7DDB86FD338BDE354DA453F70F64AF63A1A387A40DC1727D2CABBDF2D42E4C27BB655B62620B892EBBCB4E86658DE44E80753AF0A74AEEBD7A9FF1A5A44A9F53937A67D4CF4A602CFA93521C67845462D900F19AEACA33400ABF813F005136FD87C79DCB6DD1A622CED3CF94520FC942A1B3610ADD54807D0D2485EACE46516E76495CFC2E5D42E5361D919419F782FCA92AA72B40FEC0E7EA3D6F11724DB541D1F10C2AD908FC3EA89C17975C486D1DB89EC1EC8C8BD13BC7DC5780FA2B285053CE1294DD2747AEFF547FC0ACE18932320ED2DCFE3441C943E7173483AF26C2E0AC7B1534BE78BBB71688D4DBED3FA6131CE8A8D6173CE16EB4EC1A039D92D3275183D488A23C326D43FF684D9F146C6A31BC12E9A87F694AC60BC58046C7B46347F7C2C2C41A50829CA883A25D55A7D89F7DC7266E66DF38BE8DAE0537CF0DE3F3E438A2C2146355BB39A944EAD69D613F2C35B455EFFF30978FE75369BAB9244A6D09788E4E23C9B7F6002C3144F20B136F715FD906A8D08A62909D80504DBFE37611CE8238B385F98186697B3B03CC0C9C1D35A47AB2D9E90DBD0DF570CE95CBA5499A990F5FF3D3B214862E6CE8A9977A4C8859F1DEF7E5C0004346CD195EBE3E306A75129C00A14EE3B44156157BBE08A7758BEB6418727CE4F9C4397C59240FAB9905584FC2316F5E500CF8D9AA3A98225EFE7E2D740222EA39E5C9229FB746D9C1E0CF262FF824041738A3BC56616941A5A69CA111D74E72AFAA6AF6825EAC1D78AB50E3F96377FFE60D3929744BF5EC7FEB1A9DEE92B911AB39B68145FA451AC231446CAF8A561F7277338A52FD835584969E62FA28F56474BCA9D84EF5C2A232A39A2F6E291B3C11FCDD1888F1CFC38B74B0397DA3E4BF5DB9D9C44C7CB226E0D4179569BFFB10C3DE144F19FC38D81D22ED3A8778E13D110D74C976C54737C9A5753223592068F097E03B2EACFD38243B847A5D28EBA874371BD4B00EA648E3EF770B575FE57247EEE46C5BF912A2CFEA8590AD47A71520FC3E0491F26EBE196C6AD322EDA80A448DF8AD6276F3F33F8247D9BA504C289B6D8F2868924AA69EA2278CA1DF05FCD50CF820CD7EB24960C8F75683356A1238117468F9EA602EEFDAB7382AC59BB16589ECD2886F7E3EA249A6A9972BD8AE6DBF6DB8222A00FDE790080120AF36230907B1F3150D7A6890116DF862D74847F491355A1925524296EDD7EBC65C9078445EE0CDC398D5833731DD61A2DE62A2313021B718626DACA00F53DA0A824F692A46A8CE182AEADA54B44F4F856C29A2A0B59EA0932D32B44ACDA922F5BCB47C7F17A9D457187487F0F4DA56A84E6D1ED438FD6EF7C7AB74DCC0208DDA70E264085AFDF8B60F6F49006ECCB753AFE5C5FAE1BB6DAA1C7626EFA1BA68DE03A7D8F78F2C13F8D407A6749C29DA9875FCB1680FD81078D7BA9BBDFAFB3565A275BC17816E2637DB5325BFE290E48D6FD230F4658F41D885E82383D43392BCECB03A990F14149304F001FBC81494366CABFDF8689086F1967A25DD53D2CB8991B5B99C4CDC80E004E789F226C55FC63455DBB895984142F6A65F0E3956B43CDCC7ADE92E9567C88824DFAE66F7E5B186B3BD28DC8CF15E67F6ED61E40924DDB2F643B186AA11C859DA950D10F7FBE3B9163A80C9B76D7A72C09FAD355BD28778C0AAC14AC8E7121086B36AABEE7CAC8262B2DD54D0D865975ED901A6354DDF9EC3BBA541F602D429A569B4908983B4B86C01A331EBF26242F8344B8B8481FA94E26C28BF596A0BED6BF770BCD63413DCDBD2FD83188ED047E747EF3AD052AC23B3B112BA241541D62FF2AC34E34ED65F4638D99B9DF543BC12382BC8453106A3DE9CF44BDC9CFA77D6C9CB58B5A4E9BCF31283CA6F4364656876EB67831326C6D8023A5F49EE2D3B0CB31414C75A878879B4D65D7098A013637D7922AF6CD348DF584CAC496C917D88B320B433A685542F4D0D8D78D96B548493B5B39557268A28EB19C16A0896D50BE7BF917B68E3A6F7A0374DFF12877CF3BF2805BA3637A0244CB8AAFA9A66AE797FD5D9E1464E0544B1CACA08492B62D41463D854F5314F496D8D8DEF5A54BAAE38379D28C9C21539B0AFA22778FE9A975B7F18277D4ED8A3735EBF25783DDFE19793EC2BB5B3F4A89C4F10B01BFA5D829F473D233EB1CAA2371A4CD9D605D172D969947C0ADDD0FDBCD14270D5A083484B65BA7CBC81C58D7A117F11DFDE13F87926DE078BA4C0CF5A13208BD5FF9FA0A29C5BD29A8829EBBCD2779B33D7B7445772BA3BFA6B63C7FA7D76CC49A34C9E1506255D1A8E9B874200424BA0BF0971E2F482C25E7A58CB2086C04DE629AC91D971B8496912C1A1FEF6DEC4019109E9328B1163B6556FA0431D252C34CA939576E80D56012902A63A6332D2D28289DD69D1B51A431876F75A03A7AD91863E7DBC453FAA480AF867B646AAE61121F374D3B7AA0CCCEF26C83218C0A8EF515C81C238B9D0E34B30A527BADB3F8A05E40640632A4F5A9A96E0998485D132B5D81A5AC135BFEB4C283042DBA37823D160D056EC89268F66EE8F48F6212F8BCD6227C9315582074B4BC70D5D6039A0222646B821CFA3C73C6243FC141054C80F253FD58673729E5F5A1E95CED50857129D990DAB1F51DA043028D73D978E45B5E0B0ABB6BB95F3F1E54511999E9A862B5943AF05A8D24B35BE132F682B5246032FA73C60B4906A3FD68FB2670C1BA8ED389B26ADECADD7B8C0666400668157744D55B316A3EADF0FE6A7B67DEAFA0F52123AFFA59A204C764298FB5EEB43A0430CF1BAF48DE174A12B94E5544ECE92DFE923AFA4A190FC901FC715900AD67BE223CA834BF0B6D52D04C6DDDB0284D6F113B175C9C1F7AE53ACA3224079F32DDF2D8C9D6C213D73A6CFAE889C238D0E817582BC9B153FCE9281CC045748D7CEE32D9FFECF9412C75E0EC9399176AFCCAC40FB5E80073DD2AFF99393F7212D49918EF124D39E1A7EEA9E393C66FDFE0BA0D03EC62ED40B3BAF3A549FAF43600C4B9F65B46C70F0A3B798B5F6F30C47BCF6ADA45D88B7CA386587CE37E347D131EAAFFA4A902C63505820D9FA81BC572A4EABDBA49926C73F7883EDE22FBA8CE0F56A024056809F4D3AADC5AAF4CF4F559BC17F408509A2D79E8204232AB7CD23766D5473C93EBEE639056FA943E0776DB28931E43330649B7889E4BD8DD9E088D40D56F279E6744EF099C97528B1CB65E1E7DD8BFE0315760EA66F86DFCFBBEFE123594AA138E047F41E5CC0D6992566046BA399AD6B5510AE23044856CA7B9880FE0A9A190E0D6C5A3FB45E048B185A86D7B06B23F531F69C535D2B4AFA68C4E28EACB8B0D274AF2D7218606506D3240B6B07F58ADC31C62A09A3667051DBC826729D4EAB41226B5B867ADDECC4C92364A6AFF0F3DAC6E7865F25B40782735A5C8E355EA48AD21537543DDC7CF5A1BA98EE74AABD9C732B0BCDAE3C4EC962CBD3E9DA6E6D30BE157026D1E44C2AEB9ED7A71861872E7FABC25B7DB07FA5E48BFBAED9289D70D40459E159183A2739C9D4A84DCE46930C23B1D898ADFA23D7F7DEACA07D4013DCC499B2E22947EC9061ED103D62D1570A43CD6AF7814D977291679F6062831B5E9E3CD80F41481D51334C384BA5B1110F2E655154609A0F25F6E4475F49E28F3C45222BA8DABE0F33FD736B376F3F526AA2F4D1EEFF55CD6A48AB7DF93EC1F0BA52390780323FD8559377135E1EC229380C4754B9ED95D9EB37A2E62C42B00E8280C56B271139E68E39B3990F439D64ABCE94FE9F834CAC5CAA9E1304B03CE71FD2814FA21EEAF619CDF06A8C02D9F496FA36314D278099DB3C2938213C57A4DF167BAB97800680B452750DD31F025063C1DCA737769DA11F15E4A4A0F42FC1DAB26D8B124FDE946850AF8373EA3BEA4CF059C9EBE5106175D2C5DA8A3D1DE85189CD662247FFDD1D1EFAB717DA5F9CDC5C9DB6853019D31A462ECE81F783369220E9536BC667E28B4FAAFC84EFEF8590554DF1D0FD260D7A59547070DD1742A73B28EC174A7DE07FA043E1D8BD6AC6C3EAD1DF29A8F31E72CFEEF4889FD7093721B70399F5F3B709246E9E9FC7915C24A7A65102CEBA3AC3B2AD47E88B79CE3D1886793E529398441B62D941D0DEEBBCBEE34B52A7A9D7387425C959B8C63F06A000841C02FEB7FA39D9555D285BE3B00B31BE3AB10B8EDB6425032F516C2F7DFF1FDA15A34238E293B95495C6875DC50D4E08DAFD250DFE4FC69845D2655A7F4E2ECD406544B9C6455660C95316B4A3F08D6AACAD4CC9D6A9A6B9626AEA04CE21BC5F769768ED1E33BB4AAD1172B34E6CA0A02460ACDD430ABF1A5FE0337DDC0D96E8AF5660096E1FC1CFA47EC2E0146B8C9E69D9FF03349BB8A79734BECADD3A064F61FBEC0A60DD6F40DF2D3040154E858E871E8BFB365CBE32B15DDBE04B39E34859B2CB8272206C24EA20E191A100FD547040444074C96A6486884B12BE3CC92D1CF3BA974BE608FEDE315440317BD6294FC3768BEF25B9A1BD9B97EAA0654D6F7C45D61A412E4F92E99388B76B58021494A6AF1DC3789DB322429A63E8F0801F23DFADE6955D2AD608CCC2FECF551DC5084565C1B1DDB723D029B2F8DB7719CF36A11AE017F6114563D33EFDE306A0FA91F0A61206410CE3B0E85D5D4E81B1E599CA7394E590559519FB1802207A61884FFC1A567503B10E46ED742E75A0F5ECE8B8932B1B2EEE93910D9C0713E14B0DCFE0F8C8A9428DF19BF33C15C9942D0BE7AB22849DFD8E518E67919F36825724F07684BAE1BECB401BA5C5ACAC40E49FFD472F3BF62840982A7FFD63DA1C144ED79499596D8453BB2117551CB668C89313461110589D6BFAFF2F1A5410EFC7140ECDCBD291269E0BB88538C5A62AF2B662E192A699FE40D248D96558704E1679FA41B473820DFDC24967479738E92A0899BB20CD80963CE5377E27E0B65DB1FD557C9C851B69392337DC0D2276C7C5D6C04406296A6B5C1115BF1C5B017CF60F6DDF955C14803A42095D195238159A4783A6BE2EC9C363B1FB4E7D6C1B89A8788862021E8B51FC1A6E6B420DF60756C3BEC7C3768B3249B2CFB7764CA4E19A2D80FC563606C18DF6D37BD272D5E79DB7A4FF4D4992FDBF0A122A402DF74AD20A0BEFCA5747A765738D760C1728BBC8C24717EE81947B5FF1A0840B5657E04F92EC6596576B4E4522D26222709646C60A1C32D110880E4ABF574608BBF9B70FA1FA234DAEEAA6C0EACEDD5522EFC8EA67A640ADE760120D5FDE8B79F8A9AEC7BC59E567CA209444433B6ADA537A9344D2731C359A66BDDC508F41EE60DCC1217C227E555873427241E94E0B510C7751ECB430AAFAD3E1D170BFDF0F225E2A3B8ED8E6045AF05FF7EB6C690E48AD173FCE29B510129BA51A43931B5A85CC1F96D77D77828D69E468CEF98D7A6293C0C71DB10DB4E655082ACC335316ADE63690CC84EFB350A955EBC320D43959C7EBA3ECAC255E46F5435660DA6ABD8BE590304232CE0B71687F7080B82C68DAAA18D98B7EB443C91FCBACFEC54D8FCE9F42A7B6D028B36484D3D7D1A60792DDA18A8020F2F855032FDE5C64197AA468528034CDC56F51CAA4F92CFCC4E37554FD6687A61F2CE21C9B9F4199B329B138564B24AC7AEB3D8C4C643690EABD33C1B3ECFCBB4F8FE0A9A75D24D1041B946542679576C8AF6792FE8942F5C1D8E4B2AF21D851735B6BFB85750A30800A0E1B4AF0AE034083132A0F9B7CCAE52AA61124D46A8395422882B9589C7F0F274B66AE509F37099B15D5959ED9CA7580E3C42214A12B3626501B0A4C6A1212188B21E347439CF4621C51635A5E3E6CB54F3D65F1B4510392560E35A97D288AC7EE906AC6BF86D5DA143A651A2B88CA915F881A5032B041A689B1D87CC961180BB33B6FABAF963E683EC6898E88A933EFF2ED468E597CE6C9F8F568C36D07F1ED9EFBF28A132E7DBC143A497E23FEE76044A38ABA1B1E00790CA90D2231A0D54FFA96E22BF9FF90B86E7B0A03FB5675A42E3E0BDEC25D5C8332D589664D35698445A11D6A55E80F750D7C9E2FCD01F7AC84CD09609C54BFDB6E2884F10F87DB4EC21E8C4B026E80A6AC41C79A9162902FA4465F6B4AB1EB90F69276D3EF233DB733E32C919982E6159034AA841EE46A5300CD132405AC7D4DC9215CFE7496086873A46BF614CFF64DD4845C7EDED92EDFD6EC9DAA6BC259775AB24CF26C44CD0B94367D5B569AC084D62FF9542DE2211D9416EE14A4922528F9CC3B390A916C42826227512CDCCFC990B93B597C106A5108145E3C40C4EE6DAD094DD220B27FA6AA47D8534203E26365D87AB539D6F639F7292953BA805D450C54E6C24ECA247FE5FCEE8E0831966BD1F1C1169C0FB0A1015FA28ABF1CF70638A4D7FC7E753CF23E45A08BE4C7624DBF627AD1E859793577C2E76988697201DAD7EBA3F2CBE9FD9C61ACCEB88FA081672CE05E8E68D2AF1C76E5B3C7E1091D668ADBC8E87E1E635A688A797BC53C36FFE8AD384BE9FA89328BC9FC94B4F90D4C50A67D42BCA19B0F363B172C5E1CA1D06427610E2BB6C6443F7C9CD7704FDEAA00A0B61405F2E740189CCA8DA8AEF585DF7F1ED83856AF67799D57F065CC33B2353E9AF4192B08460D8CA9E555C12DF8D1F3B8C0F58D12BF7ACB5EF1299EE0356AD967B073A247612597228DE818D5941C84CB0023AE8ECD227F5D67BAA96DC0604C34ED0C3A48A7999FAF18351E7774DECABE091D88453CA048EB0F6CC472B27EBBE12552D2D52F30DFFF4F16D8D5170DB569EFDA0BC0C8C60F5110C67A99F15B46908A5EF70FA835B544049F161C7B69B44F198E46FFD928DE85B89F3E523A2ACCA0E8D5DA476C189FCF86F96947E3B39FE908179967BAF002797861EB09261BE629485C50C6D454ECD5E4D63041DC8DC8515503D1A6F3FB7D5DB680CE8350D88BE2A6507F49FB97C4C44E81F7A217DA5B31C3029C6E15C5B3F7C1C6149CCE0756C4130705ACE06892DB451D37949540B4C0B54712904CF81C59FF0D81DAE2AA8920AB3DC087FBB1056735DDFAD592FD4C000CA1E91C39DD0FB2ED42727F680DF5973DA42E22045952CF0A91864642E4AD9865FAC0D4F95D4EB1D77E05FA31093F5DD51AEF75C111F55BB20ADCB2B0B291BB61E2A571A9FCC12909E8A7C74F9FF6D176CB8067D77248EDCD4E9F4E66D0815334BA1062A60542B8F52C4CF98B51FFBC861B293680F4D3C00F466BEF774ED27944BED76D368EF6511CDD0CCC3399F81486B85C4804BA2FCAFF0581A9BA14F40E3933F4B10477136C8B9AB935B5A6BE14ACC2C90156B9FCCC996D843CBCD714B5D5F5230EF58AE9BACB5BE9C2A38D09574E07747CB592A02C53CF5C63924A60A1669D7482C8D1CB9D2A848675E813C41862C25B649B1AE18DDD5C70D9C99A1918C9624FA55CBA82EFA382E3E4AB72B272A59290494A795A1B9ADC12C6AD7E5DC1B4923578D27A5E625C73D014B7064AD4DB50D8DFEF431A4F92AC35AD45B71831A058E3647451C7F68C1CB06970578B002433A8B59DE98766A7ABC9A8380A12E687D76B83839E620CCD34A9D672485DF81E97D20C2814403BEE32684B943001C76B43A9E7F755731F833CC34C2D6A3F7FC8ADEF894B24915AB3F8A867873168A8478ADCD48C135B00DEDEF299BD3FE55D71558BC1180B6B243D15D24BE76DFE58195B843C4D020D6117A9031C0863767578A51CE32F409A16D2C633A946CEC698951AAF392A5C1C405122B86D48BF518945754EE3CAAF8CE1871C12F5E410789FFC710E713C1DBB5650C0F035C3B4455D2DBEA8EFC9937E5F1FDA6FD73E87EB850E25AE2814FD7A63F309646825B98E348A73F088B179192B24E1B47976E4E01782290BF7153087BC92DDC3DC50A5D639027B5E9853B24A6892E54516C4EB189EFA19FA81E01EE6DA588887ECF36CB8F62A33FA4C03E0E56620EDFF6A8A9C0A18C4AA7C36DC1AF82E8F472EA396596BB06299B525AD95C3090259DDEA8C6DFF9AB266F6295E30EAFEFE20C612FA2887CAFDE136D25E241354F4CA0CAB6F08611184551EA1699FFDD0D1B97C7BC45A828C4278CE720A67233D96D3ED5555BF18E38950FF61B01E3C61806C770925E157E6BAA6E60EA3E9FE576083EEE3187D053040494BFC50C2BF6FE5454C2CC809746156FD1A66BD5693F78526E0E3FFE8680C986903E42AD2D22F5B4ECD9A2EE2F662421AE44C82139B4C99DBE6C0D83C2552E125EDED45EE4C8AB468DBDD28C351958977CAFC72F56B2A2DF920D956F92800D97D68B4B40A9EA90E089132DF96C81B4BD9025B7930029148BD4BDCABDB760876E4C2497896A88ED4FC1CCEC183AAA8FDB9766C9095E3895B43ED866213DBBCABA86BD9F8BD8DAAE6E65F9D6A00121122763072500E3DECA07C39AFB15DC14E3DF4B78F86219244C2A59B19BE43871C0823C1A6DEE8DFD104036F78B8FFB278C70D5090986C5F3A4DAA3BF9B80239F1CBFD08A155DD37277667647F4EDE2F33ADB78845F1F2CA41241F931EC6CA9D39BE701AC80CD58774681F351ACACF55163339E706C80051AF153806074388EEA1BA0F6DF9D61E1734E5BC96B20B2A03EADE9F5D34341ED0AF7D1C3A03A0436933E0ABA490C8B18755C09AB09B9BFBC127A9BA07BDD8B64C5F19239468E34BA64F8A73377E382CAB133B40AFA57286BD7C990574ACFD81F879F6B5DE8209F5FEF2C0B0410D351DB5E7937E233744FC8F1DCB4E34B023B8B0616D7377CBF6506BA485966C78C975C7E9A5A57479612676561A1A81EE31899661620A999D385C83C5BE3D7C094B883052D3E51350AE2AEFF3B70CD79788BA2A10AC680C86403DF5187133F1CAA730A6FD8FC5D825FC842EC6F3178A9CB8D378C779D94CB5722929F598E7122C4ACB9C815DE1D9D3FB012A4429A4135FFE30DE26E6E6CE348A28552CD02F3BD76890E42B24CCE80551B57E10F3343CB1E22740D5DAB48D63D7EC310441C3A03EF2D068B89411D02F795B6161E05963C350B4437F7936C4A3886BD1BFD547382971F83FC64FE405DE43E97B47FC3E6A319B5BFC8C3462D327D9C017867AF4B4AD860E5149B67738008A16EEF46168753DA6B87736B279E6AD772829F074698A523F3C28444C4BD75C66BF385A78A58A2ED8FADBBA80E8F03B391A00FB6C53666D46EBF490394A2667A6E0F611EA20FC6D35DB53C07FC947D7C9975F141702C922CB92807D137A082C21AB23A53D3BBE7752057A77129AA70A6AABCD20C8C6865C2718FBAA92C25A5B09C12B5078657EA83F71F9A5F7F30305B07E20E71BAF024F174EDD64CD556917C663CB4596C05FF4A952854004F5E29BFC45DB30218CE050EE07EBEE4DB68B6ED2058B2E7A84607018689C8750F7D69B6FB50D0A1141785F88B91B9A465045C87438537715A9CB6A3CA91A2D8A097B13B883ABE281044487E42205DAB197D526A00E3D0B10F04E8E42D7DABEF50F8DB330F93EEAA513AC6C649F7392256A1F980CA0C3BB252EE86DA581D895D992D419C24F2BB9B3E8F55AF2C2E47ABCCA94D6DDCC7C774123687147732B661AAC17CD7F347CDD0753D1C074A4329D98000F648528BFD8E0B010BA56D7FC3A6A16975BEDC90CBAFAE757DC5626C6F0E25F04D762C22B3C9934A09E4BEC3E320E3ECBE8A9535319174F764FE1347DD0C5434F5E4516FBD1A548225A2568C8B97BD84E0A8EF1D1FE3A9541AE5F1973EE86FD8CE7BB7752214EABA4B75BC4811AAD5C1C561391F9CE6536F2C63EF8824EF245B57F40F9EE94376AF6B41B000B3E60138F40FFD8CE8AF624E763B409A210FC5416A95A0242494DC1D6B76A0E3040054453ECFC2018DC4BB4603DE162E1DE55FE1C4F2E99FDBA9F859FA74FB0989840746AA18A07C25C5983B7A2231E7C4EC4DA4D5C190975460754D51109453DABB1B2D71EE6F7453E55D961AC865FE18CB1D9AFCFDA327CDC529045BDE94930AC634A6F9CA9FFB6DB8DD430631B48F499B75FB2AF66D7C9C59DBF45E3C24F61178B5D8802A285A93335561D51FECC572011F3FD4FEDB73DAD314AF3E62AED3F43EC53E3DAC3C41829231A5AC37788A8C41E6E168998BE3AC0B57228F2F37E52DA96518D989FF1552222E29C1DAC74DF9EFA0B5282FD5CA0BBFC34B224F6DEB870EE2DF71592DA2762316D5C681A56052295DA7CF89E01AEE534792FD671699DF819498AB00359417A4F95A259DF094161712DD3B5DA16EC9D9E71E9E5BB4DE045EB61077233B6755C5CCA04578D679C765F6D7B090D83003C56663C30BEF964DF8E5952730201C929251CFA37A7455E80D72D19D0E22C52F8A7D1C5A7632A1324A44634C9EB5A2D41F09F336916514BBC061091206A19FD9DD831042509D6DA9BE7BADC099F84B27A8970BB62930366D1B682001E9708C4F819FBB5824C45FB7C90113636EE0BBCAB66F4653B9DE72459727A4A78B503B57C5EEA7463D40E42EE3F7DCCE12C9333F290F114A20153A1D977A0A108C9968418E1DB71DEDECB7AB965C485BE29294F59CE804782D91AD71BBB3284F15BF4F012BA9451A08EB7A75A1E266303BA4D71BD85E3132390D42616212D3ED6BADBAFDD348922A616E3D49602F09A965A1411AF93EE668EA1C271B2DCF9BF8E46DCF252B40A814A7F3C04308D1C1528D7DFB555CE1717A6A29E63FBDEAC3B468B5F1EE2E10A2281B01FAAD3DEF9189C585EC2DB74C516F2A9D7FA9DB52E573A6F4D337546A6375A10C9A0306BBC4C6F2BB16BF65A87A0DD8DD5BFC8D3FF13774A2055E5D112273F12A21B5FA1850B932884152BA2F4C8B8A51B7F6E69A4069D09739302D1F03B6E9C2411A2639B85F3D676C28C480DBB3117F94A0117E795EB14390231A9F0300536A6D8E454846894D30282735A17676688CB18709D632D3B8676C7FB96887BDF276A2D578D6CCEF5ED099849D384286EBC9B66414125E561CEA6A234835E2CBB890C75BE0B56DB35D6ECC618EB4A4E71C0F20AEF5F414843C0F8ABE9B7ABAF51A962E352A19A2FDCA53A31D7D3D9B5AFDEC3CF6051143C53C7C5C8E2C74E9C00D2AD6BB0513C73155E6B8EB93C3E95B30EDBC07608486905D210D60CF32B82B26F211A44B81240A63292271A9EA581AB3AA63A9744E5AF880AD54DFDC292AAF8FA83920D02C0CE1689E3517E5498D694AA91125F7C39BED0C175B5ED9DC2680A4AA97A8BD86DD544AD94FD50FEEC9B1B30EE72D155F573CA41D7E3C1D6D954E23AF8468C2AD9303C4B36760DE418F2490D4D4BDC3E7FD98D9865D728702F9B0A74EBFDFC69EA3AA0E67CC7CF7D12CB3DB7E733615523AFDD4C23C4BBB045116680876FF2BAD5DDF239B9E6F32412A9D0FD918BAC55FC2745CB8A4A2AB4A4B663C6A941683478D79EF66E471A4B30296FE8F888C6F2FD42E73B071153BD93AD9BE51CD6C9588D32F8BAA5F1C61AE856D57384CB498BB5A98610C2750791CA63C1AA23F3007F6C4717ADF629E84DB16B96F3FEE7E7EAF8ECCD886DDF7396EB60D4F6584DEC21B6A2156A1AE23F20111B74A9D6A45666727464DE37BA0FA409E9F4493E4BB02072EA02E8FA86FAA943E349DE25847DD69EF75C54BFEFEC6AA276840B7F5812BA98FD71F5DBB0680FA9B9C97E84570A380F674F025EA4684FD6360E35DCC7A1E71E822BB585C0486AE26C313B7898112F33026A6B11B04472321E05961203FB1CB68028875120C072F4C3CEEE18D156D0253BFA782D8D6E85C5A3110C9BCF5C72D45AEB42580637F78323687D95D528474E44A731DA9F385ADB3C7451A97BE5AD9D267BFA1D3F4114943E4E28B649F9E48E66F1CB91D67D3CAFD87DA4317DC7A49AA4494E26751F0366FBEE3B26F6AC04A5DB9C0674520D8BA30EE574742E4E8829FB11CB1E920BEC31E72855B9FE2D021FBE3B6500E7B7C567037CEBBDC3F92CAAC3146E48482E595B0CC66E978BF153EA59D642EEB1D9CAF0EAF1899D1A32F1FD4266A5120D7E87B0FE804843E3EDBEF4C1A74D394F8BA9E10CC50F69445D97DC723CC4477C699BB29D27BA3B96CFA57CD9F7DA86D92B92D871D0875969D3BE45755997C3412A46E3DDAC71960DD60F9EF1AE2BC0690DDA4AD21721322812136C5D8FCA1A9AAA0DFA5D1B397B8AD87BE85D3274E973D8EDC7086C93E0030AE2F2B70D61EAC6739CE026AC4DBCAF979CC104F547814EACB7B91ADD6C889FE35FB2EDF57F364CE26745888D1F10FFAD156B7CB324140DAC5F716F1D8FDDFF18159B4A1FC7FE1525126503CC3831EA94D7FD1E092FC52A1A102C3408E0905C8375138C6E8CE8E88E544F546A33369FDCE1BFF151C96F6867782458799F545236BE234F93C4C12B1BC0D7A148F06687256E05501D12D6BB1E11E0393AFA24A87DF5D0A9F8FB065984EF0BD2E8A25F3C7164A08A490069BB0AAF61D75EF542FF54DC8750EF16F9D60BCFEC19FB2F15314054B3F06724E5EBAB6ECD2E703EFE24839855509DB9865CD0B18C37A5A87C33D4A1DCDBECEA0BD3FF00A6111F5D9A72B56525EB80CE4A6B17A39D1A4B1CF96D7556AC97D5450A804B0E063ECDB03F63C8AB181FA5C657AE4F74E24C02B27F6F7E709B6338F226CE690604C633FA322653EB8CBF193BC5D86316F10AF9E5048BE625805154950234F8FA657ED20393787682717C0A772FCE694F32386C2FA35AB980477B9B97C0462F3FB05F645C2F0B287F183B720E3D94C1A1AC11F79A42C52A319C25D6088E68527073C36B70AB9DFD512F9E8C4BC278A127FBE4733676090E072A2A384E446DF352FE1053AFB564CA65CB44743BB4DE8FB2A164763CAA6D6616F0F264A7C7ABC4A6F64CFBEACBD07C7DEA63B2CE5F31CD608AB38C1E10A0E081636C618C88370759EA668DBBAF53D70A36455EABEA6D11030341DD085FBB9CCBEC6D502011D8887261A58B6201118FFD45A935336BC54D75A8797B9239280C0059F6749DD4B2A6410FBD19C3CCC04A47E92921A12F02BA85F5DFA15626BC0C58DACAD004BEFFE2754E7595B008793B13319F4EF1228C97248B1B12F089839FFBC1E6F55DAB9F5FC3A9A36E19208CA7C98855D7BBA14EE8D1A6AAADB559AF3ED28B7C43DF7DD94948923B3D24B6A67D6FE28F512176AEB54584FA519B2DEB358091A9878E0D76B1BBB42B471542926519B33DE96E9738C7402B2C8159F017A0A69946F06E546E668B653114AF273B1430289225694EC60E0E90064D82B2D2A0071143CAC21D09FD7CCC72D33377D2FA6EE7E1F449AF6A64B857FCB173106B84702B998AE82703E2854CAE71A3BCC2272F383265B944F243CE6178D755455CE5977E08D870F35E018CE5598797BA89AE1E04EBA95A6769026B9E72A26AAB518A6C06BF04378DAF29E05765841B4795D34B9923360333C5BDCF95BBE472078560E059846EB94CB29CB7F1F4F03F0ED1C7AFB4EA0F85863F93CC2B9709EBF8200F93BB0C217E9313E5F0EA6CC7E085AD740040BB2BC45DF2486B9AEA8B4ACBBD4D611578512903B8F5E1A269A0586419CA500E5911BFB3B04FC605CAB9D30103DE52E3320E52044E2C049FBC99DF4B43407CDA75438C5C24EB92B8FE96F6805AC674BA0B0979A7D1D50018E241DD86350C713D670A837A2D4BBACB90BD87861EF4E86448F1A55CA2EB752907038246F65B776D278135697E36506169927FCEE37FD02B66D9C662F49C12A078271123B4F834410356E24213F089B36288518F31164207A7E56997880BB7D4903571EE0CE992F862FA111C7358766944864C5DE335D03A1F591180407A7E6F241BECBB8B2964E620AC692E38FDAAB63655D99D30773C9AFF94AFDCDE5B185E51DF54A5DA08A5C7ACF115B3E14E5B029D1EB31BD2179409F6A014AD23DABC7BB7842FE23C53F815AA142685F867DCEFBB136BA816F885F4BEEB1C5A7AAD561BC83B642549A92D6631B3212219376B8A3D6C4237B4DF32EDDE266F7B98DC373129CB61ABF728862EE223A3BE85D92BD816F029664FCEC16706C473E1FBA8FB11D64F5FA3192EF06EEF1A05C7FD8BB8534FB49E22C55B8B6E929ED7A9F25B7C0674B39970E3E5A2BFA32764ABA1B85D83D2660598854BE1AB2C49025293A162BD64BE77FC924516A7E8FDC3F12ABB70E979EA2428648FBFB24B222CE8B10D8D6F659A17D0EA97426DD2FC9F6B7E91182941389CA001AEE443F6ACDCD36A00DE56B960FDABC882146378911D1589C09E41A520AD005A9879F4D2EF1522EBFAAF66831EDFB3AD76409D0A6380A18744C7D2FF5E718826075C2D6C8F78F52A29DDAAAC5901770583338FE4CF0F155CC30B62D81FE55845B224CA0AACE7BA7F2EE12E06731B5DC238B41628C14567DC29AE4E05E160D89A0E9B2D720D6F4BC0CC0C81C8EBE5CBC4ECAC219E4CD924826F0E64C72AF8A197E8AD4F48C66AF2EABADDA1B56A55B01840598AD979D824FD9A2E2640CEE7785395E1A9A9EBE54128CBA87768C8883A89F3668556E4D252097ECEA8F2FEAC4541D7AF8AEC7EB59D504E3D7FE360B468014F1C355BE8382AAFC2E6EF8E626DD8C385C7B7FA9CA56A73344C2841ED9B21338D3ED485FD9D46B3FB6981A3DD8BDFBB6854FB019BD3181EC5494E0F482385B9DA1C8EC8EC4A2D8221A32F098A784F4B24D810174029519DC676C1333AFC2074F9F94E13EDFAB391EFFF9F36A4C5A58CB6916D51A7401A0E09ED36713E62AACE1664CC3D99A44E40404A5523EBCE2358D087256C3F6E06E5A74B138CA6C94421E96F35806C160FA0C05C3F3A66BBDE71E28C5E4000F12A641683DA590F8AB914963EAA2FCB3AE6C0D787FADF448C06FE41DEEEFC3D207084AAE2E4D39E110F8294A9AA3C537482265168B968EAF40AAAD979657153C2020B3E37859E2F7B9ACCC2BB623286F74631ECED132DD6540C364B84572355B32AB596B38F6620EA2365348607BB0D3F76D5DCE048AD28434A79F8A4DD2FFD5FB23BCE4FBBF19DF8417B9F339EED060048413D21184BC00C2AC9DB38E7F9DE110A505C368243FBF6802932D463DBDDDF43F359CF4408BAF8C72FB549BD43FCEF0B4D83C0C18BBEA91835A2735333F4CB18E242D0848A098D125DD8EF095472D0254C7448423A2EC7CC95DB31DD05EE152DA321362ECDD1A15EEFB8DB0712E4F616C38105601185EA2471E379048473FB3C0EEA601EE2B7854C27FF2F6641ECEA55F86A2530564DE456A67163239B9887EEBD7C4E5E8D77666B6DF757D0DE1AC594B38C793DB57A79F172A02F456D184AAADD14FF4112331F975AF94490783AF38B63BF3B77D21014D8706B0B3FDB1112503BF2EE61EFF6BB38EF0676B93D2CC22D4DC5FF10BDA94EC00F64EE473EBF2BF1D58CD2AE9399B0F40A2128A9AE3C984621919D5CC6A3A0CBE615DA8D6485CD91D943B610425B3D01CC1705DACA66A6678AEE6E4274ED27773C2251B85ED44611C332CBFFAE6F50F530A81112BB2CAE2524187B1B27930629A979695C6497FE68C8F33A88F420DD6170F526FA6168C2BF777E06B9CBBDA2871070EB196C31AC6A6EDE4C23E85C40221BA64C49AD80C35E71E8697A2AF201C88316E0B38AD1FA7E432C358C7C49A79D7EC725E00FD73C3E8DDFD83BECBD838F74055A6088ED9E4E4BDD5DC54CEBFA425A5AEC62FE0221F36DE9CB188BE2DA01D99C908C0AB37B186A9D71964E578EA4A40DFA3650F83CBAE198CA601C0ED8C1F00A59E419196D6E76FC2C88C0B037C1B88A8624610E167FD9551A1AB0DD15EB51B67BF3259AF9043409903EAE5EF4AA9F43126CCEC884463BC92D25D6BB89F62D0ED28C73C41C9AC0B9D0812B369876F2024B60A56FEA2C9FD6D172359247FC0E3CC33E3FDC0A58FF9DCADBA4929AAA5344363FE60262C292D3CD55FF8CF4B713038D77A4785C25B24879A798BE5BDBE4EAE96BD184396C0D3CDD5F49F90CA6FA79BF02F589E68D2DA09EA6DE612DEC4CB1DFD25E3FC12B4C7D2A31D18DA04DF7E31189C526A063AAB479385BDE172CBCD1C8740670B1C2039809B44F601846B7331A158CD1B7FE7783CCE1ADA31D50AC296A3BAF44F2E53A19858B1F4D55A9BD96D5C9B62CDA1432BC06A50E44FE76C84FED8CBD8388AD3648A17D1A5848E49237A79F30A4B585433F0F41904361AFA046AB55624AE34A03D89DA786BF7BAD5548C402A9C0CFD90E2B0DFF8C3224BF564E58B6D20CC2B6B1C98649C919E96F72BC2A8AB0810F27FB25F673B1EAD0CF69AA47021743FC06179E0AEEF925279BD0784941E2E8B52B35C50621AD307E16B416457769B49DEB15226F81C2D2BBD48151CDC46BD234B3A883C501D3ED69D344FBB202237A6CF194BC13F6384D0BCA9B75BFA7C392FE755AC7581263DA8B147431E5391084BD48533ED696EE35D1B834DE02DEC88711B87AF1C5AF621578CD417964AE97CBECCCC7478E851945E9C9494877A08CB0E39655EB17B400F347FE21E107F30B3BEDAF542249E0B11C96EF7E571CC62C4DABB1C754E6A327C628634F697049EAAC4027EC6095789E608D2EA41020522F09327338DDFB1B6C4989EBAF15E390C62E8A7DCF20A9DAF8B010C9C8C3FA48CBDA9B04D264EF7301BD66ABBB44353EFA90153853626D66A52C69659F38C984B8A7176087085F2C9F2A369E84EDF4B587F45B371B35EADF944CBD4BDA42486720E94C7520564AFCCA9A4D621D2EE9447CBBE5FB0AD4B0734682ABC642679D21541E1F127B74FAA9910531E0205D7935A8F992C732DB096D097E7B74B2B7D6CB6E856E9B849B6B1DABB81B86EC25F8448F7198A87B371A00AA8F39A24A2DC8E4C4CDA086797DB9C1AEEB93193AA3608B773C9F828252AEF5A21E741CBE9BA9DAFBEB08EA70E4026EB2294E3FEA573A622C8E9181BF21AAA816D701F79A729D5C6187C3FBD68E4A3D3EDC234A3F4EDC7444FC164596C99FA2F6DBA47AB78A7C0CAB00AC41C5312EAC472D8C12A3013FF2F22BDB982896AC042A460A11ECEC68794D9921305BE1D558DCD2DAA85326F986B1623A1BC527BAE3C2F75DF0B3B01BE1DEFD3B4B403B8E738ABAC28429B5B4B056BBA39C052B799147DFD972E5308A14100DAD9EF3BD59D9420CA78F164BEBA4E4936AD70D05E6389A5210C7793160FB98CC74459450E8FDFA73DF636933FB3CD2FAEE282379ECEFBE217BD9FFF0EA9542C0A82D8A1B18DAE6EF65CA3D64A3C22B014434C569D6078DEAAC9760526BD9CC581304C9B6EE36AB0D268DE61E4742539EDCBC0BF6949A4D6280867D3273DBD6C2ED687A3BA126799E42A4E09A943713AF258B3DBB0260594FFA00CC6B91ED60F6DB0572FC8FC49EE3F536728324540DA237CB1C605B47CB4AD1F3FF3769851DBD19F3D2BB19E4CCA90093805D38B69E487DE2065CFE7D8F7DD47354D45E3C24D124532B040A6C9DE9E97341BCA68F37008A2899D157AB935039E73C90B696ABB0A1141B539BF1B1EE6231B04DC868D905B3B127B79740A03B5626EDDD5E23881EB51B93E6A8B3405700327B1A2A43EE5C06D44314E9185A47F88CCF7049B429A1C080A33C587CFC7475A221B69D7CFCDCC01BEF4C2D06F2FB62C754A6D7E6335DA5728C3FBA9E5BA1A2C6A03CED031EEA549CBF9E6D1BF89815C3F1C6FA1A7B2CB79396F41B1F3C95E1F6BA9051D8864238DDF8106C40161EE9E3EF02A6F709996D27E6289C7654F18AFC44E29D393C72C77F1CA54A9EB6C819148845F2D9FF53B2432A49F23B4B787527C8BD06E98EE5CC1E497CE864865B3B778ECE97E1E7BB61F4F226AB124E7FC3614F8983768257120338DAADD65E1E883B33AF9354753BD3C8092ECC827EBE48DF0E881ECDFB3CF672C61FAD79C237EF9D951D24CA9A1536E9149E38377A077D7C8EEDB7066D3318EB9873B5227E5CEF60B363F1B0990337138C44697A3AF9A820E7E61F1B5B70DD6636DB7CE969253681E97403CF3FA4458D050EADBF53CD4AFA340F951BF0F427033ED32FC972D79EAA9CB2E70E3B301D1B02D9BB62FCE77045956B1EA78EA9AD337CCC738F55BA528C15F52CCC396BA7283640CCF3DDC7B6EE0E7AE92EF7863DDC68F9A53E53737C478015949B7F4309321E8A611F43938B8A003EE34487CF3FE4B18929444E51EF0535E75B8FE0A3ECCF51753452FD50BEAAF04B573686258AC13B5592560E4C8719A7D728AB2164F9C05A3FE98C2CAE61D28124EC8BFD77696742F2E206AF00FC73C362E5832B994C4892BBD66E4D7D821B6056406F2703E6BA304658476BEC26DE30E5C7290E68A6CC1FFD72DE8F70D478A475E2F2561492BF1DAE9B936B1FD2887CD305B9DEC5DE0B0CE1DD8AB237D0A43E49E20719967E985A6498481EDB615C536E842B27238704BDF5146692B85C05813A960074D73C23FA118C00D7C4F322CFDCD6E87B42CC1FBEA04454748DF25F621206BA7717932701CE3AF61E1F8F899B89CB2DB63C76FCC8757AB71CEC6430F8598DD612ED24BCBECD337CB815C1B60E95478754224E45DE66CB09F073375F95025EFDBC1521BC229ED214C311460B801DF09356CDC3B4ECF70B9A9BE4D4E177D294AA11DC18A6EC10BED7DE96B20CC4296F74B28C6D79750CF439DD24C0965E557A2DCD447A4BC39AEC13CE4824E7023551C80755BDD9CB95459280497F7D36AD9E64ADDE98617107E1800D01A6DA9D3004CD4C0A6472D9F4DC9EB59B324525EE27FC8AB92732C65FC425812874A9EDE27781958B18CCDA9A9631531EB603E43FFF6CCBE442661901614658FAB1A32A0A4B6FB6FAE927A2DBC187741589E339EB259324BAF8EB23F5D8F57385D0E76FF1AB882C9C444A8B19FB5614A0AC2E7238B70FC6A01C7FDEEB1178FBC4B91803C644E0C732B83F0E2C8A68441AD0E6431FA38F09397E7C0E4C88041DE808B588FC2DC46384F720FAE1413DF6FD8387EECE2D19BDD816713D93FB37F0C17B1BB4DF99FF9193BEAAA6A64D59A156375DE808D0E571B312129AF96D4772F66356019AFCE14BE3ACB6F8057740781AEC92DDFACF1978C3D97DC2D3C61F8CE5B1F8DC283990A2887807C6B667C45C3D41161CF392AE58D6BC555E1CF714C5E010FEED160E52AE3E36D67CDCFEED00BE1D01D4DC7A87C8E725250AF38A5BD29B2DF62FF46B582C1A21EC08056526AA08555387E2DE9F599FDA07F34ACB437ADADC2C3DBC1054AA7FD6C24818C88C1E154C3F74315C7D58374199C7CB7FA945A8C6A4D499A91F5C78DA349325DD65C5162CE092ABCF4072BB48197AC1FCB5386CC74CE394DCCA72912FDDC66B5C3359D898A86C338E6590A53353B6AC7D136AD8C8AD5ABEA78DAAA702CC8666CAB857D0495A2CB86DE777F92E944C3670CB265DA429F3C256447F75C45728E34392E9F767A2593000FCE63DE65E9F457DFDDB37EA3852E790AAA01CE7D1A3330C7B4AAC9111AD9CA8F60C43468DBBD7BD697694C074A11B5D855210AF868D1F546DA03342F8B7215EFDCDB1209EF3A2877AF85BBC59AACB4ED0943DC3F65379EB573B5FEA9295DEBB9B4C80C10BD1E0E8033E9327F22C7363F48EF59AA7BEB6E4DAEC0C4131D7B3AA5068E5C513A5FBC8BECD96C4A9595EE4CBD6FE5BF8A35CD37782888F34C128940C1BC058374493575A8323EEA9E8D94BD9C39AFA20AA43D1064270CB88C3F4657D2D4A5342C97FBD53EDDB67AE45C429BBEDE713DCE11D05849233CEB3893DA21C26706C636F5D79FA124B10C8F1BAFC87C10AD2CEC4FED3CD3060C3E64FA6AC1AE19A9A1F6B932011A72D54B2E5FDA46866A35FCC887A72B8BFA60029B79FC5CE497596E7667E9A50B13968B1378DA60C40020D2EB3550DF1A31DE6F2F6DF7EAD769EAEF838371EC4FE3D4E011F3AC68DC09FD951FB458E6D3624AB4F61FA17E3CC62642690CF9A3481A495F32CD8DBA3CC07A93983801B2157306A247A6B274214506A9D5CAE0682DC5B4B41912C36D2840C92BE6B0B45E2A30C7967291E939C61374E0FBA8D2D3C80A1746D758D609D88EC42037E01F95A74708A2CE74C260A1DDD632FD1EB28FE4AA9B97CA30AA9DA645AAB330D38D25BCD6EE669D435206603EE70CB9C9A1668A81C486511F273B2430C335CB5D8B2735F6F57B7B14A6422A819F5BF7FAA4E7EF8119AA4C40F6349899B86CAA55CB7AFE3CE07BA4D766A8BE0DE7A17809DF133C1B7B4A5C08AACC329DE1C58DF162827712CD4FF5B6CCF2EFE81A8E9C741949BED62F26C433382FEAC9FCB999C6A816DDC6858B3F97CBD78F61BDB4CD68097CBBD52BD8FF01D02753044F7FCEE91268C161C8B75203AEA18AA288733458FAF03799800977007260F59DEC3B33D0DB439D5E3EE26D466FB526CD9C445E9CD15386E5F0C290F094902F7E5632B2491584B4C7992304E6626EE0A13EE1DE47AC39C4D87C0C1AF327FFFBDC0D2C7013D6551E0B6DC8B2E7BD71377E9B9F989A45E446B50B3E9E7712434AF8A717E9314C15BC08E4BE9DFA09E2580AE225BEFA4FEB4B3298D0BB34EBE72D7BA20187F438180A152A5E762BC59B78CF84C56E2798CDA1FF2DFC4B9F3838FFFD517F186A7E82DA7D39664CD92095FF3283D0D878E869E57CB7EE8729541F32ED4D420DAB8FE17A605D419FF85AF9A447ACB15F4146412CB60E851793912BF4E791CB3A854EF9DEF896F74790157742FABD93413358A79EA573CE293544ECFA2F96A0213B361B60450A385415995A329ABE0D67A9DF49D53678BA449E9646B1C2118EC84609D427BB8C849B3E4CD8FF0E66476B34CE34C9F28ED13D166ADA3A21F03E27499C48F856D50E98E4AEBCE5C2FC311873FA111791D36E5014E6D65726B5A345F5122374F6892650AD2A16D080D76FFACAF91F534E71FB2378835935FBF19BB68C9617A3CB9D3053B3796CD247A8DF7CD9CE38644B71BA246A20006C13632BF1D17FC82FFF64E9C52BB71DC8ACFC4065FF4B6822B13BA324C28D2B036C2410FEA254D8B29DE05DAF8F059A1856470A0567A16052AA4FDF8BB89364874707604237D1F305F5215E9C338D9812819D4D227579821ACE8E41020BCDD371ABAD3E694BD0B7EDEA22B763D69A1665EBED0A79213FE462BF15001F75D1D6E2D9EA2FF1E39F8EED634C30D881762F5450C2A5DCA4937D19290E76AD9949448729E495CD874D3516EBAC782E16BA180D7E222F65CB9F33F2E5248FD7D6613C7BF2ACA22EA01C7275C52BF00F07D520E06B8D4DAF072FC6AF22D62DCB9DE955CAA48A46AACBEC315B9AE66E016C284799B5E242ADE11C82F321CBD91B6B938D277F334500F82B3A0A17A0E441425C2F3E85EBE9F4F30A71833B0EA42DDED0AABA73208882F36B009B8B37F23FAF52DB3DEDAA9011B52786E56B5D8EEE8D2092E1086EE3CF2FADE2493A913C6EF41C65A40D277AAB61A1DA7E63FFD704238F2E81A24C9FAE0B5841B21EFDFB3544F40AF683DBD40BC12942D4064303D16F6E5E5D4323371D6E52A30BA043005022FDDF2CBC6A6147FDCC3416004FF247FCD273E65582ADB8ECB8C8B85B28BCDA13798993C7917E201275D0ABC3FF382948606BA79DDEBE42FC76730A1D0915D18BC7A4DCD5352C390A76AF260E6F2857E5880E0C996F0379F584FC147F495E576E60CF75D818D1C174C6CF38DE21EAE0C155938473CE4E811C5BD3F7ED8ACECE43EF303266044DFB409E33D3A343CFE16B97F9788C6FBFADB6921D583A977BF6F08CED993C506B62B0C2880B13ABB79D557E78842E7834AC67D38F761E3FB5D7E18845A7522FA0123C5BD5CC81D31525E099050181BF7D8C7672E5823A02E948EE6F7427309570B944C717F1CCC214AF63CDB0D827D58423AEBF63B847DBE51CE4E7C97D1FAEE901F73768F2AB7097911C1B8DE862E5D4F39F7E86051C9736920EE03152C4EA1AB4C7B90D9DAD5B1F313BFC5EAFDA4FCEEA7CF0E5DD68551EA75655CF8C89EB11C5285C7CE3B7E612A44CE273FD1F7538AD9AF541D3F859EF19F51159216F110075D03443DE957B622502AAD0C7678C41CB70D5686DF1338CA98F69B95C61C91F7CCCB4136F0A6E47CB799116C92600F4F1F3243DA8BF83DD432EC84A31D8F7A9C590A11985A329B17D0A221C4D4A72E6C3E2EA6CA28414717CD10A7E97F98F525D427E68A6B2AF96A93B995DB64CCA7A96CBD8E64F7002026B4C6D9C256F6214565EE8EADA590EA7733558DA1403F35424D6E76EC4E6FACC110FD53267E939849C6A42DD904E4F7FA2894FA23CF833B244B46BD9CC0F635D9A0A11F76669E9EF75F8FA9B925930C11E1E55AFD36B3546D13D225259F49E18B632925591EEEF0DEAE475888C59DF76EAC3DE040762D300EFAEE846F9BE5754268C766BB1FA1A2F064D8F353E9E31BADAFA391038FF1D9EAD1A46E6ED7CE6D26E375E9C9E3783085857B2EF1E7D4A3453EEE66E721DA569C0336CEA8389FE0C72FCB2A84698A935F9BDD8663C9E2453508FAD70E953ACC58A525255B6B0D6215771FF5C6288E3182CF4E69AF9D930EFF91517781AE31E9CF0E69CE81C7FD388A12EC5E46DB71734AC1AF5B6DEAAD47588786F5506D5A600E3FBBFA58924EF3172E8CDE63A12C3FF2DFA2AC6F6E5A4BC9799BE88CA1D26B1B775C3D36055FEA4F24D8A977098C33C582E782E4107AA8BC6F3C0BCD218353A9D9CA6489CBCDF2EF6DFAE91ECB81B6F642ED6B60DE9B29E8AEB22944C039F3ACE48F92C40EB049629744709EA63D246017860D52A69D7D7AD2563DD0CE94DB3529D51C1ED7DDB3DE7643B7CE20A5912BCB48DA36895B6DDABB7B28D7E4AF1819236F3C3DE772FB49C5CEBC211442B3ACCBFB9B0B4D2BD499DC4F7E9137409D3B012444D774F952A7F2D9EF9A4198D04005E6B16B3D546948B28524677D58CD1032E8429146C848EA47CC4F91AFBA53389FA2BA6A357424895CF618515E4056570BDD4D23E12C26B20570DAD598D318E43C2D26AB4D4B1E3461C6B4E1AADD1D71C52DB1F24464C97828BFE7A290DA6A7ED8AB88055FD8ADD23ABF99ED142D2659F2180D1545AB8D4B49E1E189B4CAC1DC635F82F32B332E71E865159769CD9341A1FCF732D5614B1ACA53DE3CD6FE5C5E3A82E0149950F97D651C15FF17622E90DDAF998E17FBBD796E81C2835C8A65EAFBFD27CCF36E0B6DF32234FBD84846E71705CB2C72634BD4BA5E1F034ED6E8A316194DBE85C437D9938A47E73822B2C21E8C30DDFAAE6967DF0724E320A8FB09109402826CE9880004ECD1BBC767A23C4E129AB61CA241993C4167C53AD9F5BCED6827E8FBE38D59A3AEA0CD135295DB8D4996F0C7740D64D8BC615FD9B3A6368AB137C4C36B30AF65EBC1210D977EB83B570D913D1C56935116E7816E8C50C28CB873CFC5E8C2894174E5266166CA6C3D5D894A8296D2C842CC83A8AE620046EFD421B3EB2635C1DE4E05BE6734E41BF5D1FF5A4F76F287799A70F99E2F4818F3C32BA619C49B477571D7A62E76F44B01EDBDC8F488C6FBAA08162C9C5C420EAAC34701FB21FD7AC1AD6EEB2BDF368364B5C74DEA4133463A2BF45ECA26F2F6EDC6E2DF0F6A6BF06D8140D35E580BEE569BAEEE972C8B9A1CC7EE65A36C93FC77D15D5C22C697DB3AFDAD7E73FA59D25F151F6BE68AAD520E22CB385A5C25EECB40F34B793B46AFE94B69E0588D691547545E7BBFE9A8CBD8F685D488A53274904A54514FF3715C611E0805CF157D1C22D2830EDCAE7BEE4F0A95E833CF07E36CD2897CBC0EC6ADEACA13291E484583D7082D3AB3C348624ACCDAC09DC05781C36A66D4C3727B787CA04E13E8FD51178E15C9CC0189D174141AF27DC80BB372A83AF1C5ADCFE9AA10410F29EE3C9D8F35E98019B6ECE3D756337A6DFEF5F26E0981680C0F5DCF45B19C355207BE2777C2085B9678CDE2F721A7368E96E04BCEC0E79F977F818A2DCCA1F1F0D8C2899669170074C5AD35F7661757DF0F9F1A545A14ACF6560939EC1698ECF26C8D9DB7ECA2B74250847EFDC54E15C99D9F8877F6A79E6C0DA28397C9CD7AF65307A03387730B84C16CC2AC0829867E89BFAE1F747E549563D99296846C8CAC7C45DCB092A2E53172E32C13D2216133E439B285299610863BAB12D135B46E95C9A1B2F88943C1EF5345943874A19EA6B8007C895A9A6DBD7E72E8604BACC067460CD621A5A25DACBE049172B9CFDC4227C0B79A7012D4E817267ECB62B3DB2D4A9DDC7B55E88DB422172F7728F201706C543FAD35C4FD788A3CD18077E7B8694D443D50BADE1FB93DC354E509BFC3656EB78890966543D8CE718F06CA2FDB36200EBA839086605CF58896B6271F9CFA405D8C9295286FB8EE744A7CD4B73278E40A51EC3D4629D8FBB8BF8678170D02468DE4B3A3F3EFB7275112E157580ADE3FDF3F0385F943BED35E29B1D902D9A553A8C950A40F8818CBA17D7B031AFEBE49BC9A72F3D03B292C769A851B27D7477D3384C748788E980E2CAA62A026E5C7B9240CBC45392DC60D562FFD2173660199B88898DB1E181CAFF9C396D630485FC1E107123FF6DBD7CD66064846144F02BA5941FF587E0B7594D83FEC866C92DF232533FFC430B6E7780E413F37E3FEFBF28793323E2A690FEDFEE39DFE827C0767E9918F0D06D1620F9EBFADDD7655DAB8EB4C305E34F31890CF8392A0B09D46329F9F1E33D3F8F8C3BA8CE73A8C5975226ECEA50842EF603DFA21D3608315861059514891ECE26A3133CD6070BA67ECAC437617AEADF751FBCE5869AD63D051563567D62065F547081DF64D3535A9C08AEB3BA2B0A8C23343DA0EABF1AAFF7513C36E14246081F938B131FDCEB19D39121906E651B9BCB29976C1D5C35444ACEBFAEAA698CB74575E4D75F350EC7553EE515F817080A1139137A8037BCE378C76744B1E8069147D964EE3ED8042ED240980B609D1432129AAB1C982CF028B6C86515F3371A8B0460E8E2FCAED90EDEC9BBB43B098E99696368DC185B757ABCA6C2F5C845938499666372893A9E00A4EDBC90224CE56866C06A5B7F283C2269D1479210F000BAEA547FB9BE79ED121715D671B8C20405AA40E0C084A50814AC8576103B8FE95C99C4CE2B20ED5D728AF7028762FDF8C5DA5ECD6B850797A4B77C30FE2BE27A6BAE2AA3AD133834DABC72B7C71528F1118DD4431D7E64CCB5BAF7CFBCC2885E55AF6879EED80C107605D413A66CD155FB0B3B27DD0D30222F2505251F28F379F9AA8AAE02C4C82481BEC59531A845661507EA0B081E571BC479EC9D87AFD8E27EAA8C1656F5DBE626F23271CA9BFE4BB2DE5D2398D187E08E2E3C537B1FD511CBF615B47CD4035BEA9E0EB1185F2ADBC23F1A834E7C282201144068E691B5B087ADC75BB8B92913653A362B339B8B13F3C9B7FE383C5B93B11D27DC50197C74A39086C5E2CBE83DC2B0E6D07AAE8D52DEA43BE3803A1F5C856DF1A0711240C746FA270295B9F09AC632D5D79CA410780D4E909DF079D31D237394E0A88756506D696574DF169275A37976E371F1A3F132667C4A9492E11C94EA00191BCC9A12258E238C7760C33F9B4146D5099D704D5E0209732E7E625379F2073698C932166F4B3B3FDF1AF4F84C787DF54A4C998B90E05F82B14A46C2C036CC38FE07E68F117BFC93E84F67ED2C7AE1D3486293E723B8635913E1D033A5E0D30ED3DD9C9316A1EBD684B17B99C26270510B9F74ECE66163F009C5E9914DEE7035527CA7AEE3A0EE286A6C631AAA37CEA787C71B38A925F47DCECA1E40AB64E3FC06F7AE0897093E26A03FC0936A1455B6D1DAF6A40A75387D3DE62BDDB071955F889AD177D7BB19BFB025C6455B110025AC53CC5174C2B7EC5B06A010C2974879C1BCA72342A1FB027BD5D2FBDF4A7D0959F76008A492DB9DFFDDFEBEBC189B3BD9B3D7F413B2335500363193C6B9139C4596EBAE3B3D868E7BF02F5E704765556B7372856CAD265D8F76159820AAF7025E6170F3725100EE5329BD458D50B692F192B36FE8F13D66C4B08F5386B719A4676FBABC7F2A2EDC116976449C1C68CC7DB592C9AE86B6DEDB589CE4512734710853DFE14501426C06F0764C3EA9C77B9269413A6E9D5261D3402E30D8FE7C86187A094BECF024290EEF98D880B85DE011F29AC8A89C77252D108C15B19FC6B4D5FFF87BA18DFFBE33CEC72F3B0A6370B45EDEF38E4DC4193F31F523CAAC381BAE0DCAA7D733B792687FD767C3E2DC52E18414E2FFDBDDE32949FC1C0968FF7C981F4CA57D382B61F5B16ABF3D568BBF1F12BC03B5DD012755A845F6508DC93AAD7AFC14E03DC5B7C78D3E62603E824AE57CBFC11E5DF3CF1D25AF682075FF1897BD37643379A79D2F727AEE56BD3E004F25A5333FF4707D0469DB290B57F16AA2D419EBCD94ED953ABB22B42D5DA901E42B41B787C365B475E6CA3E278C62D53B3A3CAEBDFCAC9AF9C84A39768FA9FFB1C0688670244314C67432134CC2FF023464411D6DEE2E344A9B0CFFEEF18AA46057FD017EC4EC23477FA2F7BB64C788BB0FDC9F79D67D636AFA135399F652B12EDA20E61D1EE5DCA82A29993E183A6DEF314001B19088B5A7A857465E68AD569C8DDF21789F4A972353214A44AEBC84769FEA20689A87F1CDBA25E380E54E2D177D077970BBFDD7EEC68C6D716A4E1D62983334A53969C8F189EB6D00E5202CD6EA0511B9076829632A88B328D7B90089680001D71329590938174E3E648A16896D5D2B3B74C3C99066AFE38DE764A08CE3D24D1E2D6A2BB407F6F0BBC59ECB5B745E6BA1C09084741A1FEFA7ED59F719EE95863BF5431463463DA5D554BA96D9DEADD93C142F95D9F5632AC5B6875C4E2462FAB652B75A6AEC7B2CFDE1BB6D59F667BD8481CACD4710D07BFBB4D4B0178F256F2A9220A0445D09D5F39BA83ADD7B1DE1652A6A7F2654ABB704C0FA8DDD268F83F475D6ECBA2825BE191610E7C75C9D4E2CC354988343D1B44661F8A3A90EE38FD9FC2ED6F7B9217584AD74D85EC2837AA8B7863F03EE452EB0D586CDF70B21525D13C41FCCC281340442D12E9D593D64CCB3FA920D158E94CABD9E72C047DB35BC3848F8553C41113032B60F2A2DA2A9C1476A9E141E29689B2371D815B43048AC0BE2D09B20A7D4B9C413BE135541D88DA756ACBDC1F44C6571F5B5182AD00E26593997F35B9583B67AC562467C59319CCC37F58E8A9EBA28678A728664D755E9B667AB8EDD4218EB5A79C4F81F8A64B96282753C62D203D9865FA83763F4C636D30F97E36DA28FEFBA6A257D99B54E7BD61E4B4321E5B963EFB9BE28B76A09BC2DA9C4433E1415BAD7BFC1E9DC099514AA34D743FA6D688DCD9A22D7C92E198EA5D5F1412B9AD42325D9973751D7AD6054E13DB2E088147A050226A5914D0EAFF9FA3E354535DC7B74EA8CD2D5F2D021A7B65BF7C401D197D5EB8A4D62B5E350FDEC5BF355F52ED6A5A56E9393DFF345B4AFB22A9949F9D60A36B45EF066613ABAC517A20CFFD434CCEAC764AE745871C2665E912AE996FF18DD697312B3EC2990A6288A41BF50377695118AECC5AD35537F58A354A74838B53D765B13F7F0F9DC24AB6B31AE0C9BCD11A9662D635BB3C923D83EE04727D95CD4916C9F0C50781455C148BD8988306319B45DD747FF73851BF423792E6C8984F455C4D88B851BE801B0F1E942ACC4793371B125CB15D230B018AC939118262BA9836DC3E7A50E207EEA85DE379201C24A034217B1FC23EA213430AF8DE721BCD929DB9F1BB1A2D2DC26E1747EC54C070AA85590B85EAD977EDC8E3A2C5E93FF717B5931746D173BAE2EF3D3191352A9EC2EF6A2261894B09A1CAEA45182B900111EB1DBE6C50F3A9ECB185394D29E760203C1435961B5242DCE1CFA5189F60810740A235B840292179C9C7210DD4A99FD637942F294435F15D5F3DDA5DF5B680E69D6BD7FFD10137EC125DA63D137B312974800642FE1DDEF0FB7E46C80E8894F2C216C40C353804BEBFA96D49E1FF9C106D424F4842A967DEBB2C77D4351509CE662C8EDCC307E95B94FE67975BA12547CA00634F5266CA4A7929C08D7A1F9C70346D642784390C9439597DCF251893D4F6C726A7B8BCE1FAA4AEB7003160423293953B1FC9F8C0C872AD1FD4CEDDD6F2F6EB0733ED51A3F7D04950E4BA7D6E90F9DA63EE7620AA449D22F17F5B4797A1B31EF6A459EEB30613237D80D267044AC9B859512719E4D6A1C00C9A4318880F794D2DFA9C6986B720886526A55992A546B775A08B8FC6F03A6371D91CDF440AFA391D1FC3A4F38747DF67A9366DAD15EFA678E61F800D136EB0B3B5939E76E90051E2E97EE1A0F4C40BF0BA87DE520AF251867893AE54574DA44A82D2F52AAB35E9969265CE1EDB00BC55CA42F3A01CFD113902C19EB107BB3FFB8BA8FEC4C1D01B334A7C759A82859E5846788DBAC6C134E505BA0D423A7C4AF695EA069ABC2C4B59954C07396B62B8C5CAC540B093728A0E1F42612D781D21C140BC3B9B2BCBAD0CF7DB12A270949A675EE85D6D4F2E741A9437F86A6A12A9324EA5AE84F1562B1750BFE51003C6B9FB2C611F2B247B6D8CBCBF658113584141639EBD53D3876C6B190BC6AD3E35412F60FB121D591F7A415B1CA064D1B394E9FB4D5A1CF747D698228E3F6590380A7DB88A7F66DB8C0417F6DB2C7F17C902258D4B747BF4E7274887B7575D6BC5EB71D570825A8283E879F6CEDE9F9C10D1C0D65580ADBA627706942E4F1F33591E49454BBC699FF393403B666E9FC51A4BE1236F25D71012B19D3B97D6BACB780800CD42072B934408A2F6621E84CC5BBD1E5EE26FE6C26519246C3576DB12B19CD55983E354FAE632D1F3F709D71A61E41AF206B3E0768A75F651B5BC478EDD0796AAEB9CB2EC945230FCECC6C28F410E8B926125CFD499C2093043FA0A3CE3BD7EC53D7786F1EE23830D113609CC3557A78A820551D7F8638025BB4BB0EBAF5EF9D03552C22F71567FFC430F8E2312782E83D6EC243A65133826719FAE961A861199BDEF57F2145EDA46AC4A7CCB458CDB73C871CEF7CF40ADE9C71A88892B73EF0E7EF6C53A4A41B763871B49070AE9B6249E9E8297C3899A06117DC42521EC905D7259C4F58CC111C98D891A74947D509D61C48E84C7750E72AB129010C4B16F5E80244E85FF1CD09F3E6F0F768D2CB6682901B7626601B2397D05FDB5F35FC2C6227D82AF29EEC4D9338006024F919DAA869270F7AD4C79BBD4B3BA2C64D2F53B002421FC5210463D1C9F8C0124EF803F6B8E3C343D8D7247FC4AF9BFFFEABF711549CC204C93D4BDC48DB7D79BD680BA0FCA53666EF5EEC72391411E47D7750934AB2CE67FD4745DABDB5A421FC5B5340BC1C97511CF0A7CD08B19CC73523A87B899C7D55D15C503335F4CB1F5DF92D4A42373B385A5709990F5B91CDF5D4068E17EA62650786A78020BF8E6EBA3DD6EE030FEB3CA221B4EFE1FE937FBD1C23297274130AE6EF0A8C10318CAC796395A93B3F04523ABF2873CFD837ADC68C17B8DD96ED9018ED05409CBC33C740051DF8E2C22557EFC3AF5B38558F11150A81DF5D9ACA9DFC1ABF72C047C735448D68D899828AA29A13714E55D4EE6B62906F9C25D15319789D0882E5C0172A6C7B35761D197C21AB6B65D5BDDEAEB6BFF30943E6E04CC40CD0BE76BAE8EE1C3CCD868DEEC10133B9284D72A60BEDA88E4C76F72D4E53D8621CF63B48C4E0DD7C6D3D705CE38BD6016AF2399601727A538243985D2E7817A07E26EEB5794DC6760214FAF23A5B09A1131FCFE0DA12568F950FD3858B3AE46D5F323E0B62E6F87C17A0FC51DC914D08CC699EF162AF976484418769E0BCA6BC4040FE877EAB24274FB265E4CD6589AE0A3263C11136C74E89A5491798A57AF7B7FC33F3C73795F8C63FEAF0BC51CE83083423642A4D2A549C5C135DDD32A19ABC13FE3EC11127D61740D7AB89002847CD54D8C82ADA3A89EE3062A498BC7D6ABD6B9761904A912A4A4D1B1A8EFF108F8F40D9D917D59E95DB71C543180903E91B204A41F1AA0CCF42AE6D6D99B39DA974F707B738195D0FDC5CF270F4685CD50713679CC78E53B56098A90EE803507317104828D192F2076A2CB794393E05110E63068BFC2DC42B6A3402D51964C459BAE76A7497A08029D7225A47193E64316DE0B56055001C36DE8CBCCD7C79D58AE762DF431A8C4F41C0C743DB8E01C2656268BE0B4F20AB1935210B0DAC31A8C5206FD9740BA72766784E8A15CF38CAEC70739832F5F25A72C8F36375B649A6C01FC3D01065ED855A0D404EF881DC4AB1AE5836E69D4CD4359EAE48D975EAFEBB3065AF382A59EF0DBD002D14E87452B6633688173A7D6FAED64CCAA3341E67FB06594306AE9CCF822C8843B24EDE78145F55C3A954C630EC03B0FD04229FD461E865241AA3CFC598A6DC94FBD83D5C3B7905E4095EA9B40BBFB3C64478F404961C314A5C0164FB2D9EF5B58E7BF613D8A6E559EEE62057FC246A0175D7818A9AAC929FF1A0D14942F4517A83DB9C4EE9DC8A2054F1E9807B8869D1E7AFC516987F3114E3DABED71BA3DFD3924FA1CF4726A8BB5DE628F1E6323BC2749CA18D3820A1ABA5238096FC6881AA52F8AD3CD023097FE5C7C18861B1DDC5DCBE5421EA2700CDFE1FC77D4C0640C69F71FAA49F4579BA3F64D4BDDB91E2C385002A85527748E998495A52A8024A4807D9AD0234F3DA52EE90B783578BFAAA1FD174F0F55951A093124CA3751EC29BD28A6E7A7D51826F984F9F581D844854DEBFBEDC74C791BEBEDDF99CCD60EC66A12FF9D0BB80A635043D649127C04C6F94BA0DB09185EA7B2C6800E6E689968E757797CABCE450EAF1D57420A49998AB00D2CC970E88D9181C4CCC5C88F813E9C8654D19D4DDA4BC8E5A4D11F78E54A3D9265CECDE4E0F243E6A689904D5BCA07052044D1A83428F8AF729061D2B7033AC4E4AA2E755FBE0529645477070A7A891AD9424BDBB72B40D376F781EE89738FA7B109B288D87D93FF6118127A9BDC6D35C229C144C68CB2E8F7399458D8794509A023ED80A3B2047A6BFCBE5B500C48CBB9EF806541417171BAA335247A95E224BD59F8599683D1EB56A26033C2E88F3A3BB58B054ADD4603D399BA046BC576625AFD59C2A239A5C80556848365B69CA308A3114040B7BF6A76475F401EF92E18E706FA25D2C92DE646A52A115ED1D2E87A4EABEB343C7688CC1AAEA36DAAEC46AB026576F25986A0E89D092557DE8B7AD8768054516553D035784E9EC04C8C79B9B5AB59A268DA72A8F4E28583B306E56AD1D86B09F7671FA1C97791A96F8D5A38D107E79B5549B4A31540B66504B035E65FB5D936A87AA78DB3A9C29531542EBF7884826ECA3DFC8D1AE03D97D2819A2ED3F08B7557FE92DC311B7C82BDAC55928D37213EBC65A901278F480790EB90677F434C89401701794B73751D37668AF3BCAED6AA04240C62F3BD9F5B73F28EBEDDD6A1DE2DDD59F2027A74254BCABEDCEBA438967714759D806641EF76E1919BEC0B9F6753BE966CDA80ED2B70A34CA676D37C1824E23719B86952C97CFEF4B6BE28776450AB006E2AE5CF1D1A2E0F3B28B8DC47C45AF94A70E6E01E2595B197EC5FD42C71EBBFD4A9954AEAF88527A49FA4D66C4079F7E72A07A7AAC40D550906B83F4451F11F57C21864664D6561A1179639157AAE8FD7D1FF288E86ABE05CA6BCA4A7014287649D330A9249637319249111A273BE568C77264C88240FB72C6AA2B9A8118FAD8EBE72F3A873B61F099BE8DEE16CB4F8F7D2F7877CEF538D8A6E06CDB254E81B853B62D93E2A7C81A244C6E7AED40DF47106A8EB3BA1302BA716BD41805C5B7813C0E8025CD7B7D6E8B819068177986967D47231CB119E40D706C55A6496218A43B1BC4F109D2B6F62DC90099D42A50C5E033449963A586C1847BB3822A3247C400607212E85E39C8A30B88372AFC7174B89DA15AF543C1C77159FA95D582659EA4723200C3EF8100A07FAA9EB95F5E8D3E51236BF0BBC41D2F3DD11B96B73F18C089ACE5FD01F79D60691E6179E46D9B5C2227126B892C36784E5F447E806DBE77796F02D4D180E53DFC34467E5245B11372EDFDB979FBA3415F4FC1C9F1ACCF40E27748E25C1F876401A30260533EE8BE06F9B19598E8AA849B7566B590911AF48C339A9F490FF7621FD2833F412A7CD2A36B8E632AB7B3C9E7C848A9651BD38B1A3AAB343913DBF4A8B090792A6612721655DC5145623D57F2C89EC0630D9CFECDFEFB3797DB16FD1D80948E0998F36533D14E07A910571D9E5B9377A211A057EC02D32AD4F3E371AB56A2F7C6747853345CA314CF5A5632D8C85E5503F918ADB72FE5871F584E64651415FAF07E814BC07A3C47C72BAB43773AE3E0E0EAA9023D6DCE539A5E0597F1243F4622E1DC559F7C453732E14992EA9D296CD7E597FA1C793A395FB5F6641DC7A258C00234D380F88CCE074198DACD319A5DD75C4474347396B6F8AC9E19D5074ACB3D921F11094C4B6C2332F07463C92032BEE6A053227B5243F313A662CDAC0D68BCB7593915349BEF0D9CAC21F37078846B8D4DB539707F3D733A88E777F811C9427696D5C229F6D19E46AA385D03DF0276BD39DCDFDAF6C674C296F88DBA559222E9D6EA5C29F3CD82D34BAFFA594F2C83831355F87ED32EF7FAB1B053FBD40F8412644D45C113FFCE6EDBC08869AFBB4AAB497B329B8B58B38F98C117F10EEC8C2E038C469DB8139DD917BE0A2B750CBC46AE808C4C0D9919D0CA56C967651145783EF4566E040430F8435A93C28AD37D506667930F78C8962CC3A92E012C8DA5D6B6D88692D3C4DC87B779B4BB654E043A11B853E758907A47B7415495B347EF85155F21F84B72BCDEEF56B27AB6FB6E0D025FFBAB1C5839D15E918F481BF4B843545D8719A723BFE52BC3DBE5EE1AEF0013CD2DD1AB9B31E0D981F0540ED84A6057D8AEC179DC81643913355F470F4982B77CD89FAF228C29FE1720F54144772F08797810D9503A3613FB6642B50F225FA0DBACB91BB501E42181046722A166D547594D2CD4CD3728320A1B85A88F0DB0A69BC9ECA65BEAFE17B783596E8FCC4B5E13D4309F135D011DA254DF9A64E35D4751D5EA77AD6195721878A6FDE8FD445368F2840E3CAD1DA921ECCCBEAF831DDCD859F146B1218756374037B15C22FED7AC3CD92EFD94AC7843868A8F47A0EFCD6E8DC270511330D9FF045717E862B4AABC93653F731C6D542C0BA930D69FC574076DD569DCE8EB8E75332FAA55CECE78C3C05ACBB5C5442C98BE1865B43A367DEB92DF8C5F8D623C2FAC5563AE4BDFB185E0B5E7EC75168ECC33E0D32B6DE1AD9CA6C8CBDEEDB72F90CE6514ACCEC7DD7DC32F73AD80A6704D119049F537F03D56AF42F6B9E5269DA20ABCF0B4747218D3BC5F5DC47339E78821921499A41CF31B08A73B194DA790899AB2A7183C63CE48B700A4A41D18052D640A1BCEDA72383B090D8D56385F3B9098BB6CBBDD62C96662CE69CE7CAA6094B71262D7665D049D7726016ABD28A7A4BDC9A92F9D1153EC629AC00D5AC2E174AE986E38C8634BBCBA111549AD16F8AFEDDCE1DC9C6B9AD85EBA52619D1AAF5B6DD4FB86DA5BFCDFC9392B3F929457031D41B84E2C81B068623E5D21A5734B196C199DAC3BF4DFEDC3AD0E9F95345E4B437C068F58A4C204D77638A1F59B48FF9174A1EEE74C94211C2450E2F90A348D6B26B163418C3898DEFE3EBC4EDB7B929A1F17058BAB722C8BCE577C6E41DC587A51858BEB3F3332A00943CAB2ABA8A66B45E8A33D743AF0173BF439C8B5D3B79E5C32323E35DDEF66F08B3CEF8FB4CD13BB14BFBE39C857F1B7D62217F33B4C5E0EB3134133FF516442DCB3EF89481D971E1904A9E0373E33EF317F3119CF08AEB4277F1EDB64536F62D05D9609B2AD0BA12D0FDB7D03238CA42AD4F094968E5F33A422E44824A0D4F68341EBD06B3211EFF561F4FB8C1E220B7BAA150EC4C714AC6647CCED0554C5E6252BDA66E45011979653B0506B59C30EC16450D673106ABFC33BDA58F5491D804164C818700E3E587CE0C7682C4B0B0A7BED90B7230557A23F31C890294981B41AF506BE03C74BA8CC8426782D3EFC6DA4BEE7877B4EDBC190FC23C56A849DC62307CF8CC5E4E05FA64BEB146A92416E1C4CD2FD269503411286706B671E6565E7A7B161FC3D429EFAD012EB0B7E6C6765C606E0115C5EF64C2092D7A35FB7C4C3B2F07ED8925449F7B517CB164AD208F2EDB390BA10D424E5FE90FA0BA08F5210394CC1AB861BF21291BEDC818201F50E9213E6BB48B18D7AFD92FF9F8F54988953841FAC0F55FDB68B495A327A8B8F8786239A4FE3426235451714DAB496A1825AD66D8FE9A7F831BFF4283C48F90A28571A4633C7F74D22A6446141B3684F9D92ABC03B1029A0A2428495B9F2774D5C169C3FD4E6886322FEBBEC383070AD08CF7571FAD9C9567A769BD43003F6704A6041F9C8C3AEAAEAC1CCC4981961EB55D6449A353B6D08EF78806A967C23762005034D19BE40C6D8625004A478A7B35CA4D29DEEB9F904A905B50268A9B69F48A1EDA29D702DA46A0E0F84CBA63D8BFB4D82FEA132CFC1B1E8EDB3C16780E2B9EA6CB0B8119A02A1FF7354B265F797844B3B735FDFBC9AAAB4379C0E32434B2F12407A212EBD50575EF33389701B8728F0C170C8DD9FC9FBDFAC2D48D45A03E19D3A0170CB435BB0C607A0F77AA913ACDC718D67EAA307B53F8338E846787B2A2D360FC0AAF4ED8DA9EA2D2A77411BF0FCACF8989B3EB0351BEE0F4EF2119C4AC51437D0823EDB1DAF1A8581C92AA32D17BBCEA80DE2C4097B3993BE16E08E9EA37EB17F1137B82D6D77F42FAE8521CC6B8420682B4227025395F6CF41F3C04490D09219D2EEAA86BE2C8C93CF59EC2C93B694FB60D422581BBCC823CA69C7CC5D75406C4365E6318DD935306B8739317C578D0FCC15E1D0E83683200428D9C466D1E8BCA23BB0C5F689E5C00B3315DC24C08AED06A5A975D8087AF42814CFD115A670F98DF15AD494895482B88876AA1BB6D9F805AF4401D747C45426E81F1A9E9B26541CF246C5813F4D0236D9A94306BDAFFDC04625719FC3FB868819DF558774D63D133C1C9E152A75693DD180621056A5B32CEEEE0D179865F2664F190B4DD927A13921796CEED93662AF73EFF0020F261F7218C5243B8305FF1B8D600E911757345B67A72F6F0F2FF3B51CAB4FEE910CD9F7400A07028A528B4FB4DDDD43303A1D48EDE78A46D62CFA74AFF9EA5DACE434ACE6D58E013A377A28A6CA82CA2E4B7BC911912BDDA0928E25DA052AC4A14C54DB1369BCFA9F764C8E49F687C631F0C1DDFAD98F62EF6A0A43359F036BD76FC07E43AEDB353D057F9A4358A735C957CE4FB97BA2F2D33E7E6425DC6CE4F49ECDCCC934DAD214563E24C318DC5430482388ED53E390BEB4FCA0B600E79BD705F481D395CA0A1D3675D573817DD8093BD5CA23CF2290DF9003DC82C844554D15F43AC605E5C05F049FC621B4D6B518F586EDFFC66C4C90713DB7F559AB4350C74AC25738DB271D060D8A24DCCEEA9CBE2727D54CE0E626D92B1557DF8814EB54F7C3357F5A74AB8CB7D3C3DADD8CE4488BB88043B2944D8C15F86DA6C0D2528864104A55275298C6B1F26CE24C02068EA07DC91FA16F9ADEE55EECC3F1455418E632B3F51E394C8B0D94A6361A8FE6C1D6BF4CB5630ECA64E1C8E10D0C7AF558AD7E4C409C4E3BB75723650336F0F12B7607267A9AA7B26CD66E34DA287DE27E35B52F92AD22A87C061D829E5DC07978181846B848471397AEFF2BE613C0B146973AB787D2775D9170070A02C2DD403D68745ECF4E5BCF95BE388D16680AEBB405BB73AFCF5E3DE1278D53A5FEAA127E57BB418FDD481E7373DFDC68CD926FD32EB30563ECCAA42D232FD3DC4B04CB2B9E5501CAF557E510601B99F41CF833898C460E29DF4EB5D1237BD054362E8F33FDDC6539486C32B17CF1FA2DEFD7DC1A3FBA683F556255D722A9FF60BC49E2DD4BB504160A96862228C5AA1B130253BC16B77F6F4CE27A10AE6B04C1FE88628AD41885073C25245B69648FE75F85B9D0B1767A6997415F146F2EFC12879D0A2807BB654BD0CFB55B19122FF1CA17E9FB2E21C3AAEBAAF7C9CA5F36E13C574F8CF051D6CD458F208CDD86CB43E4A2E559BCE3B5A8346382CF2362B84A33FA8D06204D58B010C2FFC4EC78FF2B12504870105BCE06A562EA481253BAA5E601DCAC3C38A228FACFD93E55F3EBFD24A49EAD6164058805671D5683A6A7A467112F1FC12E73B73076B077A2E860DA0ECD032FCFB42DED0DC7AF628178C37507B342BCA2F54487D4E4BF071F2AA719493AAEEB25E45C72B2A8C4B7A354A57C894E945442E102374D9AE86B2AD9011DF7E9B3A30754D9872E175214EACE4D9C65405541C302ADE383D82837F7BC074ED389B0786084E0BBFF6B17B6859A14B10E52BAD87AF069145621F9B04CAA2CC2EEE3341076D8CA75AE5F8D685E18B1659667FD243605C2A7517724603550343AD606F459569F88EEB68231A2262BA550FF1A2BDEE8719824B749582D82ACB2A1489E2D59C6714B6DFB4C08ACFEDAB8AD3F14E800F9067FE418E015938DA07CF214DF810DB530509252368782DE6B40BE425C6E9BC0266F4288544929BA8C3154DC8F014E70C7F716F0AF040C99EABB252EE2F630EEDE29CAA226B3351148A2E684C02143B74D0C17744630838ECD3DB1C3E08CA7EDBDCDDACE8F024DA0D8DF865A5DBF76B5BB663180084397B3E7CBDF28DDD78491078A191B6B73B56C656BB3EBC393B350A082DE4FB4F301C7DE8D603EC733443409E7DB02DA7C3A43CBC2B10F5F0B4EDAC421F2BA2FD17D178CC66645811FD1E2BAEA62AB91E09567A5621E06F1CBD12632EEC3E21F493B3A98CA58FDCC14324D692A9C4FCF949F2D3D01BCC25A1F8BC1D1A03F31E997A148A520B0FF604C65F3ABE32C4B311F46010450993480F3CA9BE32599E509BA68E7747BBA0B6481F9BF5738D94B85FC10CF7F58E14B1AA46C6E20E7A09157296C859DD22D38F9D58DEBC418519344E30CBE392EAACC7EE58E39AB336AEA2095F62943D2AF22C785EB281F45667C95006432774088DB18419F7A52430470E3260C709604BDD2BA55ADEC2736F3735EE4EFE96586259AEDB15DC5289020D7DE76F76ADC51A5B98195A2B742FFE0C8C77BD26438D948551E1AA1815B84D15141B90DC8CC78EC332188E97D7DA603A108D67312A663D39B22F72145E36A519BAB6CCCC8E1BDE17ED4F9E19473B05FB665DF3851A436690BD5479BCD913A20FFC83F43580E0A7B0A73604FF74E8AC8400EBD266571BA2AEE7A95B33C1082BD9A39330E62CFFE29B629473F966BC57E0CEF1BBBBA1FB36628138761F957F2AADB2A0132CAD8A6EF2F0AD4BB7E62D9FEE72C00FD095DB479BD3833532475F8C5A90719274ED3E2B15D13D73C21D1107167140B65661439BDED15F95FDA97456EC3EF7BA97226D12D775BF6109B5B6DD5725A325A7F95A9D5E2BF4AFBACE6698493CB66362430C54FC286A60DA95536E4F484E8CBB20EA10F334E1F025C6F279B69F623EA6810848D00FAF1EE61C0C592BA60D32CF10FD66E8B12B4699C9E816B801B503F02CF6D833D5A66A56D6853EFC6804333B0F9A03B4800505CD2A89DFFCA807981638B171BBCF1FCECE72760FD2C55CFEDA4B9FF8845A21A0F5D4910461291AE5EE24879232862EAF52DB7AA525261B3F1A43A518DBE72EC6DE26EB8780E43982AEC18B74E958AD95C45322E41D2E6A6B0216B0465326CF7EF760932A5224FC87B86C5708C8F3500F854D4B62985EA1B231A6AA7B5D7BCF83A184B063CD1272F72F266B2A384852E387FD3073CCDC6256719286C50D476AF246BC028A15BD4F8AEB4524A3924B99D8E2B9D4B9FC98DAC1325F49F5FDB5C32EE7EDC136ECAAE1027096728FD985970C59FD45F1328CA1AA2C6EE57A9F4D551232AC7A2029E4D25151363B6320EBBEE4D3D58A610B5B21EEF85976B86467FBF0544ADA2D5F02CBE2399E83CB3F1910573A7F078265B9BEED00C56BB315283C19A5A08D8DA3C5C779B8C94F80CCE4198ECDB1FB86B7CED2CCCA4C937E360EF0BD0B5939A2309D6210D80BE0D166F07C877F9B9C74DF1CB254A0AA46F395C3EFA3CD920FE6C007237CB11FC8E82043F184548F7E46FD50EFEF7B7650CECCBA472E00157FD7B65068BF20FB839A9D0CB6E8D06F810FE969A91EC120A7DD507FBB996A5C8E53E48C6586CC59090D6E44900342EB47684B67EAADADD6B5FD2E677811F66D90CBE745A01BF0D43F91DC517BA283CFFA1A9365DB7C3377CFB8D54DA1034462BD2E4A4D4109D6A080F7164A5AD95B507109D063A5AFE47BE2F28C8849591CFB444D8349F454B719B674040048D15DAB67538EA27EC8842A84B19F55A9CED4C8FFFF0AC754939DD9BDCFE1E5FA04B8294BEC99A995EA90F0CC2ED4DCE5B0B4750153185E512A2E5DB03CC0DFA5778A663546F0E7F8B14873C7A13FD88DAC111198E0792D396602EBEDF82896C40712924960E2EC123E131EA4F44187D2608CAE03E2CF3E496C387FFB372A3FC6067ABD0CB5B335B5ABC17D4FD9E3ACAFD82C6C5C362E78B21B177BE804E5E5D54AD7E3402C17558F63C866504D5380D44F7D5C47E70E40C7E74AA1D890C963A9549F7A0F78AF2EEFA7D8427B0BA60EBF2FD4CE30A7AAD207F861CAC1FF534D17A1115B664BE2B30ABE89138537205D115BC5EFCD0308FE677F2C3F1E1694B0665963235A41536C71E517FD2CB5393165A75F565F7CBEF0230718E0297C58161B8098BB0AAB9A5FBDB4A572E17E78082D16E27C6996A5118E0AB330ADF01C9AC225E352ED803BB9BAAE039990554D25CA179AA7FB3D9A8B547039A22F747C5B3113E92E9BA343D0ED3B79B26D2C22EE4E81143A349909142290C17947BDBB5FED6736E56B52FF8C20649EBF8903F1364D9C149473566F5EEF0D3BE894B98D744AAADFF9A2E9C3BB91C7362FC9C975D8B328872EFC961A7305AF1A1DE12A9586AF67473B5A82165D11DA20094102C2DE65EDFA2CF88CF4ABF1DF42C6E3DBD9A50ADEEED4443C2D30373B7E4AF1D3E38A3B4E8C9DCE9DACE7C9EC0DC256C9CC1F02CE91850C63542F79BDEE56B03D0272D57C2A620D1F52DD4BCA6AF7709FC394B1C25454CE3D7DA0D277D021BB3B16B97BE06C845E65142B84EAB9C70FAF52B0C5F0D265FA2B168BA09BC0B7F3CADA7DCE4E9EA9D88DDD9423B449618A07E59306A57D26649125C2251ABC7440619BEC0AD666DF4BBD59ABBDD0D1956F4A484F1C6F9140E966397A218969B6EC0F508562B1C14B7AAABF0B88FF887CC736F5A95F0AA7B130A12EFA5AF46089C0B9C408671E29320F2C6D339B446248F011A288B84D9EB97D70BDD3486E9759EA3D4A1519FF1B6D00B1EC1CD8631B1BF8C10E1CB6A0132F46377FC1AE489B041866BE4AD4218984868201DE09EE13971F90A334D3F90505409B5EFB5D125DADCF59B58604147C49BFF5B40A43A50902677F05DF541161217C9F12ED15C327666A70C3E8F794D295D2783658377644D298BC4D693D310A2630F4FD65504AB8A7B0B039B37F291660AB629C75896247C50037ADF9222773FF96A4D75B9548F05D9DD943FC3E245AC2ACE42FA66FFCCD81B40889B25F9E439DD4C8FB7588625F0B6B6E1523BF2AB03C32243722BD59F0484B8C8EFBD56671D63613F84E266E63B25A384FB93CDA047D26F616BD2D419C057B4625410D2BA04BA3E91043590FDB526118B686D183F802420E2F7808A77AA0604EEE4B83437D9399C734510E37A5100C3E0B59BC77D0835AB58EC510B63591D26EBF5DE919C02AEDBE50C9C9C36FA52B0EA2ECBA1853D310440D52D81754D521C67221BC88A1E58BFB84F339F7AAC1B5BC196A4FBF59154FD1B83B0B9108FAC88C69DA4F4E3B1B7865526E2A17BB1A2350E6C5F2458F1CE139E0249992A6F456D35942521AF4D64C0F9A2820A16BBE18E4A2DE934AD25F56E101579F493776808A3AA327E7B0B0AC34647FD53697BBA90D5F463358E14FF50427742F079E4498655A1052209C7E7817ADCE815284CE7D59A11395677A7E7F920C01C697E3FC09D852EBAF1F5535D587D4BAB4EB3EC6490A42312CF37AA665878447BCD78AC93F6B78F0D27C9E2572DE6DC81A957E32DC307E6071C1A8CF33045E0E985D4C804DC7147AF9B9FCA6537E7A889EE3F0356EFE0D68C8FD01B1F0BCBA14C179E46C26A43401151F77E438F730C1A0823A32B8573DFF6D89926958BFD2678D59B0E51A569AB697510F040D94A17C3EC3520DA19BE5E896D26A792C2B15030C6B5068292A9B8F431C2DE8093253781FD90A7050719B0EA417EBEF88A3BDCC53DC93EA74C28376103EB5C4C65EED6623525CF47227E0DBC7A019EED70045298A680AF937AEAE895B31C2EF665952E8759A9932211F68FF77EDCC092DE3786950FF940176C8146CBEE634616A643BFFC583E66214E15EF5B22BF45F2FB4CA2D25D76AAF3C721D5B51F9CE4D94C425629ADA9082E976777BC632A2FF09D86F8F2EDEF2C4EE30A0BF8C9341DC2DC8BCCE06B2060C664C48367815F66FBC6E7286535CBC5EAD3F8565ECEF308E4FC3582E2F088102BEAB0887775D003E9531575D3D099C96FD529AAB736130D2F41D42841F3DA14AAD54EA0FAFAABCC848DCFFE07D94EC87A2C3BDF30287286C07A74A17F07DF51384D27552CF328948C620138395903BF4D1365441EA6AF10A9626EE6FFD8920A1D80E2EDE718749A7B7A7A95DD88AAED38D524D8DA08996AD148C0B40337DE28BB383FCBFC5680928F249C6753BA5059215990459CF4D401AD8C52A34A5733BDD6BC5838863F90A578C01A20A8F1DD603A5F1DA69B87D487899E9006F1C2BD45A4DD9A31E3EF30FECBC3370A815480AE04A2817A73154664F4A0ED412DB88E95DB99C15B9D93682D9AC414E325D771180E1115D11FC321B452FACF10DC1B5DC128E0B5BE16FF7608831FF4FC64390C1F2D3B9DDCB5645D404749C991DFD4EBBAE566C0FFE3D179CA897536941CCE9DA5D404F3890570238F286C6BEC1ECD2799AD6F5BCED4C628F32E1977852649941552C23B116FB766364705F0C99E04CEB459E52505C825BA2F46BE55B3DF979955C56F02527F9304C76E3074F451DBB3A563AFB7A138A78D3937E4F7B572D94A4DC357278C42240711E10ED955DC7322AC7A9F87B7B13C2B63EEAB9958D4E213C8D8D02EC7A34C07AEB20734B7E65C294694070352CF0828724951FC0F0D25579C4DA5FDC635B1B9E388D2115D11D42509C7508934E498A6041351520CBE7C43532948EB351AE3E6EDFAF1433480BF727096A37CF1B7C49D2B45C10C93246C15497A6C4847CEB664A92EAE5DFDE2FDF87BADC4C15225389A77B31EB79E5972DE910FAC113C8D7F1FCF71CA6D1D86740A2F3C95667672A8BC33FCDD2D2F6146B58ADD55C8C976B2E4CCF73B29C70D6DDA36FAC456702C2F5CCC47549611AD629A201D64A6469F590F08886EAB226B9114E4FCE4DF19E69EFDD7DEB534B17C3C1836428427E2CA0A06F40F343BB4A5DC96CABE5EFD1DACDE25CABF96E0A9779DCD63317390D8ED2E3F875AB099DAA87BBBFCB5EC4B9C9D7F6EB6BA68933AC589BF3E6B92AACE1F6B02ECC1CF9D1F98AD1FF1281777CD7E397C696A1515CB11AFF0071F51BF2BF2F18109505425ADF4B8464E1AA90FC6573333E9F247096C9413E92D0AE0B7D018CF66D825F1BF8CEC075623D725BCBD64D1087190579D7A726E99C6C184F8EEC82FB53A041187537AA18FBD44DE326EEE6FE63A4CF22BADAEFCA9C683AFB5C6CBF062F72224AAA58F18DFEC506FBBCA1040FD26B5C8BABB3B1DA1A93954320115E3D37EDB70B978B9FDFF8070C8AE29EE114F6345C4374A46DADE2D74A4DEDFC50814630248638F28BD8ED5C26BE4C2D9017F524A0F2B67C77F562A7A49F6CEE586B8433FAF923CE3E706ED46E8306B8A9342E17743A3161FAA991E6C2AC05F562819F099B661E5DFE9B8D72FFFEC6D42DDD1B25D5AEBD34999EE53814B862EF4BA0C0FE6705E81CD4B45C1E9522E0B30BF7A6673A1ED84C76629057CC8E935E3ECF7BCBC5915EA089963AB7EEDAAFAD979D345943276DE6E0D2829A3FBE4CA2030ADCB6FE71FEEA190B6D0BEDA206354397CB64F90FB507B4C3467121E713B08C107A2C885C44971C4A3CD2F9EB93302B4177AB84876E93AE1A1241B436B52EFB7F00342A5F06FC422E402ED583D8506C432D43F116FDF36705E7B7C1985579B4765E31347CBF58458D6EDBFA03B735DDCBC66F35F2382CC6610785EE2EB5EBF988B36CCF2FCBD669C9F1F52E038AF740DB138C79C6B2364CA7B4460BC9FACB2B6C6326EB5D1379235F2028E129629E6265C65FC21DFD20CD887E286399ADB43D680D6526F01ED4DDABB85C4C86D2929C0926D7BFEBF7AAED93C25CCAED86119E3CBFF9FF033E698E5119DD5FED5EB87343F34B170DC5BBB21F2163D0FB3FB04A092BF1B1AACEAC670E3B057955E83473250EC679A24DCEC9CBD8DF37BE84AAC6AED9697B60899E20546CB26A2FF2AA785516151571322E8A36654E9C9C73F9384630E9CB000693743996CEBFCC2A447D60D3DC56CFE4DBCED0F1837F2D1798A2008A15E02FC280CFDEEDEF66E5BBFAA4F9374BB11A4FE4C789A0970CA5317B6CE01FADDB95376120BABB6674AC889C57ACCB43FC5E556D48195E82657503EE3DC354898DC32B4D5F9664CFF3399C8C4604EC7D1571E4677985B3F2C06A9EDD25DEA372A521A106A3F09F27211062B75D76D6D2ADC8BA2E8C8F2DAE7FEB9DAD8561017C3194658F3C43AA4A9ED6E00B5CA3B7375E5CDDD605223EA9E69C9DCD92E811F00FC7F04D92EBB5E58089DA09C8B03684C918DD50681D30BB4EF6A4F7882FD7E8B22C422202A1CF4F2CBD992D663ECC3FA3F02CCC539599D3A8DAEAF3FC4F313E0313F2544103E10FDD69EE9A30A9159BA44EC2EB0A890DB1EBA0C70AAFB65CEA29B9DEB4242FA42ECED0BB7C287D69ACFA74011656A0F75AF93C58F42C15CAC1E7E21AAC2CBF2E0626C61644DA39FBEBF9DF2A89BCBE16E9DFD4680BB2D466FD1D9BFDFF9F1FC8F77B502818B93ED9DDDA0CECFB2A4B89AF6D319ED4B89B91A5856602D62A7206E67AAE8C58FD3B4A2361943F885851160D2C25821172A8E5CA9512E13D63C389875ECD16E9CDD60934893C97FC45C5930DA5D00091002C9A9519B7410C6F8988C49AF36C95CDAE40BE356E557FF3E96CAC4B75BB7EA80CD1B686253F4B90C5DCAEA45E2D55DC9425762DAA25146D74D25AF5F329E67882976C93D2149B50FDAF69A068E5082A4EAC3F63974168CE626D33A8A645012EA83E36FAC41632348C6CEBAC7A436E7B34C70F5E7D5F36414CA15B1C8564F302F09C68ECD5BE50804BDED712B35024BCBE790B0D8C07C8DD3FAD772969A7E244D0938D0B5E8D24753BE1D4F72329998E66ED9673A61901332D2E9779DF65F03AD67C1B56710D37D15479BF5F7184E25A27FA5B0CF5934BD26684D8C604C8D1BB89B4A1B1767E7BF67718B88CA874331920990109CE2DB29288B4162151B86FD5770192B96E2DF5CDD6A65BB01D369077E07ADFEE45EEA5649EC29C6CBCB26BD7669B35AF6F023F7C6D2BD44F1612525C17462B166376D3F8AA62AB0F6A3A45B7B47D6BBC06873CDEC2F7016DC5B2768A7A68E343B7A0FA12EDB77C2962594B4A11D175F1D7C6BA10C8115A175E56A038FB80CD53427E4A485BBDE60F5E54347381BBD665D84E04EA747F4524BA6B35425CC49A6CD2D8AF075D4621AB40C075B33376DA5869B0E3525843132706A4C7D1BD4454CF88D16EC560F5E0F02DE5C13691C3C1FCCECE19979CE0859E97CC029694B4EE0138A3E7790F764596E82E6B9FA5009FAC1ADF24E35AB20BC37795615D4BE1DA62A53969F23DBC8726A4DF6CFBC7AA6CC700046A8F38D75E23D2AE0455209AC9ED54B02DFFC598810CC1716EE37CB2528A82963888F6AF742A6827005845488B93C6FF3F48BBBE9DDC9AF675540753C73BF86E612789EB490F788DAC223E18015DE8131524E555E685EE6A967297A683E7142D0E5664183A26FAE5105FE929C06CB260D6E13402FE7FF24FFD832C6E83EC88AB253DD703CBA0DC8FD9DA16F62A01E000527A18F0DEF014CF8586E353ECD95C192A17A2BF654A50FAB4C290CF91DD80E4825D70959588D9EA565D6D194B1034CC4CA98E430A41454A4A83B85CEFDB0BB3A748ED85FBAB77FD48786D4C069ABDACF408E4AC7D8375F0B87A2940F99E065C2F9E0A849C7FDFD235222CB5AB4E58B2570024B0A627E1B8FB46C4BBCBDE33B1F328E7E5F413FD4FF4DE3B1D428416F001D140F968096FDA26E0157CF1DB42E65B9103123D0CBD00D8DE0DBE17A80B55329E80820DCF3418A75F7BDA5A33ECA2C8574865A1D958A225334626F669FD2965145357BEFEBF558CF3A932FF7287CF8625CAEEECA52A526A2C39369A4C6667FB131B1A3EC0DE319F33CD7E4C751D7A86337540806EA0B2D033F782671FB48B047B6DC54592F529D52C7F081AA8CC4F09C2106004F802D31B02B8958EB6038C34CD934BE1873E5A0F2196FD58D1B4A6350174F77016DEA4301C395F71B6D7AB299DC71ADDA5C34FCFD053EE8914EEF35D15B3CBFF687487E70EBC50B0D34E831667D6ED65FC5474B22B79DC59E9EC5E837ED9B05C1BCCF845D10FB40E77FC58A6E0D9488371D938E9CCB6A3709AC82B77F2CCEB7B448A3132ED035C231E114963B738E48A3E48BBB7DB0536F591210383D7A8475EA0A9E8D4C64DBC3D710BB0578E47B35E4D6DC5B2D1121BC2540A2BB46F4F1EB6F5EEFA33B620A15A674FD7B9B7602E561D95178D6148FB67483A79FEADCE6CF2E854C3C7ABF2C35B769EE5552E55A0D14D0C984473C41644A08D43344F43436680615C1FF96955B1B2699CEDFA2A6CADA455BE94D5AD1513B67A1162CD3847E1CDB611C37566C66A9FBE477C63DDB9A25A7B29C1F20369EAEE682E4E8BFA0D7F3789A055E8FE7BBECD014D72C7ED43996A78101FC750FF1595DC43686FFB46498C91314A5B0C4D53509BF315417CD85510CC20FEAD1B35564D265D4D7E9BF6DB74B43BE18F4F0E3AC8F2721B1BE322EAAE018CE7561EDC7814B4E9314C452EDDF4884E61C4398D11D59F777690688928AFD03DB9DFEC3D61B676DF7D3F857BD2D15012F6BB9ECB3AF90A49A2621EB40B7B7611CE0AB66842999240FE891DC72C0F9B245D6A456C511A2F6E17CA7BA2137F92311584CE908634E3E39D72031285360B33E9D60AE601074C9F3168657F13D4008AB825D5F5FF0A21BDEEABFF56A5C78F7DBB56B89F40861E0947D7AA2867A79C9430C1BFB6FD18AE66C622D93EB115374EB2507FDCEB2ACE366074745A37CA6C7E3B0F7C6E850BE74A02C733F6A0D049EE9A5E26FC67F0BDB21267D67C8C30C8AB9ACC591B867EDD9C34092C5B84463838C0998940F5EEF53F8AE479620AF12EC0D43A8FAF4C0B0A422D8A8B6FFC5A6555187CD3C831A570CC2CAC2058A18C9565D485D4D437886A9B27AD4CD14ECFD799C423B2C845BFF4A6F8E98B6B2F42FD26B625C15BD356F57EFACA6DDCA96B9BF442CFF0CCC382B28106E1DC62A18E7039B1F70DD16400B494AC764A919AA30C7AEFB947E45306B8DEC969BDBEB7474AE20EB7E08363CC21427E27E5CA753E81363970D7B0DAEF34AC1025C9A22B8345D91046346DCE62CE6904CE2931E86EA026787564947026AA842444DDABF156ECB338597268E6466E17D9458A258A0765FE05BF89386B3C2BD19BE06274E05189DFF9192F051614E860F2A500F9982B158EB2987C45DF24BFFBEFD56B62D36EE2F29257C400018386328FE3C708987BFAE42014D88D3E094B9179AFC7961472296DD21DA96FED02A7517F7A314949959BE06100F88769F458CED65E2D2384305D563FE8A94E9A2C15E9B44B17C7BC4DD1E946F3C584D34BBCC6D975DCE7B1FF99BDE0951AB1111113D2B3A61AA06793A3667C7D490D4FA7EECA0771F7499D4029339B73CE6983FB8E56757F6F06A830FA67207EB19380C1984A3DAFD44F0AB559139316AE0AC01EB80CE2E9829C3A1C742B27631C5E309607A92EDC2327F8EF8A7F5A7BD45758494A3B01F05A7F8CABD44C1D27E8D2071F20F7E41E4051FB65DA5D63D4E7DED97F05678B0073042535BA30ADE54D1D6973895552D644AE216C5AB6155F1E1BFECB69573668EC9B08877742B1A5E37E9A49BDEF019A213F711C3977F0BAED8ACBD2345D750B42900BAB07F0A780612958D274B26C0E24FF9791F7349B89BA63925ECC01082C25027BD9DC69BA52CB709544566FADB4B64E435E9DEA837BC531DA332DB2BAEA774FF0D153745BDC9ECFC3968374E81DAED1D7A6D9E72DE61E7C944ED76D03A0FDE3AB3C97C0383BB1B72D8438AB8EC4F420EFFC70365957C27595404F04B17B2130150443CA3E7EDC54CD2AFA0DEFCA665D7F9C2C16A95059D5C4694A5E72598BD0EDED2B2C8666489732CDD930556D5835412AA8132D9EC17819524A3E5074FE79422A3641EADC4FE4B543A89790E451DCFE1A74ADB7226C4354FB83558127E947164FFC3CB4794C98B1A193F7D76550C1D1BF23D1FF6A8AC85BB0685660A180A26184FDEA677F8C5AFC04CBA1D196EFDAC86E551DC0342EA939794EDF537DFFC2913035DF199828E47C9BF49A98A9D6BE35A7FEB1312A3D5808043DB9F1031A1770270BEA71B3597E94DD23237064B9FDF8D0151492D455E1074A334BCE8B2AD5D052951C8208971D9082E88862EDE46D969D363DB7D674EF981A09E217FF82FBFD0B5180F2861EA2A5C31EA6F640EE6A6C076868672923090DCF78384FD4E8071E66C3E6B2591E2E50DE27769419061250145F821498E074A923FA5A8975DC081BE3C46D66A303B323819B0D4F4201A3B7F433C670E28668CF9D5A8F2B35729F961C32CA7264FD5DDB3E38A36E537D214CEFEFC068B8CD273D487D55FE0595C2D3FD891023F59F974BD92A1372982F773BE461B865B37D8DC09EB40549C9842FF4A333DDF6BD963A7D9BD56CAD45D6926479E045B68AAEC9EB6EA561117D1F6B8ADB83E1BA08D2DC49BC17BB14EA92A5A7EC3BF1D5A4842933194806EE54BABDB5F21CB6499A9FF6E9A96059E51F99A420A7F032DEAAC4CC8A376E89BB2E3B0BE77B330B850B349643DCCAD94C31AE9B80EDAB9A56283FE0572371B37493949B987E965D9995DD2D66F2E15897519C62AB5A6FC617B3F054F6867B88E9FA404074972F312C2F342F749567702E39CC2AD4E34D8320882F835D0ECDEA515140D798259DC483D93382AE6FC358D4DDA18BCAFECBB234B379FCFD34BC101DD0A9D8D0C5F63580EF323D2D3936B3784044F0BCC9842CC980A723BFA9E1D92C5FFFAB508800CE7EBC8B99623D21268981B4EC359448C9A2F539EB19855164B18722E1F902ACCE64626072776F9030B6CE7335ECF150924F78AC7DAB973FDE1BE4B1A726C908D3A291DFFF0FE37110ED95BDC53EC35ADC1AAFD9844F138E3C93CBD963954AB2CACF358C37B714B42AD5A3F148B56EFEDF933D6D87AAB3866B05E78DA3ECD98427B9C2691BA6AB46193597BCAEC5133F15F7EF4BE61B2F06D01000D940B659CE951367D21474C004A14DA14B921FD9F0ACFE2875468B6AD87EDD66784D15E7B058E310EB3C247C6E8AF7B25699E30733269CA247B25173FF9DFDFA816E9D3C051A33C73134B691CF0AF47FCCB1B7B989397A536C93FD1D9E0D6D3C44367A320DFA2B0E1166231CAFB6AA05DD4C5134142731B2AA06394BD3672948F399878AA19AE8CC2DA8BBF290D0F75184932494068276B54453DE3347867D090D5720D70304E1A021C3B899DE4016B5D6F4F5E0BA223A0BDEF79ABD9D1A6E7488C2AB25671F5ACD1D3B63FAE5D06F0E91242938925BB551CC56D751FD18E9BEE669BAA75F319F1F26DC3777F9D0BED4EF8F96D913EEED5D639E46149242E13A331905EF97C5CC546B1A23E5E7CD38036BFF36F975BA4F2A3C59A8548BCBB1140155434056DCB50DC28744CD67C2CDD649541962B30122BA3826423AEA4A2D7B0C411CEB02A4C517A6B53FBAB73765E3D0D820A2FAFB3C6A71AF69D9B140EE89067218063B4AC8B9D860F516CA551F079635323E0B0040B9F6039DD20CFC58D18FA74BC74B02DE73BEB37C7D15D5D0122D1DB15AEE8449089F994D55100D65546B50B3F11DB66E47462833AD13EF76F83FF96671C901CCAB2DDBC70E2FF47EDBE63D29CE76F4BA6A24F3E82C604CD7B66AED6DB16AAA0DD485E399BCCC05341C4FE64D98EFCDDA9908061BB89E36AF136FC5E5B6BD6F757F2036A1D1743B95C456F5F25B81D981B0477B2BAD0AC359C5344E170103C99FB162ADB7289399BB4F223C8660EF4CCEC8FC47BDE2E2D856BF43F72AE4E55E72DE12AFF0AE494113602A1C390F321F773575FBB9D0239DE0C63DEC165D420111E8D616B85B71641EF7EAFA1D88347EE7B8C964143F1C3DF4E6D38101026230298917CE390C10075BAC6B1E8367DF91EC7CE42486140AF3F6D51639C54A61E97E4AE5445D2E6F07DCFB9E7190FEBDA058401B9CD1BAE0636FD0C59DBBE71795A58834E885883ED000EB160209C274AE82A732E22A447E271D6D5C832B3A282ABB820F8DCF1A1975B2E92F3C66B55DB54607D3AB21EBFC7AFB42A3D7E19F73BC8D53B99CAB20AF95E3E669E980E6154478020A5AE6DE70E0DC5A331E4443061AFD5C868F2F9EF9AF5F89EE31205CF1DEB9E6165C916594E1D6F4D467788AD5203069DE2EC2EF0529C550F8B42E4D05FC94FA14DD87C9D869633CF14C8CB0085DB6D3238266B374C9C77F3FB6C28E133FF3CCF559F7018D1361656A7A9A108769E828A8AFB4AE45A8957D7C03668C93E646AF6BA245ABB4A27A2C7171A1BA69F340B428171AF837F33A02D7AB3A963179D3000E79D525FD17723CBBB4665D38A4BDC524FAEA02F595E3705789F77EC57C0B7B8B6024348C1C763975633D9EDFF002614593AD210190C1D73FFF978AD1DAAFD32A60C475680321AD411E91BEC94438ED9E818C4A63F7997B1FF1BD12135B7B405A10F1575D874E47F007DC14A4AD2B0AF863818A2064EAD55E5743776516C92B3907973B8836E5FF1070098873C0B2A616C93B84CDE4C5D2C9D91BD2275A1B83F65F3E671C8F6FD718A08EA4B583B9882E37B2ED3AFB20E5DEF50AE84480E28EC33A43A302C9730EC644C32FB3F0C9E211E5D70965EE05D424E415898B3C835F83C9B2A210B6C3698BB9DB0A7379C6D954DFB2269C8BD1C09FAB9131F53E58ABB0DCDB86CE588D56A5D3B5C723F84D8698BC2DB8BFD1A317C91B11A22D6CE2E67C9CD347A979C5416EC2C3E29E87E6FA079B97A58CE3D339F1BA8D62F3EB3DD5E11E1700D1C66001A3FDDAFCA6E89DA83065946CBEA15D583C38A67C8BCDF92BFD877C03888804DA9ED7D5BFFF024FD519E2ECDFF67327FEE749E51D82F0CCFBA83D99EEE253C6DE68C89F7C59F7C4515821BB764DD435D03BB471075022EC7BF7D042AA14282AB0AF86909A13E81CC0AB6F0B7FA31FB931BF5723FFFBE711EC8FB64B04F603BD79E162F9ED7F76ED87012F76928E43B2B998D307D2C61DA463C60A443544D1B0282022F100241993781E8C5C21FE91C13962743F6289B9321E86EBB94023BBCEF22455EFD429A920547F022E570949A894AED405ABED4B3F34D534BE3B701E41D330A2F6FCA9EA55CDE6097E22E178863D16CE4EC234DDD464035803F20A02E0E41F0F1C809B1CAF6640D2B1524E4FB37AEC470F46280948E730EFB7B97CF8DE1F1CAF96AA99988CCD314EDFAD066B9F09BEEEC93563E0ABE3E32D5420027E7AC60940874AE686F15F2B0F02546ED93D2B885563600A5EFEA18760EC53AF4F3806A1367BD11A48DBADCD07FF97E3A4A3AA643B3BDB9378A2F7B7E144F6224FBC71536793F2CEF2672636CC53D5D68587C6F1349CD3E8BC459B80DE3280F198976B3675B37B80392F7E646A38591686D9C82941CDEF69049F5F2ADDD9AECAFEC1581985B0575D072D7EA6AD907BAD894DC2954634E308B762592304F78FDC1DDCECFCB78EABE89AC66CAF7A439051885A7F6ECE849A43AF3C476019F479B5F3EAE1497E1A0A7FDD409D67F0C077E1D4F1FC8C79A1BC0FC9203C939054806C4B0DCA7A8E12567A5E73BD7CCDE8E7C09D31AEBA2F816DBF32B0E03995C1D99A088D3743E018C0231DAB5DC164BABEE3CE9D965A5C3711CA975BE6F7E809CFA92F83B1B4B7664566E85A278837ADA734DC1F2847A4FFC92C08BC32AF1E53882012CB25BDA4B378671E06592E1C9B3885D9F8296569DB4699761E491676B51B7F7597701FA9E26BBA878BAB97CBDF18520A53E5EC6D5F80BA9BE50BB1CB702581BEF9C76F3D4FEDEBAB5B076B6168D60FCF2DDFD8D15F104EA242619DB629D80F7ACB337DC77A47B8957D84F6843B1ADF829887B1A94FA8FBE2CC2C7F362CF44812D1CC84FECFBF0D30548FEFE61CDA5C705607F5964631244FCAC28A1669DFBDA8D58E80D8C3DE321E644C22525D4EFA6D242A28EDC339C016BF990202C98819BF4BFC4DA8BA4B0C98FEEBBB0DA346670517BA76E787678840F419547C7F119186FB9DB6D880D23B4571C8A0104A3B57132B21DC2919EF48B88A84E7ECA7C224F9FA65B3FA258B5C15E9860ED076B588D73A14ED64B3D7F810023033DBF994DFA96872844888C0535C08950D99F1199C5B59AAD95B99810BF56D194BF5D20B4684BDCE4809049200A55669D120F9496CE8FEA81B8AAABD49752A426A6D0BDCB8CB8D4666F7042C15C0AA3A2DF4342FD5588BFB741EC03BA41036C2209A5DEA80F4123CE4FC397B440A242E1E7E6E5CF74648DF2799C6841E1E0FF6A56FBFE2E949C32B4F6188F3046325C73F4BBBF916CD79EC2EE11C01B62642DE5FCC314FB8FDB7AD61AC41FA3139A89506FE669BCFAB7DFDF1E12DC0EEB833AC3E2DE79FEC75FB57FDE63C87504CFA0A254F4B3CB7D22348A92F8F3D0B146FCFA05E1B2ED2ADEA592DEAECE1BA78D812B629431EC67723A79968597A024FC89A558A12E4A0B165498769B93083FC6A411AC06D505F0525D1EC1983F2256502E453E40D12B3B4DA4C42539DCCCB5D91007AEADE43897494059A061F77EB67A0363DA5C44D20D33BA0C8A3B44DF69CE87CF7143B80D0870F454993B7B45D16A4E6E531BFBAE714C393B09028B92EE3A6808529B80D163164CD98709EDF5AD8D7B1C479522855B4C078590EE5A5C342A7A8C01C1F54FC914500483833B41910799F07844860E69B16652F19B7A01F1790FDC7127B8D098553FD16640B78E9C78E663A396E49B9AC6E590141CD155E6B111CC589FB0A632171D9D2F094E98D5FE377CC13BD6E945288E399D7E39D78C80193A2A2BB37E3483A3CB96616B32BFF546B9E70D3F91D3F7F796B0D70F7F9914CEB013AF4E95D2F7F31115ABAD5D362C27E50999A1565A26F7B0457766E91F22440DE0A7715DCF11A2897FAA05930A960F9937F44A3301847C7A10AF9BE3D375397A76711786403BD4132B2791926F7D9E86316ED33CB50F6BC7556C04FA8FF57D19EDD7F3AD7F1FAAEDF3186E4DEDA8D7FC825F7E57E7941C6CABBF093A6E88E0235C2A110B83793064F6A5BCBFAA5C3059D7B243CF1D0AED8F29B8EE3340D438CE8DFB9AD7606D94F24ED741B244ED40925F9860F27FF5C3341273B85285F8FFAB875FE75D4D53F1C6D1CD081FBD19642424A87C650FB6DDFAE397FF15F5A97B61826A27B1E150C109074344520A72D232F0C89AAE1021A76F8499C31501B4509AF79E4048B15F0C223C08550EF9D4D0845A68C7B09B466AD53F2636631BF063397C51424F57E342890E949E4CBC28A6EC3B68C59C0335815E5D29D4E7DF880B86E5A38399E865A5A29187BB0BE90B822F6812D36DCBCEDDAF257E48DC8D8B53C6D1A0DB173C110294E6D92EBF0B0C92DB80EAB7B23F1B73A4E221F80BDFF63F591C7C214DCB90EDABE729140884597EAE3F28BA2D7300A308D468C4AC60CA4CC469C4CB78F8060BF80EDE45E496312469553E53FDA8691E4741FDFBF22BCEDA2D9A1DE0B7DE77ECA913F1131CC494DB40BBC24D7C37D996CB6739FC2CA123EC6DDC02CC0A83348669A1EEAE2A88ABCD72CBCF3D653ECC78EBD340BE6A3557BEDDD15B618CC0DAAC10D6D247776C59C4DB9CF19A446F7F2ACE3155C5021176DB40F5E99A7496AC75BCF15E4A7393DF25A02A053F4AFF10B546E12BF5B94B1980A933558CF180ECE63AD7356CE0030D8795CA0D2819C88C921377850163EA5E3F06461EA15ACEA48AC2AE344D0C09F7550EF4991C53905EF891CCA5CA2230E3FDC2873D4FBD30EB312E31C1B391136EA093233665C47D1D1CB148A0DC77926EDB51811EB31E97D0235E551F3E9DD48C5A804BD4129A4BE15F81A13DA8121C368B35FA3F4D0E44DA321CE977F855AD45E1A2044B45894336645C675B8BF2A7190B5B5BDA4465FF624E6C6BE802993F3A72D5A65FA2851CF3AE35AEF407791BD598A33BB09D35BC6A34AB487AC4BD512670AAC5E492DAD2FC373A6064E4F256AE4EB61CB5571960DEB0EB681C4E5DF614C93F823B08B079C307883416002952876193CEE214238855F59C64F2D3F5199060BD660D38A6D6B4174453C9B87CC42D12465114E9F723059A09DC899282FDB99B3DB65CCF8E0761ED01C77890D424BA744F01C2CE675D5AE3CEE89F9A8F83A9BA8972C54C12478BB1197BAE904C7D2DDBE6BA6069CD481FEB756CF461027E0FF677D65109932C4D66B2F0A3F1771DCA28C1BD232F99F4AFF9BDFF6E040EB064286A8067323784C92F6561A56B2A0108FAD2D1D12D87D264B2D36B8534A1EB1B64C013DA46CE438A51555A5889F0BBD4ED62BFA1477CD29CBED58C704430F09C99BF001CD8351326C1D20A0B9198373C1D824F48EFC423D6748DBA7C9326B5C33CC57AE2BB439E7B2064E394FD758F69C544C4AC7B3B83D724FFDA48D6A8F1CC1235B7DF9C404E4EAD0091D747310DE614CD2DD01F9CEFE942F776871FCD93C8FBCDC8AECA6A7986DA4406DE82FAD220D91EE00E57373D586E5F417F0CC371E1A1A584381F4AAF6A45CA9E0EFE1DA203E8FFA36399F1574142275E3FD2DE5211295F183D1C60DF0DFE6A93868642FC72A54F51351C83CE7218F257871870574B7E73233A7FD85166C0E0EF1532617896E634626E25ABA114DD75C80CEE426DA53BE011738C63B48B7BF6744AB19FCA46517AB75784EA03752F0741308584E04560A5FA0BA8DF7313DF7EFB13C709C50FDABDB84E8274E11DF08D7BCAC4361C7C8707E0A49E75EFFA78DD00859C03B83990C3F57EB19822A7E85D3FAB225693B0DA925EA5354A12A38287598771166CDA7ACAAF0210F923C0D1027CF2D13E154F311BDE4820F293CC6E1638D706883BA32FD8E8923AA6E76A2F0C8E96E3AA1842B467B46C78BF8D9DD683B7C3824DB2FFAEEFD484319921B217DCDB0536238D7F0A96E6E19E2284DCB3A4D275880F16D05942127DCDF6047E5C3997692E5A385B4B1959A7B22853542C39B462F2C1B9C7B491B683C16BC68EA41029C749D62226097AA28573F2657D83387EC6522F8F250150445ACA03722EC62DFBCF43AFA4F3F6ECEC089103218A876233811592B23C6A6E4597BAC84857FF50141E8C2AE3AF48FA7338F479A5CA37B6C16AEDF520C724458E0C06CB1BE3273691EEB9FCFEC06EFEAE35B60ABFECEFC097DF96A27C62E057E51C35D02A03CA171E5FA43139C28A827D4D23C61CA083D75B63BA84782E66EE765388585A1098F53C75A5D60E8A9FD5D59DB855519A4625E0B5904EBA6A201FBB76E12B31EF0BF69DFF5581FFA0048588C2F64625854CA2286A6B19448552627BA4D658AED49E45724B37F1EA14C020ED53B9B00BF71319560EDB4341BFAC783885410AF54C182FEEDF2CCD793EE06D68D29F5CA3005C3C1031A4877543CC7E60151992F8B6D2C4F7ABCF4FC9A9234EEB830802417AF210F3300A64B511523D1A7A07C3363B44670B96AD019AD60E24413738B23018044A54E3639F26FEF032A79F3A0738B0B219DD72504AF010EFCEB8C7B81D7C706DD09F0AD3E3E48E1544E6CFAF5777EE420ED7F6C47F7E70386F0D67055B938EE58C5FBE7C1FC75510EAF91F4155E09CC82FD8F5797D5FF160AE9C7501A73EBB329412BAFA2079356C37325C1DD7438BB44FBC9AA050FF418332B3689FC25CE5CE034F58344E845BA4000B7E84A7CE2973ADEF4A1CA633B24143CAD8B8ABDDC69730B1EFDCC7BCB5DA9DE6ACE5E805B5B6C53BD15D8456E0859736D714C198F1CF63219C2884631310B092724126E56BAA394AF1DE0A6155E9B1BD627286D4891C730E97BA186152DD1BC2D4FE99CB73417A13F89C1F534ECF7D9CAC389CEB8239C04BB17D7E597DF1F2EFC9B12D2CF942E642357135B0C4A9B6FA592ABB2C43BD163A1CBA3ABBB6EB2005FAAB70E5008D0C94A33B5D6A2DC665FBD613AD23EE6632AE7CAC93258D73A0CBAE390BEB55D3C56FB898593A00221DE9A68EE2873EB83C600F49D38B7B2E6C90CCF7A07F9D6F20494E6E88E42A7BC0F0C5B81A3F25168D90EB740CB0C9AB105B893FCB9CF43C7946563B9F2CCA419EB49093211D2326A9F3E61FA5486F927C98B53C80FD8C0EE43929C00F65ED8AFA7BC3786900A54E35440C2676BE89E2CDE6189EDCCAC860DE6696F56463EAF426B96CE29DC5321B87906AA9E247709351E8E6AF41EA1E9E01AD559BFAA41F3795863156795AA1331558D25A5956A2BD6D892189BBBC62467F60ABB061DD9816A48138E9B61C3F1870795697D3B4FEA00F863F2AA1CA995909950A8896F238E3D2B2890E181B666C44B311BDC6F91A61D1ED86267626E2B6EEBC74E84FBB4C63E16544046CF223EE68150CA20E51530448A01A660E1AFDD27CF0136F284443BF92745FADB81AC69A0B8BE91A9C4ACDB12B92FF48A2FEB249F82F5D9147481B35B0DC36DFBAA2E0D6D2BDE7FC46D5ED9D0E2DFA033C76E3257C9B832993EDF5121F4CEFADF83061C5F9D137634AE2E2B794B058DBA79E95B6EBCB3537C71469A62DA13392079160472782A34771045CA49B69695DFB17AB25BD306ED094ABF6F2A492C3CB2343DF3CC6CC5E796C012698F40208C2B8C4215A0D48551AEF591C1F688EF21751E2774C46F4B91FA45136DE40119E4AF0E2672EB36C66C89F180E78FD5A06999084DF31BDD0D57D199E2A421FE50954C63D968E27F3DCC0776741B8D6E835547E9E8B4809CB98CC781B4C5A5CB9B711EE31134CDE18739AAE35B2BE6812FC92F7EBF5041C236A89B803213BB76CA7B68F4D233E797A9CFF73446E1F67AABCA545E219E8767B37AB1AA31049740DF2F045A9F20084D300E3C40881E3C81AAF30328D51774114C73150DEDA32FCB4005065CDFD7AA31190B2A72EF3CDF9581EC725A90B7DEFAF32AD527F5DAB8C7A271BDB3DA3457A0ED35B43143AD82423D280C422A091F1883034D13A5DF87046FC7DC87F830D6933B73E0544352CE6CDE981737C3B61542BC240E2CDDB028AC219543C57401A81EFE0358E08BBF6D9CD5981AEBAA54DFD3695C351CD702071595F91FF2A1A6FD7660949DCED824C3F4942A8693D76592B15887BD4473B045AD4D0015BB3A581F96D7F515253451BAF6242EFA6EA3829CB0F02B18206D133134743B9D1805993A6F194DA42E225AC0793C16DF80DE043C49F5CDD9635996181DB76F3404F25B3B9E2E22316C9287ABDD2C743B04CFC8F7269D94F3B8E0DE177F8DB80F7FEA0B411078AC7D351E62B0CEA3DFFB54711166F9CE6512FD2600272B583F305289F6722280933E95208AB513129B1107A5B511A18DF542FCBC85772E6747FE11D79B722877D45175DD56A9C2293C8E1FD1B3334F59C0E0571049B2847CC3E02EE2C98CCD1555420E5E38D506DF3D08F70CCF82A022124CBA797792528139E1FBA1D325681D1EC8A28184DC3C157E89757196CA201E1A61BF1CC1EB7B6DA3AA89F7383F1ABD8DA13696F4983C4EEB5BD770CA906C7D1BCD580FDCD4D71E183A060F83F1F0AE26E7852B8A588159D8FD14E6854E251478AE68E0D1DBB46BC339D2AA6270CF76DA98D076297B4E944491770E361888556264C0D7755300C3D7B4C6BE590C719C1B71764B9C59B19949D2527C0E780249ECE82535C306008E4AAF3049C0150E160170DC0433CB0CC9432C8D6AF52FD0532B69748BDFCA5CC696D9854143DE930A0349E314DD194A7853AF2CD58E60462427F3F221857C482528174D3794B038ACB955EC0764FE3CB927FEC2E9BF998C270EC67712EA495C2BF57B0730FD21F0C2344C1652BC172A7B6C140F0DCF5AB0D13A13319F294200A389F9613B28BE59C34A7007A23E13837CCCC83859A518A852ACAE33D2309D2329CDC5CBCEF8406D218163A1CBDBECD57E72DB4B5B39CC0462F518A532DD3BD285F460257349F27A7D32B7CDAB21A7ADEA77DDE09723CFD6B1B06DEEB4F4B024EE7C5E88161EB8ED92D77E25D023B095128E31DF97F4A2A8F0F31254BA9BB75EF76D8CCBCA40C05EC6A1C38C6882F0F394E6A69ECA05EB9531B268F3289AE54CD1F5A35875A0D6E183FB496A8239954431515205F83194B8CA05C2637E6AA2F15182E2CA29F17500A9FA10EA5AA25A325C681C2CC33B28909D8FC6ACFCAA3DB76A8E41FCC4F0C882EE881F2547CFF1EA710EB35395C0A6A4A347EB260A29AAA389323CC8DF5B58C211B703FD630DC60596B9031001913F7E00C088BFA95D47B39CA825E92888B973AE26375952245227CFA8B29D367AF69AB7B0669314420EE791D9452ECFE248ABFB0BCD54DAAE50B5652689229D9F9A781EDDEF8BBCDFE3ABD69D67DBDE80F71153F291925CAACDC393C9F0A7B4C9E5E670F87FACB5E374074CE36B10CD369C407F0B63986B163AC9E32DC2F4B3A52EE1C56AA7810A2A8CEDBA50560436C35E71D219FC7D1BEA2E84A38B3BF56E3715C9D52933C60ED354D35EF3DD9FD6061B6EAD24387F41073C3349816B11F91DF7E5CCCBD5E92A9F88B587EAC22E3D2CA4855F4C641BC4DD24E31AB72AE73FDB5C5862A180ED944F70CFB0D61E5499F744B7BF048E940CB63D5DEA8E4989E867DD20D4DCC51B9C5D66A65C0C4EA5189E4C7A79CBB9E204E0AB20C98EC64D3CFC14AC0F9F5F8923BA96F1A48950DA177DE445734979E0F1BB56FDD85E75D366A132E5F6F8DC76382E9F789A26BD5976FBB6F3475D0B351FA90E00ED93DF7D55B6BFC8756E249BCB6255F376DFCCBDCFB5087EB92187A6976F78436AFE3590DB5215E20E2885A218CBAE7287E2A80F60F84E11099B4F618F8F2DDBA0D69AE5B8E360CC298F01E8423275376CD4C89B5D3D554C0DCE46F18ECE07711877BBF44837DA0CDD0978C817807CABC0CA545BBC011282D7B81F56CEA0584846BA885A64F701C5F1325CF969BFE0589AE42FFC18EA6007D480D0A70D08DDB96E366F0D838DF065EEFCD3A9CA7DC36CDF52BE150D478B2A28BCCA9E41707CA5E720A4012DEF416D350EB470FC3A9F0082ECFBBC8083B2D3068293ABB38A5634CE65047A3986BD754EC96C31ADB6C4359538DF0CDF6F82C4BF0A8EA02658F6CB89509D48FE2383A0A82C091221D14D19ED233E539C17FDD6C77409F0FD0A2973368345E3895A7A541A5054D794BC27124973D07D718545EDA8936FB07D9C2B8605C1846A3E97451109DECBC4242CF2B8CE70252C6503C5AA5FDCE912CBF728847B4E4C500CFEDE000B95C98CA02B5C229631EC2C0F1A72B1EFFFF28DAC3DB060B6E028A51CE8953BBCC926208E454BB76F26F87025B41EF8F00B135AA9FEFF1C0BC50EEDFE8C1DC645538D3431B76EC64C649E198F7679D5FBBD5E14648D0CAE5A3E75D2967BC0F9C57EDBA3FA8D79CEA4521B9D1EAC0034ECDAF1571E352BEB68B2273FC74E0CD6EB320F17A3BDC77425BCF41CDF17644E9DEA08AEFCB5E2AB9803799FCF92FCA4BC21AF5ACBF3FBBC1F73B184BE00CB6551DC4A79F5F688B02F2D56159E2E8C31915FD0F69ED87D3BF1399B877BE8E276E8992D327B45231EE99A18121A918A7B561A91B9786AA858FDB29C4E02D522BEB09B59F07BC812F3FE4C9078E2CD7CB81A51041E30B42FE1C93E283B4EC8F7B51FC8E64CB4DBB014C4737466B592C3770653DD799841401D4400054D7E9B97418B1F15F6C8F225FDAACC6DC1C101F7DC1E0AED4ADEF4DD96D456129A3483B6D846C9637153DBEAD0180C7D463F8654C2D75523A6D3D0D450ACAC7EE80BFB32AD0FF901E4763F87A9AC926A06A34FD2085026E56EEC05E8E31392C99A4C5EE2505FF14E54A832D061507A8FCC80172ED58C9AE75F19A282C05C4F100721024ACAA2B43002866390C1E97DDF1A72A87882DBB1BAADF35E41C8E98F62DD1E33D96352AB17D906A4EE1B885CB9D47F9B7A24C7CD88E1B5084F9C301BEBCB068051D77E2D1E0E3A07639472874E6F260156A0F17417310F08B418CC6E7AF47B1825D3C045293B57FD696EE5485995C00E0F9D87F34FA2A140F165B3A49084863F4E2515CC0222974FB7E3486E7DF3E362E7CAB2F3EC67ACA16C94CAF6A6EDC9C17A2836F60442061FF0E8F1C8D6E505421E50E785958A4D78931691E79315CEA72D2E9C31480C40E02BA76385BB78D8AFC875EA8AFD516E7BC75A58B01636D6994369738542B19CBBF463F2E38A6F3551F2B93697425161D993F61CA2D55BF45B6BEF9905FF1DC1A45CCA04C96721CFB50C04FCC23DA2B7A4D746FAE8B946846058B977A86E132546A2C19AC1C97A690808766997E000F21DB294338CDA9D47DA1A971E03D534D9FC9C133F42C9543EB80207245FA525696D7CAC223BD21B4A3686F8010A9CC52847EB5E9229115BCC29B2F8FFCAAA4A1B7A7192A84F969258F8E96D27DFA9315629A704E328DF9706046AE3D438462382E2BE6F518A122ED8492342ACC67682239FC3530858BA34F19E6BB7C115ACB3FA69A17C0A9A9240719C33F1AE7290A9165D5376B209FF0D2D242A83587553D7EEBAC9024FCEF413D924A2F48F0A37873753898C8AE75E3A95EDDED0E05A3EACE648CDFD5396D71FA80204BA96708564B147CB0A7703AE1C73A7DD762F263D6929989FA361343EDC28AD1160781ADD18D2F5A7628CD8F44D1A03319B9783E848CA98A5A9172377DFFC5FA037D16797648630D3EEBD4AAD647934CAF4541C3EFDDFE4FDFB94AC1D715CE764944C36731AE1A82D0DF664CCD7AA73C84C11517024769D04AB5665CBD97F9376DD88BB902B0C567435937521A35AE658F091A45B63435EC024E3CD204F40044A181DF6E244E153D166FDA23C4632A7D504AA0A3BE31EE41CA68B1B13C5BF32EE882E48FD5632EE804EBC6872FBBF3070B1C7E085E3BF0D18951B9E5E7C54543535E36F9503CBEB5443280FC9B7D506C45FFEBE2E31A8175779B18966B67D4011D5CF5050B2448FDE0C2F80CBC5AC29D09B830B8BCB48BBBA5330FA8579528B9730EEECD7EF26EB63FD398BF68B0D5F94A4E931F56F53B2592DB3BD2843F124A7F8B4A9219159D738A6863F5D16B69CA0673464497C830DD7B994DA4852639B90F21620BE79186419ACD4C0DACC556DA01217F95AE384547838FAA33D501FED0403D9360B02BA6835AB4791C215D12091B2237EEAD29E09E22D18C4CFBE924A8E84DF4AF42F87761A6A13C1985B06A11BBD0FF68823F6B021E9A2F9E0D477544D69323187FDA32B5E522327C4AD4B86EEC551C52E0D221F3AC830A1BB0F8F13C287C91234C4ACA23A15916C2B68D55192347BEAB141D9FD38D2616EBC4482343D6AE213BA9C225F093643AC3B33E0C8FB719A30C6B8436E6C5A77E0E6171047435642FA7309EA9ECF9D41EB5658687C4563C9B1AF01685BE5EFB6B511C186331AFBFCF661975DED7A850260C0377B7774080F5BECECBB5D5AA89ABB80D197C1A6A58581F1EE03C135FB46E2F3422A9145599C600FA6ED05A55D69FAA839B40A3DAD2B316F55875699293ED546243D60A5FE589045B9DBBED98455BC85FDA4B58209CB9A8EB368F1DEAC6521C0B8CE9A5BEF7BA9197F6C39D439CAF4ADB960B8FB29D3CE1E1415872682540980BBE7D1A018268917C57B256E87718E777E13BBDB350EE25181A5EB7E4B12FFB8624D575737CF2973C99BE8A2427E063438E750F42B4444D6BB2A51827D520BB93F6862B3117D8A3A79066E90DBE5AEB6BECE99E277D78382780D066FAE50F0F4075B6F92A3B6A19B10AFF22BC9E715DEF54F752C1473532A0DE8830C4A71640DC7A2875EE4F3031C9F412F316B40281F0D49DB9FA5BB06E4BCCA9C7871274F7C9160C3735DC6E0D20A8299DD87555554EBF9C02E4EAF9DFD56E53CAE3B56E78015FEDF79748DFB46E25C11D36A3FA4A3C6ADEE43530D7F6869734FF3663A4236706C8EC71835B7A8D495AAE5F050B87473E1D4DF591B90BEF85607F5006349250534BF06261F09B4ED6337B2A0A1BB45D2E59A461197E8CAE098406E4C51F9DE8BDD041A925EF5AFB7832D3E4C816F4D13FB6E2238B387EB27E28E7DFE7343D29A717FF0A07D35CBBB375179B437740DDBC878F2459E97C2C3941CF2B051ECCDD0BDAA82BF13DB91B760F709003561B1CE1144F2B66D048ECAF510174F992BA5FAB55E79613E1A1107BAEB84FC695CBD1DF85CACC4F4B02A0DBA7F88828F385A9B3836D59CD1D317E1A1A3487BC4A27920C1136159D4E02142091D7B1DDBAFF8DC3C89577AC55AB026BA14617601BCF5602F488AF88FF88220A485BFA616E86E6D2D05B5031088313753BA6E6B3528640EADD7811E3013CF1A547AD96589824F848F1A761D13C794D21FA25443A34E61933CAEA8388FD23A48434E9F333C56979F5A0931A46C215C01532528906C3131DA314582DC6C5FF15C1B621483F6B13CD9A84E3D994462F4F168E2D589FBEDE0F0574B15E59490C75C5FD8775B86072523BE8D31790A8AC41DEC428711823A0B9BE5C11AE55B30122178D5EF7E215767D49F9457A2EBF250ECA5191B1824C88549388ECEC8D4A46174C45AE682DF4D34595E811176B567CA445BDD3E73CA57FB08639E18EFF5E5FC462D1D0F9E244B2885E348B3CB664E2EC8FEE241F0E7707A57E9F3730563E1E9A3EEEFCF84469FD7335A4AEF5FDEC67AD0C7E425AF3734D435BB7AE585BC012BA42E4775EF637F2CE2C68181254F318E36E03207B0B7E035F8E35B8B6F8E7AF352126F41836E2623E62BE2E4E89BB32D892E8AF5D8F294D149AD68AF00785AA007D030F7E1DBC9A5265624065A74DF27F1F2AEDCB756A4078F72D3A22DC0EE4CA11AF357CC1D87D2E9BCB61CD98736C9EB5902982D69934AAE3398EC705C1165710C392E3219883A1EBD1C1046103F2229B7315D1D8C3B9D00C0AB72E319C8047B1305BD64909F1B16FD5D61D7AE7FEBC81A2AC1005F1A228D87392EBBDE01D0386C338DF659D5C713CB02BF4A069B5662E203D2900CE27A15FC535B0EBB4BBC3AC19E8E9D849030DAEDA50E8061E55CB71205EDB08084C6D98E37F65913AFB294B7404A5F80D49736D01784C588DD056CAAC70D88C78AE400A490A8EC7B3AFA2AE39C83162A6A4CAB065B97FA7AD98B8038EC5982A0541C14D4465B2A941B4B6B35AE39CD3A9F2B1463A804E2F25657746DDBCC8FCEE243AAE00CD96F557DB7705E6F8D6EA56116459B161300FC75CD49BFFF6416474D684B22B16A1E5B6A13A250894DB181D51EF45839116ABB1CD3789F0013C4D73EEB2E5FB4511E45C934CD8DD61E1385DD52DB8C85AA6BB1AA1E215094D70B92631A339ABDB39320A32C8A9BF409826EC0A1AEFDB3B48416A88F1ACF3C4956D7B4E4B0729DD84E0F1D54AE1E11D794631840CB9D3884ACAACE38ECFC991EAF38D50C734E6AD46D4F12FCAA20B3C978977C7EA54583F53C7B6C95B82F94117E5698C2DB9BDDE03716833A570AAB0C73DB976D0B19299DE2CEF8725D6E4272238115B5C8BB4CBF4B0728DF47F378FDB565468CB12D25B4B005268D9FCE8667E9E54F5781BC901A92733DE9270E9135580DC91340AF15CD5CA2D2DA8B2C47504678DB66F4C457B649E0B0340B9A384BEA52243D9C492EDC58C94E0618DB84AE916E54238DE31879964F34B6409ECA022E4C90005DA3D3C65D71E76933D528A83F2A2129AC5BCF3543A1D314F93FFF3F003FF47B74D6132BA9A331F7B20BDEC7FB69326935C1C89195F11B9A2BE6F511E668323D1258AC95FAA87C069FB511201F97CB15487D3488608EADAF436A5AAAC611211CE792F343991BC736E2AF5F9F12C20148DDE4D43758888DEB4633AD5C035D2AD86E8F5A0D78F331993744933F4D9DF00688B75FE50E76155B3C1E19E1C12A23220CA82E8FB2A73E815D8CA38933BE1C5D790D175CD85318B1C2A5C30099EB94365B8E6C40FF4636E52DEEC2A1BF5ECB14D08C5794AEBC227D32470EF7B2A02EA7221A8999235AAEBC90025527AF51E96F6684C1856BA4FF3DA234E3D2711D17FE4F6AAAA25D137DAD1E8B0446F004AC3D092D3431F6254FAD1F9B22BD26EC86B9BDEC7B6996B61676E5137376072B4E1C7E7304ECF3331F20C9A3C714BB07A46C09F521A2215F7A7C4AD5FCD9E9911617EF8596D8D60BD70DC16964F972F4F4FBB83B51EAB949C5553563DD4F37FA1DADEE67F8B96B66D2BBD200745A052EA21EB7D90533ABBC7D951933D267D34C8E757BFD50EFDBC856C64B811DCD5C8DD18D7474E04E876807531E1380F6A48BC46869806C7DF9A16DCA26E595B2CA6835AB5E6383B1CAFDD11AEDB61FE7E1E57D43C0FB7779DE02DE6BB4D3B86A07B59A233EB5A339A65D7A674A084E368E38BC13C38FE3D3CD58026A16F48EC513332B49A13A09DAC34ED41FAEB88C00E855C481A422D2D95B5890CB889741B77EC1447D799EFA9492BA5F3BA6CF22B86CED01600F661222B6648D8EF522C6E6A0FB2862FDE641D325C4717B024F8C20166E24BE4C6970F914B9BE78B4481C312C226CCA82DCC6EDB367C955C590AADFB1A2520C8E6941A9B5B0AC2499FC1CE2A911C4BEBD59F99F627B69767E920B121DA2F8F2CB45E434678283A1C2A777C6DE2A2ABC6A0709480956C719D13D1202824F6F6324160FC728A2DC449E1CDE8A58A4F385F315B12C701D144FA8797F455EAA4F8C843C95E2963E26BCF590992DD0245C645A65748EA727FF189B3728E0A3A2BA21FB4D877FCEC369C0228C5964DD64F5D2C786D4EB433EC4ED484CD3FA09B6A008634CEA40A147BD51B2A7B47901F05A31C360B0286DA46AD41CC7C1C646B43AF8B77EDF78CB501FC1091D6D59470C6EED6F78EA8B905A9A3756319DFE9F2880602E3D2AF4476126968888A17844BBD8BBB001ED2B2FE8F152B71C73F2EB63B9F24FEA013E45FB711FD7617EBDDDF701236F8C0FE10AEA5C1337BFD618550B95ACEE3E563033E72B1ED80B353A2CB7854B6AE5D26CE46602826E32B5B8E6652B52939E6C75A551379AC1B43D1CDB5509C8341AE1F048C44A262D9DA8412F091DD9A0E79C00DD6FD71BBC15F9AE7B058C22757C383F1D68C1F4E9F0D2E0DDC7083DE361D3D1326976D724C797368B7BF585BEE60B8F588F443334B0231E732E45275F2CE440302C130979D38E345AAB0CC745E9318988044E973E4A884EAF9D823285B117CBAE4576FEFB884F338CAD2ED9DE77A871365D1D9E750AD6E1A5D1F5A7D7758D3ECDC69FECCD2EA0E440E4E174183586EA8BF5BCAABEE5526CDA365792466C974E8BA04646D759751A4E2D6C78728785BFBCF309A33733A077A5F6D81677067CCDC48F3F4E945434FE278BB934B6A7F1FB20881D47D058284079E5BF470E7FE947446B5D1E16CA64334C6D845B973E9DF39E5EF4F46A27B49DE00E4C92909CDC47881A05C5E61DC179F2DBDAE914D1E322976CA8F7449572EBAA7C07E520EF2E500F42B903870EA1014FEF18537935A82DD1B094124DDA01E278BF8040A9D13E4FE0C41D97CBD05396EFA37BA02123F668FA1FCED4860F74E5DC8B73B79325E1AD25A8867D9662BE2F091F2B195EECFF67BDA1E0139E26C4E923F0905BF3413B4EA76AA9F645576925A22AE7120F68F05B0A5524163BEB222FCF9691941452A122E5658FD1065EB6D53A9D6BD9936FC6F786A79F4739ED0D0FCB4A466027386A2EAAB1418B49A392CED40C7DBCA95986C7A3ACEB0110F004326FE1F8B019916B84BE7BFDED763960F710E81E3C0247AEDF2715B18A6406AE424326396A8AD20310102F0730F4F308612341B53A09FAC17923879238A3D3B0A890E416837678D4D01480C5B0D728EE70BC40D4368AEDB502B562D44C018417F3534E2526F020E15E5D18292FF08D2E26DD895F9596E198C2215BE8C8A2A8F9F4C86C9B707536139EA2060F5717AC84B4DF30DC93ADE4ADF1987D74C413E01783C5ABCFD6E5676712E0D0B26BB29E2476F7796D6EF80B5489628599758319D1C18A1E938FC8B01F172DE9BEF5416E2BFBC1407FF093BBAD58534672736B138BC7847D4B05B247CFEB527A2E1A42ED691DE25D91B2FF797B3CC43E74E1D51FEAD0BE893F264AF28C6F24435BF6AE1E564B8D2014E8C77477BBCB53239E2022268EEF8750867568DFC2DFD2D8876ECC1ACB18C18CBE5998BBBB8FF475F743818AABA1D0BFC4C7C6A0F6E7CB66F9728DB659640DB0E399FB501D76910B3AB89BE8AA13F67270F6A9BCBB909309FB70DC3EC407AF1937DEACB5069D4CA67A0EE553B0005ACDB8E0AD22E5BBB24F4E55C86660CB19D4B95EF29B959BCC2A7FC54B7EEBB8DD16FE31A35CECAA12933D482B07452F1AD4A7F0CB13DA5785A4C2FFE5F33EADC28B1FB027BAFFA2E84E142797E4A072CC748A7257B1CB057C884DEF770B53558933C0B93A026F32596800A844C19AE2D265D86F50AF2C2FC39540A32C19655E88F26972D3A62A24D6316DF8B7C6EE9586C9EAEB112BF29E94B52082CC11A8E9788753267EC3787BDFBFBF87F16D1407F13404B0175D349868601412F03669BBD2252DEF95C4435B834FFC16EBA6F4EF949568A0DE1C6013EA6978B2A6FCE09E17FDE8E4CF33BE61F14FA9F32878A160995A5FA3CB8C052492ABAC760D4BFAF47B5E0910277E6755473CB708C0D4DA8CE02C6FECA61267DC69F0AF54A6F79399E492E85437176FA2018BFE937568D166C36924795088EA7CD1FA108147FDF82A0E61C22BF618CCB78484E7272184493C8770816EAC6155585CA1B77598B6B910C872D99F591943D38D957B74867439207AB5A10BF64D76DECA3AE50CED3CCAF697E8D95CF52C61BF398E00C63D72192D0EE7105C0029AC76C32AD05150F77A51B042B4120B77E4D4779F9626539138406E616A44AEDB47800E2C23546CEF5583C0127174195099C9EE126CB627452F9EA16B6C5EBBBB31233EB283427F7CE2B7F84F6801BCAD47C1B0D4F46EB92B44E5846C5B7C3460EE3EBFBB28DF363476F08AB4F57283D26C5563CFAF01730EEE4B1EF34B94F8CB90BB1013029DEAC6F6E132C65B81BFBD95164FBFC7AFC44695E82B443565A5AF0BE322E01F87A35AE981B53659D2AABFF7872F72B21130622645401FB08C5CD53CAB0A0DE21DFA35579871480DAC90345D1E02F7E957071A320FFEB055A27CEA83B1053D27299B3F33D721DAE476D227E434287587D3ADFD5983DAC894CD7C133E0AB27FFE3044F45808D8C705CBD1620E3D7CB5B6FC0E8646A3C72007DC86A1D8AC82910B73F4AD1D4007967F5D65F795BB88B34C79C0081538C34BCC7E40E30E7F56D4A69CBD10FD9AF26C6345F00655DCD23F6B69D317994C0AD5593954655C96C9F3C22CD9D3AB3DA94E8E711DE10F3D2282AB4AD6040CBAC477BF4C598538A6FC81A01C14383515115C4FBD5020D5F1228E2384EAAC746358ABE7B0C3B7106AE9504C3B6ABEBCABD79707E41C0204115D00EB80016BF8DE55DEB4E1A43B4B869552DBE36A0C73E41515B8FD87331B582E9D8C9A28F7F1AE49C2DEDB08141F5F7F6ECECC5AC28C5828E5543F8176FC2317EC5F822A8A2F3742EC7034C804AB41F445E654594C7AA6EAA520F1BD5EEE6809C2A1D60BAFC6997699F218E99A8E583C0C28B48EA303A72FDDA79FE9D1082274395E31EF118B6E3CB64E7E59A48120648D6D3E5EDB3923301B2E98B8980982BC2EE38B733D0F44E88ABC22F469AA06EF195CA170216F1901E7825FC708556829C46F5AA2272AD88C755FADB9822D750EE4A9F28C0C481FED560222DF0B8ECF0F2F472E338FDA1F9BFEC25733C9BCF8D4E6D18F886E796CDCF0A68C135708CBD4022516C79582FCDF4AE096E965B7CFF886ADCD739543BB501B27F0B92221CE72B2617C82AF62F46D58B1018B1F7AB2B06D89FC821EFCD3867BE0F15E90EFB2C016698B4F91E558978DCCC233A70E4B341CC9776570C3DCBDEC3B90A42C01096DC05E7AC230753F0462A0CF34D2B1DEEA89237744959EE6AF3C4A3898EF153BED4C3CF7C5BF7C0E0CEE9652CE3B7A0693E11586AE28ED6D0F375A318FA919C023EDBA583E49974B2B57D81857B43B423C31F1467D0CC1B6438C6EDC251B3A6E148A92C9E08C1B263D66878F42755F447CABC15CFB74E6BAFBC53AB650F8006ED540E91C63F2109224D2BF365FEF30D4623C9C9EB80117D5434C0A9077EBCDB8EB729CD7D98E58982CB151DEE96B0E122310E922FD54708367DD6AA8110DCC17CAC9CBCF30B781D61E2D9A1A367AA71A0B6DB0AD065049A9729AD2C0D351BB7394AF9C23F8E59BA14D9564DA786722E05CBB7723F697C5540196CEB07EB59593D9DA54F7E8221C70C9E98A02F74898F1F17E648B6CB415E7BDCDB2F74D3CCC8E05477A66FB65444B479DF3A9A949D854A6B530D6D5555D3B0DF29FEBC34C7778A9871C397DD24026502B0F936B1FFC80026440C4F9715BF761F8C661DD06097E55A411B9783F2F5C332AA7E756A095AE139C5D56EF634B2809632780F584A9CCCDC67590402B9B6F1CD219284F896208A66D92164A08482FF8DBD4D2882E63108707A08D3C090B5E30655B69A253C2A9EC81553EB2FB11A813C1B101AB6FB5D82A95AFC587A2C9651DA8353803FBFDF043EC86C0338D0217C46DB2552B196794171A266E8FAA003D6B8088A2E0AA69D12AA1C28E80E140BD5B73D4A1A7CE534DDF79B35D6870F6227DEB2E2EF72B869FF7D6C898DBBD92221A253A90CFF8F0247C03626E2AEF0CD4EC1E511B054C06DD172A5DA32E241BBBB73D13593B83F4E7F3900A8144F9C79A7A98F1483DE501305CAED2C7321D58FDDDC077ABE644B5676BB12F0FB24BED16AFB0DF7D930D83740C2AA10C36BD2AE04234BD9E46029930228975B0D07FED6535F1955FC5AF3C8203589D32C4E104AA98243E4B0A4681958ACF4F7FF0842AD56D978E19BDC37D46E74C2125B719452DE664C53B1DE7D516676AAFCE821FBB9050ECBDC4325FA1B78EB5CF0046B5945474E5720605E233BBE51B8A5B627630E0A9D6B0EF20B16015164A69B1D826DFFE85E48DDB74BFE910297BA5B9B685ECD88FDFA480A30BA70E1F20A676457DDD26B7E553CEBD01D96E8C518C50D000D3B11DF188D48A9CB9BAE44FA5DFD1F80A77AAB8862EC8E04C34D2D2F4D39A174B9262D236FFDB6A89F083AAEDB82B1746868B77F7B5A4150511F0F7D5755210D3C3DDB32DDA892456EAC5F6F86D5008FD20C46400CF78F5B1176842DEBB1BD5515C6E141ABE519FCB6D29FF244D305AC68A742F01BC078443FC87B8DF698938B093A94F79A670234C14097DCE86ED1B7056F87DF009ADCFB453CEF7AB1F5E74C0664A8CBDCEF1D6DDD674D5CABAD29C39A766BB4C620F35F5002BD461C6DAE74C67DFBB7FEC001F7F82DE3531C856D791442F2E0569E36A5D92FD9869F6F0A085B89D37709C61863108172FC7E22ABFD21491E0F5A384CB8DD0B97496BBE7F1E6ED9A08B511A1912A33D243CFEF1E2AF2349226BEC5A8AABB18292D350F5214BC770B9B1E81F1277DD54AFCFEAB009AE5BA5AF31D18FB5F5E1AF43D4D93B06B9C129DE0579568E82B0E9022B8638C7804761D8218A8F3B79B0DBACA60E24BB6EEA8C75A4683236A88FC6EF5D9AA57FD0F76690BB9F8636F96298ECBE43B9B086B3BF8CEEDCE2D5A34A4C5B9B40975C0181A738EB67E6F2E15C3FE937C9F29A17FC96CBDF8E45F0156DD97350B1D4C36FDBF751E375CD456F14E9E46532CD017BD7D15E6C6B6F58D4355EF93633E174A40D3854A9412FA608218448094EC7BE8D8C524D821821960F2894BB8CD36B2111FB5B2F660F44A48D439C4BCD4A01FCA4E5B6DEE45EBF043D9D0622CEBEDFCC6BEBBE186045099F558E51E966EC11FA2178E1DDBE46D54CC04D1AA0EA7283C63FDF4923A6CFEC115490F6B20E7CC56D7C9A5FEDDAD11E808B8711711006CD1A137C2B379C5CF677645024BE99BE5CAB4AD414F46D027986A0416CEC28BD2D40024E5C16C2F96E35EA089D2844E483FD7FFDBB15EEC3179870765CE73FBF8D801B4B9D3E6EBAC9B9723796691BAFCA62A95F0C391A1173C7F6F97CC33F7421952CAC970A81627D84246E03D376FDF666A19574ED9E2091A8B9ADA0DC02ECEBC68BB2E4A8161B5A73E690A07E26EF54548D7E78998F4E03BA5E728540FE19D01C3A8F324F08292EC316C0623421E8BAF3CD0708B5F2835303B2D07F9083B2298D12DF61F814526353E4133C45BA09889E9F316B1EB490607B1F957DFEEFE54E84E6B5407CF136F23B1ECDD2A5FEBB40E2232158556920E120C17849E912A6326AE385AE765D6E5CB191A4FF0D6BBD9FCA198C048302FC2CDB8A26FDF548F59ED54FB28307603B9A13AFD41A7A7E2DE754E915FD2778F420ED071EEFC48CE27CA2F4A198F96C15AAF1A1817887715055356E2FE8D6265DB32D20BD12F83DFB1B147BB04C3BAED98E83136C14EA64A437F06EFA82F29DE9A2B3F51E7989B17F122B9412AFB1E7A0BC8709A44BF2CD02D03572194AB2DB9F89193F75C2B8DCA8FB9371575D967F6157E03EA1BDC0F8B77F12169DFD80D49DDAC27A45B16E16408B05ABF97534A15B3ED61F7EF4F1335AC771AD2688415B705E04D3A8335CC4DA2EB4877894C549837825FF69F9A02ECD4E3D2AD15A04755A16B01A7677B82F9C74B6950D51CF14163B7F0ED98F229919DCFA42A59C1D930AD59511B0D6892DD05D94808420E9DFF99F00190C9A0A0917D7DACD352EC34ACBE891674AD06332CD6930086132B5C2A2E68C546B079E810B55C05D7969BE65D97A18A11CC438CF655B60C7F65FF3806F4794219E66EA2227C72ECA1A73ADB823F68E855C8B1D990CE1AD989A47E6F119EB6E07198A9B57CFDBE47E37E79F657F5CF73BC39FC2FC5C8996DA2528BF579B882E637D37FB1E3E88E87039EC3B3117637E691264A628939AB32476A24CF23EC1171E1504CE04351F1F8A74EC2617F8F668D62637EDB8637678F50E0D40F707906B979B07979371A9BEE717DE98835EC413CD2AFE22B05D548D84290F12628AE51CA9E03901C0696937BADC32DCF06559648F29ADF3CA0A76171B64ABB3A5B52160F621FB9E03146F6FB931B83E16CDAD21CFB256AFDC0B77ACDC0EBBB14979FEEB3FDC9A62C0C998F988094E9481FA968FEC2403BB85074253EDAD2C24E7C40C5E3617B3B0E480BEF5CC2F0C542AC57FA660B5CEB83754E83689E5D8774446D4A2C9370AB716CD7D0A1D6EAD35261DEF5FFE50565DB183465FB571FE4DB998DC8A9858D71ECFDAE54FD7D510C601F9F3012394250EC252EBDCA657A8599EE8D714B97A36466C19B5575F8F22C39B72994D2FDCE2BE48481A93972AF0301A3B737D2C86E3124F05F39A76B065674427A1B168F902FF9BDE19CDFCEE957244A7CA7F09732FABFF4E9A6D511895769A282061EE64EE39BBBD624B819C463758E0E941A3F7E9A172E00D3AD53B0489FC2DB84CB0BB29DD866C43B70CDC4027AA93CDF5A94B090FFBCF27D10E7DA3997AC4F1E86A67A6AB59D4E23193EB6DB3D06D47F78FF2C80BD4B06A5C910B90F21C5B3C0507F8C17D3D4AE7619D0E3485B3A6B3536AB3790CCD842E0E248D55A53D14E8D2AB91129CEDC7DA859FD02ED919CF79F23FFC078208FA234AA634FCB7300E58E3503FBAA21ED9D6E7CBC12A0B9DA390E44E7036355521B369A935FB6A3561170CEAEC879EC97C7B4ADE71776134F952BB64B60D45370364F67CFF693C142C9275AFB89816ACC3923B846594467031DD12876A6CC6622343B422EDAFCF905AC6CA2CCF648A9ED401C3830854A4E1C299D89D27AF51E3E7EDB28FF9FE25003442C459385ED67C4B5BA468189A42BDC6F999613EEC8F8224E4803A6CBD2DA308B88BF8878EE8AC8DBF76286A5DD78009D8BD15E4D88A7D6A081A9CE90142308768B230BFAFC02051BDC90AD6F075B04544947DE044733EF5EEBC3F8B1488157346550A7610B17AA1631E5FCC070EB6EC196230EE79F54F1A2BFD6F5622E611125E43CB36C713842D897BCD93EF1584349F728EC7732B188B93F02D178F620351D3C10F340CF648FACA0396CD06608C24CE87E39A35CFB27850001EB95268F540491E2F6A15A86D5BB72EC86B625BB4A39E75961BB08C288BFF0135A9B31111B3AD3F4690B7CF06D76512720C25F62AFF2BACCAC0CAEADF2F0042B2E149D8F7CCEA3B51072316D681ED1F05F437B530E6DF7FD0A2A3B664BC3D6A7BDFF8AA8DEC1891AAED55ACFB47148D8B992C2F9D644753AE7A1705557DDD9B4F0E51AE064A553783312E82406AE0184AD83834C2AE382BF6FCF4FE2EA558C3965CF6EF6DC8706BA627791B8FD43D84231C595E67B431B625A6FFA8679D5107A88E4439E74F8FBE024FE20456CCF2B18171C9789B5903288542100E4B699FBAC6BD00B750A91E4A7D44A14295DAB2FB67B55290FFE998CF943C851369406EB0A7003BCE1E8FE840D33BB54CEC5031DEFB8B9A6FAE0E5B71C8E263622CDE81029C3DBC1203960BA51D270ADCC575412298BC85A8A20BF2EC7C1FF4B8E9B1C014305FA0BEF5371CBF1B3AD7EA804FB83AC8F78930FAB8E07E2AA2FF20B8405AE4106873F1DACB1C6C26B8CE2370FE20D065E309D225597AD6B2E85012FA4537AE8380A55AAF5D4DCAF59E621385CD997AB2B04797D47CD2A8C6105AA0FFF9B50F6060971CECD9E081244ED35543CE95221ADA29CB424859CA71D1E2E056A96C8827386886C7C5057FEEAF1A8865F016AFB6CE66F8D29B26E34C10981A3572F06E8741EDF20DE2677ED6D7B1FE199DF693D090A06B4866F05D276D8BF454609DEE9E4C48975174548FA0E1853B93202DF912644ACFCB7924193EF8FB8807154629220F264269652A347EC9ACADDEF0DBA78948EF0F7896098331692611B81FB63DD79780A3140D60EAA23515D2359DBC4BD1B7B728183C49F28BA144B0F6D01C82E03F7B3A040F0B85B210CC1352D2C3C58ABD12AAD610A0C26E81BA2E1D7030BE72EE64EE140CC34149032DAAF57CC5A476B567CDE48032602B3D6759E56DCC504F612E8D1BEBE9FE7000B61896668503A2E7B2076C470BC549F92AEA06F8F65F9A7798A99D7658348D3032E9101C8E900845C43F855AC05063B5253BE037A63C2EE76B756162E113A903CD475D1DB149BDC533C653196C5566AB1ADAB259A5D0D22C4B39853F9DB270D69C93110E4324D4E93EC8F58332887AA351CFBFDF954503225D5CBFF4A65ACFDBA34BE720B15112ABFFAF12807DDF84EB655C8CEBEB13D3375D3ED4999F56F576F00D89D3434ACE8897F36D8E78A8B019FBAE22952BCE6DBB3EBE755F492769D3B2672506E981A4DC72C94C8E137C261EC34C7EF19DC551BD7D8B8F540B2188E6F1A1EA4094BF7340D67CFABFA810C6F8D2203E3765C068C7C31247C70B3AF3590B8DEBCD86B2C9DDD5A6200C92A37267037AB31118F518432D4D071955E680A3ED49234312B5DF396F9802DA1A1B8349C95F969BC9AF610229E9D15DF28E2C431F9B5DF30EC743F0A563097F1490D0DADFB8028582151B39A1F488DEBE9A490976B3A02FA1BB3D86202597E36368EA10DFF6A0370B4950B675869C61E947E514B0173CA050CC90D4E52AED9E47B77A19B7D88A661FE8FA8985E5FC1548CD542F8ECDBD858E804E5BB95239C8AC750F09EC6800F97706A841861BBC05A5816D2E47D2F52E0DA4BBBC479FCA1C64085C1B7EEDFB0D0196097C3A696454B6DAAE1192FC434BEEA8792C28C9B654772994C3369B8E7FB100A8296F68E9B0FF0B1F12C688D21F3633C09F04742AD4D6BDDEF65E8FCD7331D27B1F883A5410C5AC633F0643EFBEB025C19035AA7C3249DA81F7543731FCF47772FD04819CE601F10E9C27A9C1DE7E3968E04C70E7D5F4B7122A91A6588EBC9AFF49A74DBFAEE4CFB6B0658CFD8E0B9221F44CB5D4B21FD7E9B6C62F04977EEF856C2F4C4DD606C276C718770417DAA7D4B4F9AD6478EFDFF177327899D30D53EFE7BA0392191E9032174FEB694A424562A943E29C0754D33835C38617C53DE6E0F8D6026187E085BFC06EA236FE5C4502E4EC133027092EA06CA5DA63A64F244BB2617DEE3CAF1C1FDCB61E6E409F32305A38F5FAAC0A20352ED822C86D09D7F39110D662B937768DDA2A90B5DE39ABDA8DD10AE0978777F495B9824AEA21C0322996EED5090CF67A66263D48B1AF3D804E3BE0D963712670081E3AE5306153B93074581BEEED04A1FA360F75E850288579A9E372D81E2DDE8B7962F40FC97D9DC1D7E4A267ED4D2AD9B7E3231EFC28060DB90CBDF0BBEBE46827BCEE9905724D468E64E4478152153C408AC4BEA434AB5934C6A07267D47B9BA88C7BCA485B18AAC8E85E6C979ECDE1976AA67DE578406EAB3B5E721A68ABFBCB2CFAED660E719C559ED45137047295BD5FC0792F7EE4D0F51A45440B0C47BA7C1F3F4B6945B431E761343F826715C7198F4CA514C1537AB48D61972EA531D9D65B6A7F4DDF0F83A6D01751F8D13193907C82E7F2A61A3F2D46EE665B2942D39FA93A46166EC2F327E8F335C1AB0A7127DB2F62580816F022ECD48AD37D34020B44DD74B3E0F89039304FCBAD59A51E8015916DB56E6E970FA7226483AFFF9308A47D2969381C363ED6579C028BC89257AA19860DC14B7452F8C2A0C95EA37D052F00FECAEAF210159D4F54C5505540B38E9D6C2A3E99C1BC77B5CCB2C27317F5083DF9A010AA73760017F909B9A1C124C5017F55BCA30A787C629194D3B1F8DA7516BBBA7BD3A8ACEA6E121E78C57603315FF4251294DD356876E457B65B921F21AD58CEA6313C4AB3DFD4E50EBC6FAC7C5D01B4603DC4FEFB5389DB92A91A326B8DAD2A00BEAD397BC44594C7E749F9A13CB34DF805097FC4B3E494CE3E98C3E88EB2980776D6229F86B5579BA56CA62918705F086A705FCE6F50AB5823C26B5881654A174EB64D6816AD9FEBACBD3D5CBD0311B51FDAB1C432FB55C69A8BFF0A28AC419268BAE812FB216A16E3C1070452652EFE101A980FEC0600C767543171D8BB5A0C57BE1A022293A8F9EEE5CCA45D9F6B607B04FB72F408DB1771F8452737CC552A6EDB3244E1E1A210511A0F6326A7E3B91C1E1676C523AE78957B83FBCA3A5C99DD3B2ADD7F1BEA562D98787A32798BCE04851B112052173653B25202825DB5E45DBCAE61B9D2127EA14F47F83328E65A30CD73252597F1C802B9C246AA1B949364F69AAECE4FE399AA02DD97925724DD049C73B977DE1B0858E4C8C702D8BBDF9DB2364E856B9C6114B25C9FAD3582F0CCAD92C6D650CBC032DF3FB4C210A105128F53BB1C92F3E248BEF5BA2AA26DE907A5FC47F6B7303DEE805295179C24CBBBCDBCDF107DC626E0130113D15637F529C7302CB9CA679C105F45CACF8C7C289E49EC2ECCED1A26D3495105A91EB7A3241625724B5A82339EA2682A15978EC75F4800FD03331EABD4B561D2BD957DBB37A42C6CF44AC2AE033FB61CF9FF207181B51276B0A65AA394A8AF53B8D6814F94BB1E67A37C0EAABAA3D57A010F8F0A2AE9AC90946EB3FD9AA1B5DBD45B1653F0DF1412A9383147CB8C90497ABE52E5AC65161B38E52FED20E15D79E15811F60055E3B3C6EE7BBF80542346146B79EDE28B80310940A426EC3E5D57AB0E5EF25EE9975469112FD56126D02BC047ED81AC1DBB08ADF206FE9792CC575ED9EDF9102B0BEBBD63391C9DCAE2E522AB20E13CFED55ABE06C5B9865FD3BBBD63173D26A3CBE48DA16399525B2C028BEBEC88745A56A30B5795CFD69AD5B3E1385C87847C53BE7D8B09FFB21D57ECEBA5D1865B34ADA3523D6D1C39CE3BEDA8D81208EB4AF4DC137FBE78A330C31F4F89E04F47F424490AA7A6180DB6B1B6BD8224803CA28B7E331FA3978073E20FF1F5D8F826595EFE1C5C40783491AA0181EA41709C47B30553E57571A132629E04C9AD4EE698CD45B9DD9A42747CC3E80800B3F01539FE7133E600266B53E52B82E9048251ED5A047F71FFB72B04F7AB29773DFBEC3695222B5CE8B89F901267508089C485E6DD714FCCB82EA2A9305A6698C940C9D2D1A2E53A0919366E013D14706DBFFFC4C0C1AB0BAA9F7983686F7709CDE9728BF899B9DCD4F0986354090F04BA5F1EB45975C0648D048952C71B471D396275A99AD4521E77C25669EC73C4C031E9737729416D5C2F8D13D9F7886D15921F67DCB652A3428B9B816D95F9F3FCAD65B02795D63CFADC663BAF979B8305B7AD3D655626ED8E81678B817910768297698E94234E6A9AC2B7851E55B2EE917D9492AED13D4580EC24D073861E003E38919108245C53B9AE3AD31F71A6F027038714F1FA1F4E4FC70492FED2EBE3B1C49FCE3297C8C86CAF5CDDDEE506467E6B36D2433A62CF2EBD88E5571F4114EE655AF7096CDD885A0CB70E5599FF852B3A3FACCC85C2CF27C30A97544562C8674C0C8DD5D44060913DD9465F8899A0178C29F7B1B07046FBB338BE9CEEB8B97D14D97AF6EC211CE04815AF04F9964807F2F0665A96E4A628DBB076ACCF8C32BEE3A353FDFC79929C86E03A8ECBFFCED7C3E5BE4107A1933EB542C782177BDB04F2985752B054ACE9BE1114D9F996475383BABDAAED90E9B24839C97BDFAB5658B9E9C00A772298CB4BADB723BC2CB73FBC4518B0D9799CCD26DE8E0A70086128A76FD7E3DA06B56E35FC5C3B2399A16C49808D2881110FC4ADFCCBFCE3331F780C45B8D5A7F94524FCB6C214EB283033C016F2DB9CC3C15C1D067AE17D9458F032D542646284E707273742115CC610AC131BE5AA21D80C171A2ABFF27BF484FE2F9AC31AB188EF03509A8319B46D62C5E4C4048B3451B4B6D9BC09AE89B94024FD42213BCAECDBAD42AC4B07A5BD0677843685451CFD582797F68CB63746C5D85B90117D35C7B3C9FEEBBBB678B9884A9774CFE8D8E538815DF6700E701118CF6107B7D68584C5422367CDFE8FA97FA95DA24C8AE2905F5E1FBC5DB62F88D7BB266260B859A086EF802E9A0846EEC0A52363E7D5A6565A75A765300120FABD216B0EF758FCE01E438224FFBE7CBA76D57F327A3E0D45122270A81BD2C6AA3BE530F9C4E374F134D29444EFB076EF53F11F3DAB33EFB0F0F05C03DF46AA017278C713DA694E2A47F996EAC915649A84FCCFD07F2EBEBD947AA731A46B4F1BBE869007A5311E361EFA1838A53DF473A916B90E9E2642156ACC2034ACE2D17DADC66D66758315AA90CAB8B69A749EA408EE9F544F5811CCECC371B2B8AA28430D516B80C407962829FF24F0CE6A45F8462B41FF880D492AF8A2EEEA5BDDEC8DBBA86875563149E0B0451D067022C30D6FCBF14D4B28E33D4A749B31DE6E5D003F118954B3EA9986EE2522E52D2EB8524F3C8719296C43469A1940ED2D8BA99D740A80871615E6D50F302B4A04339A9EC36D67EB645C82DB4BDABB37EA4B3B1204C516A96EC4FA5928ADD9E81C4CFE1675A7A6ED03D1EF774FF55EEEC54865E172BE2B89538E87CD9EDA4BCCD26717E7CE0AAF285438E6F44F8A4C63DAAE241610E03BBB711531E15204648A4DBE902B904D84044B023D84F61933143F2EF1EA1CFF3AA84BC5E46E0FF8CB1A5F7354FE6FC45011C3209CEF305245710953D32E78A87DE9773014E2A5A9795DEC18081068CA6C79B2A661E401F1F22C4FF394984F5C8044B6775D3763F58422819A9D467B72D0D54A431D73D678F431D7B62FE854BEB39802AE7170769D76F24598D88691210EDCAA0205AB7919905A48FD84292B9CFB34B8457A4CD4A28192BC8B8C282D9F860D218CCE7FEFE0F0FC9E92DF31871CCED383595144F8B2C62BDF3D9AEBAC083857EDBED50DD6AA0CBC4DAB89B938D13C024F7201866B4C4F5486ACECFD01D6550BCA9B9A9567D9B454F18D9F6A07A11D8AC8D036DCB7DB55617124D7307E4DCDC62F166C8D775AC156380CB2D693A39C0C7EB875E3B7B7A4E7D771C586B56D6E77D7317891364FEF9BCB2A0A1F7E2D98DAC242D90C878A916ADC1B13C1232A7A36296B5DE94FDDA968048705CDDC05FC968B7374C6FA0DB3F18CF2027EDBE8A1E19723BF99A1D4D43E1300DC47DA86F2ADB58F9409C9407C8D68D2AADCA037A920BFC4444A5B4CE463B43AB13F0CC0E25FE49CD370CFD735EF496B45332E2B5529853E7608BC68EE9427C2189B92DC65CFEC027A313AD65A43082F466E6D5F6D407FBD7E62245D8965D2726608E0B322CE73594CB59B61FFB5FD3C1C2724C032F3F15718278AADE681BE0AF6463C9B9DF685AA8A4594A4EA78929B074057FE4A8191E1F9F41AAABC716116B3B9AEC35FE690DBDF8081AB351369EB03E1527CF40CAC7CB0000B9FA833775DF441AF2E936C2456393A40FB970D4EF81CB014F7118D81807C6F5A6CBF3CEBA29A67D22312D2C0B54A59CB4C21AEA9F4E88DFFC28EAF5F48433D0BD23822897160E86D5C7DBD0232E4199714DC86E7E6CB37076F921093E5CDC84C3FB761FD62DEC946C71E74D654F66AD998FBF5D9F0854677E9E1809FCF5C58B9A41BCB71B56C11207F9C0898FB3DE8FF47458BE013CCD8C5D192F16AF71DBC58223B29BE26AB6D3D1E497A77D0A3202E6AB99725F5AC3CBFC9512F5CA832292430C42C625A71F6A23AEDDC045027C0D0BFCB1F14801A0F6066B0305F66ADBEB6022BAA28D03D83EC2C023A224E9888F51B0A02239CAB2B60AF5B2C4C4255F7C2191F0E9DCADC8E0D2143B52CDE09D96B741524690CF42C8E3044463C70D2CFB3D6332964FD790163B998CF9A4F6ED11BD1ADA2705CB7091AF9B359B75D18FDDBC9E09170ADAE7CFD9C4150CC70033862CD8D5A6F97A142B13BA37B633FCA6A534CB91852DA22D1C4A2964512869364BF8B431126AFEC51E3619718B06FCB308320AF76A3D90479F050313E13F4D48BD1F6BBFB6DE844DA5AD42E7738FCA9EC4AD8D9DE63595EB63E8D224392722C75B1F42A023D04FA75F59CB30FFA88F2D81ED0A59E02E1C153549CB58771DA6BAEEE5729D46C5A9F2CDEF3EC6507D55E8AFE4D3478928808B3E01AAFEF04DF3C4F6112A3B4902D2A127BE8254C423045138333891789D9218383C017E8C1C88D7B7B4C774A267DE11B4C7B5400CB42D753629F7A1B74B7C33BDAEE5425265B0A49F60F8432AC038CB5A7539CC907CB5080695934A69ECC20C41FDA2089B6EC73037221D666ACCCE38C1B7C58CDC267F5AF1CB0E56D65DD4B019FAD5255B2E50FDF7FADB37C1D63E951B20A430530C95F48268707F57E632ECFB2646D1FDBD13BB4FD32B4192593829F42954D52DF775EE931AFBB4FF8D5D18A3EE0ADC0EBCDE0473A4BF25E23134F6E3EF9A4EF7CB25E8F2262910265E94572943B247E44AFE113236E71A7D15C94A0ED7D5E5291B6E48CCF91779AEDCAF8325D23C0057E3C4B81E5213F21433207CEC6DB2E189CBA80A08400E23DA03858D1D49EB677873F5EFC29F67DDA75E37BB273740A9B20E48F41259B31ABF3224A7E814965ABCBB0A71F81E64D2DA471C1FD3329999E6E104D48B549395D7911E25C388042846DF8DF47DD3FAC0927A5D51D2E6375532D34CCE0492B981AFFAC5AA0312953B228229A902FCBE6CCE72B8C77DC518E7A5070AEDEA115899CB12B4D2AA28390D5301926EDC7A53AD1E99BDC717A6704DCCE8464757259D55A22287CBE1045ED3996D0855BDFF451173280BCA6F27E6074BDEC82957AF9D99EF2228496A69121ED333E16EDD5055816E5817FB48E2ED20A612E361B39019E7DF343E23D5A885D14F01BA6F2C9AB4851C45025A6A2FC74CF7A936FED465051BC2E4FF92461E28A1A06E0071F0702623743AC6213D2B95DA8B9DE25F16C37BD2ADA692765EC8F061208ED985DDD2F05258C084DBF87D607BE888625D592684CABC812F5548247E8CC519C75ECB347F8718A71C02D5FF22C888270CF7A52580398D9F2A1257305ADCCC521C6C3EFCF3EBDBE5F164C6A0D5538EC79706D9ED12CA9C1B9594C1154BD61A9EFAE859C0628C07D9C9AE7D6613697404B5E941EDB16A330D43BD0713BD691C7E2BC72E4FFECE6AB72A70E8AB42CC7EE3156C27FD6BAB963381A36527BB756C1716B5BEEBD1D8501C0D03C1A7100AC44558D9FFA02130E6C8F913461E9AFCB620CD12AFB466C8285B81CD81E24459B7240D68DED92093F8E145A541D59B436419F679BB36470610A25BC9629D17493E688D53A4ABBE43AE51AC9512FFDF57736BF2DB25304024771F412B0BFB881DDDC38DA46BDD15D6E6451524C21D0DEF763AE08B46DF5AFB05395F12FCFA69220112A80AA8A704E3587C721D882E6F64AEB2E1612FCA54310F51E8040D957653D112A5DE9BA13961F77C8D76FA619E6A9AABC562A959F39841F13122F0889B443522AEEA04A705362168EE412212A5BBB1E8F552B581D2B14EF6D4571DB6231CCCEAC4FE115436899184EA13EBBCBEDA909CADF42BD23453D9A5DB55A697AE8C848859AD3EFA2A49C627286432CD34475261AD80F6CAAF9A106E6ACB7E042C9F27E53DB6926D28FEF1187E11C5EE94D214ECEAD5438E0016D52979E338F22B9CE2E0CFA4B16FFEAEEEF724FDF9634A5051FF38B6620F06F8FE2F67F23483365A0ECD82F6BDE81DFBB770EE53E5E0E0C7986ACDD36F8E902D09565D26659FFDAB8F70CB70FB1CE3A30737A5DE69085F8999AB074E17E25447B02C7E0223B319E58F896F8DB9CC5AA231EF137C2558B6D6DD49396D1F5820D85B5543F5F137294BEE1E8AB364904D49554F670169F674E02ACDF36633C98B2B307B4C60D1D138570EBCC29465F0ACB59C2DD3EEFC7B1E37E2D0F315C76FB128C344F17770F1AB2D1C9D73E9A2E4D49943B701FC9163CE3B03E85D03E8408030BA7D1E16EE11B8550AFE6409D31EE89E804BE399B02C243233BAB98293700DE27BA9F2DF66E734F449B4D99CB01353C5AB44D348C705809F4EDF15C578A666DDCE2632706CFC2E2343F24821539C34A19F530AA89E2B26CF45FD96557F913BBD8F5B7DFFC145D5606E5180AE1534EDAC4AE2D4F3D0C08E571E747DE72EC3BB1179CD3AFB75A8E18D5320727854507278273BB49D5E87D0E578CD01BEEE5E9A598F2A09BA623890E0B3E2E86E6D9B1729D685B3AF5AC8E4ABB6B1841CD1CE6F4D9A4909039349BA7E23AB37257ED65520DED2B1DC351AD2B05AA238C0BC5DD80B1F9144712E0DB3CC312CD7972803DB2052556B26AB3DE3C36BCB12FB071A9ACC476DE381AFCC2971C6FAC7B363841CCD8F05C1364C9527AA3BD0E9354E9A648527FDE78267AFC1ECBAD5AED65065148B5DD6B6AE1AF489317DBFBE9AAC603D902B725F9AD5A7FCF524C087E7BEA2B2F9A3A1D2C8D4EA79362DA4AB245528180DDD93E4427CF7ECEFB771EAA5A3208BD348694F069E6BED242FC086828726A98FB2C0CDF3A49C0FEFDEBE83B4EBC077D48DFF40441BC090F7AC65E5121631A728336D5189D07A710CA2A29B855B897B4C36D01E79B32F37738F013EBCEBE67B8C9532015B744500BE5E23126738CD28CE34BF22E881FC50DC905CDC88B19985B5CD62946F09681648B08C5D2E7C29F6E31460EED6F4DC9F7EA646B84DAEED064F46F624AB0C7EA09CEA73708EB4575514D25487310468D419402E87DF65E6598F467136A8FBA8F0C89EF2C09A90160A9174C354EDC15C2297E91FD7175E5916B90067C87EC13E62EB6CCF1CCAAC97DD05388589E9EEBD67AAB3474FA7F682A757E862062D0421AEC7638B2D17F2D54B2138067162EFDC9A96BCE23AF5E32930886345352E869D3E2CC6FEB1847C6464091C5258B067F1AD098CF0B4664CD85EB9F400E201A39CE05465AD42307B9C27825EF366C9A46955D70D40410E5173AE9056B43FC0AAB422C0FE136951436AC6F46A73C22BBD82DC4D8C7FC51E1100889DC380A585F3B4A33422621404B5273EC6742DE1F0610C91168AA343101BCEE4C1D0DFA784091E60E7E50047A6712D8503B58C9B0F529C3C1AFBD35688647403538DDEFD267761843FC05856AE1F3CED51972B775D8DD5E531DC9EED23BE0CACBE68D6E5E90E003A4BBC51CD71650541534C80E93EEBAEA3AE578D4AE9A3E1EA051D80A25C9A53D48B7FDE8CC7270E87A7867643B394006D2EBC09295FE1E244E7824DB260087357E9655232305F2A21D5FD91519DD85BE3AF3628C7A077069961C875564F72446288F38EDA51E1F82D8DEDCC5A9B3D085CE540CA2D28F0743F199B10B7BE6998B1B5879719045C1183FEF95421116C0A501C802860038507026531644238974AFFFF22EAA192E41EEEF5E9001106A5F74B659515AD030B493D32810C5B805A9D829B7D731BAE9445CCD0E3762AD4FCA84B071B1863920112613FC17F8A7F8370B3882405DF2B4BA067902BA2CEDA53AA53C084F93ACDDAB024BC5F614938CD4D9C380175B08355EFC86B385945942418AE15ADB79B96429D3BD35778B0299349CEA18C1DB65BBBF427D9BC5B3C8E41C01824ACDCDBC83C588F7BFBDABDB684E6B6EB850911D9597D17F4EA8D8E50C41A96F2C0857605193158F574D900E9B1139A6E67B62495B87039840FED4829EEB5D25A255DC8CB9BFF3EA7F38FC22D90C66E87661713E61D4D199D1E356C117F8F6481F9DDA59BA86CE331816A68102AEF19502E2F1E458D099F35BA66FBB330607D741A6877CCD66434C096F7E439E1FFB8A799C1B013BC30F9A16D4616A224E8FFD36482BA5A826C3756A3A4C5D54618E4EA8280F925983EE970851DAD6DBAC8F7557BD3427A1F56A2543E18ADCB5A1B3ACD34687881B174F50C4F81D0C1171993BB629C32871D1DE5B42411F985E5E48C04A68213FA0B2757BD3876211A27A83234B88600BFC69F57A25CB4B4FDA33A42BA89F45A562CC0CA60681B15F87EDB622CD92494A803A57F7AB272FEC8171001F545B5196C7EE5158515A94444201359C7FE617243CE95E307F4AD87012BE89AFA23E4248A32FD60078236F824EC735A0CA7269D0CDE142E71C9754998D6D2041F5F4A5B7A261D9E9C857A04818315A53933D9AE07A648B77C2C1866CA7294539CA6528A71BD1D9AB951C60D8BA884ED1A7AADFACE8E389E20B7DEE72544657CFE55127EEE5440643380C6DC97CBD5308E0FB5A6B2EE10A0444CD70F468BC0C7F13798F0277C10DF16A8990161DDF7AAC38C388476CDF9D8D1BE72A28BE1BF9354D01B496A17CA4818F3889EEFA083D7E4DDF894393E0D67AC75BD34857647A238D91AB0FE1381315182C8EC838FD85F2000525656181A36ECFA7FDD385901866D223FA10D68B3776D3A4F333D58DBE824E31B5541B2B2483868C473AE9E6ABCFA2B7EEEAEF988BBD06FC63511E030370A88CE9010DB27F766882DBE69B5C3C7C094E2D8064C2C95FBBB2CC3F120800F2C5D772C852D3BFAFA7DDB394943FC92D31E23D3847E6933F265951FBEEA1182926C0F99B27052925075ECC50F01B6654D66D1C6982A27064670BB2BF8855FC0CBA78F5B6149C9EA583253F773EEE03EE28FF738F21F048DC3076D5847309237EEBDFE03D4561E4972A033048C39A8D4FEA0745C1E5E1D36E92D0EA0ADA3E025B6909C86065F5CA944D650533A8518AD02345967C53E2A14129944E101DF2A37F03E986DFD62D45FACD4D71D018460CC2D038E7ACA4F1D8BBE57BB92E1053D3ABDE11E41A440405FD1B99A983B1281ED56A0A7596BA6C6DF2C5CA31FEEFDFB72F1DAB3ED65C6DF169EB50E031492755F9ED2B2E984E527B2BA3C95C5D240A2A1A9020366D2A25CBD50DDD1D673B4B5C8861D47221A0DBB56F8C6E4329869C1526B21A795A27F068575F2E5A6970D121F1007D0EDAAC4953E56FE7C252E46AF5278CA77E3F4D044A6A8E64AF07E72784CE500F2C742086655045952D215632EE3526E63FCCDB3240A686C49CD950F83F78A12323E31FD497CEA2EB7563C9EB5BB4CF0C97062E415BAC50FC883E00CA75F9A0F4513A1591CAE9BC41201A0BD1575FD4E16818E56BF0D414626E331E4B2FBB81DA7B41C32ED6CD8FC900CB517C59076D8F41360A5BC623B05A9607B21E5319603FD89E64BEDC8010C16DF6CE775595848956824BC0A0A3E239C9A388FB875B69AA746F5E9C5694B035F6A5152E10B77476F5748DB8B838774AE25060B99B57C1D1CAF0BA7B0DB892FC3359601CD172FC656F4E9D92651E750A5364B1C40E2BE00263B0BC50EE8D5777205501C8A196B73E7CF9E763ABE03383313C89796B8D0504754F71FD282171EEC1AADAD79235C95244F3BA8299421BDFCEF0540925EC7F13ACE7BB6DC38345CEE29C6FF8CB5561DA38BECAD8D8257120EE4EB7C2332B92C22E3C997AB32DB789854985AD987163A9860C35237EEFEEB7509108FACA9342623D1FAF687D8F3922E7523B08600D3B2EE9CFA2B5612C74147C07149EBECF49837435ECF2D5C8F5711A5C8637E76160AC282931324C05689032AC1EA5B3901976AEB165F94B9E366D0FB7D48A31EA13F89E7EF3504270C47DD7FD2F004E69E782D3F0C63E429B2CF198AC5201A1B760D7018C5F76F1AD34C11ED1E18BDCBDBE7780F3ED8C620D53ED330B0946B66F5D46CC381B8B71A35CFE8D7D0FB6B6CE894484CE7427D2C7B1F7A908097209DC37ED69C2483846118AC8EFFAEFCD937A6B82282BC9E976DE6DBAFBA3BBA0F4DDBEA4E1A9090AE90922909674A38E7DE620B036C79AC06A865A860F36380D37DB802C042121BDDCEACF58EF3707566084AF6DC8CA2999A12AE386E891B8D6143F9662061E6B9B0E54890FF03989EAB0846EE6D483494D3FC13FEFCB9CDAE9D273D05F5007765453F6E4366A7BDA9A6700E00FE25304AAA82A1A1BDF10B8448E4BFDED076088E301C6895A89536FF8938F3C7A43C481444F1AF4A34A5B74B8D2507B34989511ED641E30BD4A2FAD38FC65DEA223523A92D386AC592047333BF2A956C94DF0592E08A4DFF7BCF238AEAE045A17CCD24838FA14851202C95CCB79585D865B862EAF0C6E156501755DBCE0B4C82CC6AC46ED2D9549BDC116E3C8B7C6EC62A67CACDEA6621951E2B40DD45DAF85C90BAD0F7F6F17A1148639C42150AB999A8822131106867A96B62AE25D9503BB02AE1D5B2E6211FE9601B1FAE84D477D41EE69DFDA748DEC02FF40E5433A9980F13662B1C470269A735BB218AF4642C1CC968C86BC7D3FE4D70F2CEBC5BF382D5B1E8FC4DD05217DF7D4FD8FFD2AAFBF025D99A55C38F01840F1EFBF7EDE154683EF2528D86CCFC7B409AF3E55C5D804E93EA040E86C08803D42BF6F0EBF2FE1FF37C9461D4D498A6B9020250FEBEE8359098BFC189C825356993DB4D4DB8883AEB466EB21DADE339794374AC4C1E49728DC68FD525308B022BCD17EB3AD62B379F00C9BDCED89BAFB458245692DE755331E7F400ADB65A4DBEA816F394E08BA8A2B400C85C1BA66F54A94034E5CFEDFFE948D368EFC6801D551755B1E6707FBEB1227F9A677BC28EEFF7F889AB116AD93B080D36242B5799B6CFAC2ABE4849DF1EAACAC2D3163992D569B15A82EA344C959FC78167EF30990BDB865C57BF8285117CDB83FCEAE50DEAFBB3A05BC7D2D2B438B75A58EE22800A8B27E5E4497834B11C3924367E3B8E06F3AC7C5A15A17ED2A17C07814FF8AD3A4F29D567BD12A960337EB4618D7E8FE9F15B2E474C97D79D62A0D9206016D306B794A000DD545E3662D956FC2FE263805B15057DFC0AB10985B1857E2A1FBE29F5043B2C50FA509CD707582266552CFD3F5578769DB419A4AC0CCC42F04745B5D976C0DE8472E3FB92EC35483B8F1B8CE367D74677AA56AF8775064331085ED10F078B8F009A31504339B98431212BEEA353CF0523B62003CB8FD03E60A1A5D2C042B97128287C4552CFA766851EF79263775F3F1B12A243B604A49AD74DD6F7E8F24F51B5917CF29BE4041EA4B04B78578B775E06BAD1A47D540B3CF42A0BC82664DF4CD4F5AE387DDD13263058291032F8D547CFEB00B869ABEF6CE5F5FF09BDC7F986D6DCA908D49E5FB7C60F6769589CC8A3BAB7EF6DA73F43A3113C7639BF48E41514D3842291CA3F7ABBF70D6E28AC50F7211D779017BD4B9235D6FCEACDF0E1E73F3454A852F96702FAEB3F51AE82299295A2778A254B3EB532D6087A5B01E2528D3719860444A76A4936229412553203C11D1395E23E93ED127719C479B270407835E13351B387B00771B982B05F1B68FBC15B1F1D65E67B7750B1D057B2F94DF4D3D8610E3E228744FE83BE81A3F6A0F915E040ADDFA9BDFDF541BF578B94549E5098BC44E5979A9F74396830F9E06321686BA2A601718E515ACD56BFA87BBE62944895A18F01D0D76365153C85B78840661ADADAAB9AA008E9401FEFF2799ADAC7BF20E5FA5C11D34B2DA6E89B88A46F7E996EAF27C14E3D42BA17CF2774F6BFB2A53B17C79CA8C58346F2BFE7EBBE75BBC5FE2F14231A8E66B689594ADC222B31D353149676054D90B91E32551F2F086CA4490749A133F1F3A805EE0A7EE93B5EACBAECA99464F0218A54F293CFA60A8399E2F4DBBF9A06BC34555EB535CE15B837524AEEA098DCC5C6DD923679A12EBB9E068473A1554122B0CDC37A14DDF31DF6CA9B24B2156C8C55505C9C32484DC878BFA34B68756140C83337EA818A6AF3EEB7C639F12C29F239B9F924555B6A7C1210DEF1667685F340DCB4F9703D509C557AD8A68B6B5B7BB5456776FB156B995ED706550D16206725DFB50D44C25C68EA13479BEAE2EEA5519F7D48BD0FF73214D35B803930F57F087A06155E4DD3475737CA0C132E16882199C667DE99C6A64B6D01427610C70BC246C08FD72314DB032597C890A54F1D3EC9A49ECE4FD959D4DBCB543B82762F177CE4AB5CDB3235DF9AC8F63935E720D4F1867A26F891B13D5AE71A824AA02A8BA6833A4F2C665B024637DB9EED305BEA1ADB089927B5C4D21A70DEEC17142477A02FD1DA31FC39AD51217770A68591C25FCEF0347B06A6FB002E5A82E3992ADA8A4B94829F5E1A4915CAF7B7372F734B159BC1DF85E1A6557617FF7809A4F200B379472FDD6798E3F0D0AD6A5DEE80AE4A678E3DEAB44981445742DD857DB5795A5C81A0D0E62AE92EDE89413B946C2C4CF14B1E2110FF5B2159B96F1516C9C8D4778D1F0BF307B8D27A4AAC3AD415143BC20D20FD58A44D2A7EBF609C99919B3B50629D9477465A11A50C58C806986E66C09392940461197B5BF93662526EAA48A821C83A6A8E2E01838B5A057636193196C9CD7EEC203A453B235524A1E4D3787B744FC341941EFD74C1CF1CDC671CADC97F6012B2F046990BF920EA30DFDDF3D6E7A66617CF370E669998164B61CA9C290F6BDDB761814A981C7A9B3D35341FB318CC6BF0B9689CA7075B33CC2DB02BA0DD932BFF9887458ACDE5B4B39E6596113731D40C2F2545B3C7F4BFA7888EC58FEECE43975B667734BDDF833B7279C6E939596E12696860DCCE0782A0479B224CC92376A320E4E2ABF4914A3C02D18824B036F18DC4AF5731AF62D6AD1DEF36A9FFF4CED55450ABD5DB1E4754B79C6E8EE17B55ADA0BC71CB2DCA58C7E3466B95EABB4D218495025F58EDBE89F93C3838A7B8EDF9E6130CBF2D6EC7670F002E894D6595ED52FD791277FA79C781CB36BD8F44EBF89C27C687C0B4E7ED33A76961BA04B6535A02748CDCDDAF8B08698FA0A23D1164277E0EB295244E23BA36FBD1437ED333A55B8AC14A8E24358CBCE75898E1B4EAB8419C22EB5D5C003D930526B5FB0A364A4293FAF48D877C051997BAE8DAB853D37F2ACBDE8E5BE582986C81799B689F54662BF7631ADFDA14EBDC4527C2E618ED52793677ED2C30E24FD4769C82A4754AC716CF55BD88F42BDC445CB8FAB03BC2B56F322027CEE9E28D02719FADC82EF4E0FE40CF760AA55D54A1484C542A1604E334D12FE97D78631A3AA21CA102B66EC31049E0EA4B06CFCF9965008890CD7B6BE9315C99205521A3E384FB0A815219D0DA400377D002C3D2B354D3C0E96D1F25AA89FD8DB87922F2BC2551C83609D98808A1FD7E2497F7256F670467194DB802DCC57F993E701FC2CD8B4838A96E2156984FC74399DFC4ADC0A83835325D9FE65904BEABB81940A4DD89BEC4FC81C20BE9AC8369FB993F2EBE821C6906A5E9964F5A783793E41A0F91E566DA4E80E6AEFF3357B76444A1321BB3861343CC1571172E2D703647B97C33191518CF0D4B8585FFAA4670C9B2922B44707AFED7455352F35663B838D846ED3327CBF5149F6B47026CD810D682F64B71FE90FB015B0228EE0B4C4A609254F84E26CB3AFA7105894B84BA35FE1EEFF20C168E336F35CEEA00406CD71016B084C7D295E187C8B76999A513B5C0FB77B727C45808A64685F7CCD135B0B86EED8741D1922900205CBA3E23DEFD9F98F468CEF5F71254745EB7F5D02412E74F90213EFD38A2EE4C42132E637AA0D202038ADDA79E0B77E3E0E7C65A4D51A7F334D714B0DEE9721F23EB552D78700C16AE4BBD1EF88B4E3914D01F454BA344D872455000B4C534C4D5F9B5D1E2744C32C848DFD0709544EBA578841069B5F2BF354096302357679879E96CD32D9ECB63E3AADF00221A7833F57677EA1949E16B5CB1F8EBB6A7C0FB480B2F15801448BD858744113FDD3414242C0AD8D5287E05ACB70FE34A5B83D19A53F655C5C301799AE3323B21FC293D121D0A5FCF2056A57083415BE01E9DFF0D553DB70F4DFD1776AB5FB02C6EDC79A868B4BB484BC5AB50A8CF98B1D3EF5ABF85E797A29C31545C809B076DE66ADC704665574C818211780487D99F70A800598B53544AC486736A748E7296E9CC5F13B5E9A8107F3B7422680793365698BE635C948880C34127D49E64A5CA1718137B6EB13BA9AF8512A1EF155C20DD012B91595BFDBF9C7ECEBA72596CB0F2C390ED266BBFB60B39F4B0AB20C28D16D89173430D949E10F73E61FCC55B7AAE49EEDCA2EE53AC9B96C2CC742C7C5843DCE5AF1192778B6E6CF4E12A5A33D09002CCDDB4F223DB5E680EDFF31704A5DCE00D8819BBBB04DE2BFCC6746E100C9345E19E71CCD3A36607AC82B3F3269072E067F7FB110D8595B7FC4FDC067AE87E7E971E00FE492F1E01371B0AAEC88DA8E952B27BB5F162B91BA23D1BAD3A9F3CAD308551976B2B457622AD4504C34B5E1079DE149FC52D9E2487D9AE9435C252A203F1E3F5B32F0C43A648A6FA6EF000A4FA03D866192DF5F4496B058BC23F79E1E617364AD41E4524780DA0827497E4BCA2A4A878C1B0AB89DC632F34128E9698C5EB62C3141B654283D5D75E1D1BF410C70F109181FB82DEB2639A5A38991C1260BEBF6E01AE4E314B09BE2AF627ABC0042C30F397E78D7D3645E656EB3CD8DDAA4472A6DF7EE68A56128257E8C392EFB942967A76404F662C82D6CBBAC4D443607BA89B187FB7390B7911D8A8B1723C9BE484F12B76AC64173478B21B3AFACB6091142BD81E32C80C25249A69AFC9CFF0BB4574F639BF35ECCA9A09549213E4309B450885756A57AA5DB400A496DA72BEA92432A6EF5CDE500500AA2A1BABE239051034933A7E082B95D362E9A6A8BEA2A1FA642AAF2B0540E94E3F8648E13BEDDBA77BF58E83971762083278418873FDEFFC96400B55AEDA8638194A36F41BBA799A92A52D3FA2F94158F6B07C2D65345F3241246ABA5AD872EDC0DB8F8782AAFDA218FEF129073755A87D99B48707E290020DA599FE3A49125EC3CFCCBB396EEFF9391C56B8ABB620805DAA9EFF7CF5F03EA4297AD375629927EECBBC8880B75BCAAE75CBEC5F7331576BE448D7875F6A2CD516D3D0A7E7CB6B17EA1AED416D2EEC03708BEE5525D6AFFDA5A460109D1142AB87BDFCB5850537E8A6D07D9F8818F578FC7700CE508D93A10CC90DA7A0FFC6F8DFB08257513C9C2BB43349FE9A33F95A482C40323689C7109DCA035F3E23E349CE2905C8DBA4EAABC787E2EA396DCB5DA4E8027D91C64CD70C7FD68E3371F6FD3298A0E14A6F5055EDCF75D911A817DBEE2FC417BDE71760C74D9E4FE1D9AD283D7AC217EE9929A5037ED7E9717D6E345ECC246CCE3EDFDACB3D0948A36CB41059420158A94F24FC048DF3CF80BF6F9806E4760C3DCC095EDB7F1A17941BD6B88D8AD23A92B122D1B86A2F5A674CAE107966303C6061AC3031321DDE1363EFEFC51A071C57C3852F4CFC232B177E886969D5CD2CD79A2A3EE7456FC3830BB79E528305AA73016F0D10B94A1D824E57CC6CBD8571952FA684DD2635FACC38E4F335D1EC824FF47CA457366DCB1FE5FACCC9ECB00225A5C4E447B410D4E02EF2BCEB933D7DCB92381777F50673368DEB74E88C4CB306C05E1A57056165D3C6BAD534BA1E0C58CC6B0B3DF87C4B3F4E679E98AD81907F3C32389A477181ABD2710593C36DA28C6441B0364130DB577FEEE58E6C5FD94264A620ADDFD030FF624341AA37D501E7B46216B4D90EC4A375FF8C46435AB26CE15E1C2856825DA4E62AD9E295B4FCEAE7920F44A4F3BAFC4D320DBE286138A071D0882CEAAB40847C03346404EAF19A4537D1E2DDD82D7645779C01FA2C8569CDA84F0181DA953C5E33746E3E8B0BFED3D42CBFE75F0DEBC33C6CB5CAED1A0495721EE9F4C692E8E0042A8B79B6A6B6A9C3390B30C5B12F493741547AA43D5B5D5B8A6F28D9CB31CB0F0F2C57FE57F80089F9439D79B6769C399FED6A0AFF98A5A4D1EB8901F9AD0F9223E071C999A3AC4D1E20C5E9A419DC2A3670312B8DF7D3DF9DB8CBA483D62C6DB2EC2F82B8B37DF482ABD89BF510AEB5295A8CE22A88AE7620BAEDAFB165E58884C6099BCEDC33522EEC1F316807A99EC7113662BDF334D1527A0AFE2173DCD4C92CF9FD3ECCB0C0CD71DDDAA5120B2845C56B6D699DEB113DBB026944811235945E72BA01931926635D498A6F15E9994208705BF6019BC3DD31B70CAF5B5F1FEF475FA61AB8D6316FB3C4880E10A745B4D4311AACA934A014091190AF86D8DE01B8D6EE0831711EEE6963E1D2F09DCFC0BFB88007B34AE8390D4E64C7E673AAD35600856E42CAC986F3D60E4CB83A08BF71946BF408F99A952DD49C900675C4484FD9FF8605182659F6B821E1AE77FD812FD465D2947812D3A1D757582549C6ADE50A2AEA6D5255A7DD86D4DDDA5FA41847CD97DD0AFFB943025ACA75014B90B90E32D62FBF60799E0C2CB18C7CEF696C92F6F291AE30F8CF261C8FF3DD000C9BC3BE180D0BDAD52DDD0F234DE7B95097D2965CC69CF8D23FE465E6C3402DBCF28BCB8C20D5B18C446F74A3F82C3629F9D85776902023E379E74B9F6EDBCB47CFA125CC8FCB6C6786BD4363988A4814AF60D59D1678ED93B062D213899B99A3A2497DF70F2B6A2D3DFEA3601DB477487F28BBB73BD2E2EB5D849DE2180D8A8C479CED874926D0DE4AF4CE11DADF94C0CFB686B630A2D57171A0887641C59F094713E6AA3A60394C4556AE58BC91E808A7A93F071538D38A74D0BAB55D8A0524817EE2066E6508B1C759F56A4B6CD257EF2C243CE2B5E0EC2715B9744E17206E54D4ABCB1CF81280DD22DEC4BFB649F2DAB66A7F3EF00E1A31157F815DF1404F6110E919C9813D785A5BE2556803C6DA2E7FDB791873F5EDF9E839A0670560ABE283D89DD8FDFE9D4AA5AE53D44141A9EB0353AE0860E526F871461B490853D9B8A2EB63D191257C3DA7D1FD878AA513C340F2993374BF65B2DCA33BB1F4A2A571B30CA75F76F6901E24137EEB467C3A89A69AF65F67A2ED8F136C831D59AEC27380B249F48C5E771873E9A6339D0FD6407F04E43863384324A03983FB25C55231B7ED8E097483D0784BEC37819620827C5D08E143BB468A5DB636E413E74DC49D27ABC1D9600747DFB8A645DC0BD92ECC75689735058CEBE7FF3105BBD3EB5D9795AD0A963F43EC60B2E559D08EFB990CC594F78CAA5ACA888828EAF46E80D4EB1143116EDFD5F32794AF7A2017A6C0BF66A65DD1880EE56E9593ED411880F8ABF7D435FF7265260D55E774B4C75C96805E40D5E13DCAC66C416C9732975D1EFA02C170634A7DF3E4DAC3F9D26B2118E1C33282B9C2737D3DF6103390137C358B0CC1EB572AD9CD4B5317DCA5456B07D8CB36CE37619B1F053CF813AA11D55967B551ACF49DEBE5498F6F62EEE724D088D9714DA656F378379E4C76693F271A36AB04D2408DFCDADCA1B6C69FD220A074D7F0589DBE1E84E4CC23D812A1A8E3973DA3F3219B6E812005E4F66FD2F73F238B6A1CEF3A7BEAA280A29814C927C0730AB0808CD15F65C9816C243C719CCFFC3434EE8DF597DAE714B47B73710C78A1965E3B393E513AA99643F9DB4E63EB6D7AB08F48CB64BCF4AFBCAE38625472997224A1BBC664F4122AC531CEC35628EE4BC157BC3858B0D8FF2BF6852F2F6521EA33F4DCBCB8BCFD20719D5248CFD858E1FDBEFC5070C8301C917672CA6F16FE55BBA1385FD019E29345852BBD1F82AD85BC7DBE5ADEBEE964C298F746105386D97FDA7B0CFF14D8316F72F256547114E2126DC75D97A35E122625AB965E37B906769C4381FB57B8629670DF8D6B4717719076381CBD5AEB893A5F04D469D2DF014665B98A0B3963A32D7C2D29D34A8843DF949F7DA473911A8955A9253B3957D3521FB72159378564CBC58A2AD9D2F50C71D582A50D3AD5B739F8002983780A0EE0C7CF28F762115AF83EACC5B654F5EC5AD2AED1ECA1C03ABBA79146AC21C7B7B668BA96883FC3A4BCD8AD43B6F771FF89F31502FAFEDFE2EE6139D943CFFA74AC19536FD4C723C2E0536AE1565C36011EF76824216F3C82CCEAB0C23BDD9BCE7EBD136ED9A39BB93246E9CE2B4D8F0DFDE3E42B36E6C98CAC5D1C5E7D35F1F694D4E4D73C3A355D1C558AD1639CF6A9C44877F82B34CA0BDD7B369244728C3BD3265D2E988375A91E836252FCABC4940182545B5178733C112274A5DA9ACF96E687ECD1D7888EE75682412676D00C5BA616ABB992E21D3BC88AA4F674977F77BB38EF08AB7C0650A3364AB598619F1895C03F14B02166D553069484AF41D1A5366AED5CF6053025745D1146231F708099CD1232DF77C57BE0DAEA271F2BA04D15BCD9388CD31FF7048D59F229F7FC8E362C989516D3A21366AF5B0884830096DEDB40A4FD46EA420047F6560A6CF6D0ED9364978A046DA01198194C774EA3CFE21E47A22C93D56E393F88EAF327DC5F1230693684B1DAD141354A54E00424E4590B0862D53D4CEEE267ABF9F14FD9D37540522CA3EE4D56ACE2B8F285D414F5FB76EF473E7ABAF5D23A503FAED1A9AC86136351DF11426ABA639D1918C0239B9441B7B2A0C1FA2BF818588FDA974C35D2D3D6B1C6A76FFA6CA6FEC2BC0A17C070C1C434F62A707EF21A5A9AE2EA4EFC7668788C7A5BA01B707A38C5914651619FE2028B0BDFC0D343019014527236415D02B83BFE10BF1896E18941FB060A0678B0CB39D47B3DB8DA89995D229B73B759B51B62E3431847D7D0F2D866B121E8814D1B9A9751E4E245DAC6357AC1135EF7AA7C1495E7F413E671A1EEB41D9CC020C7D1CC4AAB412218DD23A8F71DA398CFC1D08B3A6CC4FDEDDB21B1AAB25E54CB9B7B11BC48453DAFFC8C00ACBA1B44D673890CEF475C2FE244C9D4866AA10A77C775BA6F2C2076BDBD576D9864F5679FEC4BCA1EC4AD297E0724FCE07A535945AE4060BDCBEF1B15B967BF00F80505CB0FD4BD842FFDECB07899FCDDEF63B7FC451A5BF1ED6BD28A30156FEE36AE47D704882019A9BF2C8F8C77083E3F7AE84393C299569CD02D1596B76D26A857269DBDA0B2897D72571DC66B198E01E4B558344BF60AD264B1EFAF5A1248EA5B3246B0CEFDD14EF2CE576575A3C16A921387D795813DC04E02FC80978404968E12B862D0ECF0538AB77645D9106C3863D6419CB3B55006B03B1352D06399416D5F9629E66A9D3C60FA736F68DEC984B60FF7962111E05B919EC66726096264D64B03559FA504751216772254CE474EFAF3C44324EFA10BB6A5B4A52DBFD3BF3FA2AF9251C97F185AC6A14B4D155480A0FE219F683B940921CCC54967C1C0F9EA7271729354EEB72F57882DDB1C3640A699AE55A6DC15F2019351CB194C6083FF039E2D3741E137D7954805829E114ABA2FD008EE6EBD43AF6DD0D00A33A234B3E19175ECF68A53A6861EA2F73194F140C461EE6FF96DABFFAD93EF21E89E16867CD5BF37085B421FBE0E800BE3884B4D7770F54F0DAB4B5CE23C4408C1CCF1E87458FC9FAEE48FC0244C08948A19DC2BC7B2A5DB02D289BCB56B03FDE184B71C3D4637E6C04F31DACCE8CFDF295CBF25340AB1592E24B174E508AE66D96B230E1A9B416319AE9561A2902CE82D322671E6387C1F263F3240B11F5D4DF16EA77A28A45F7C549D0A1AEADBB37F099F96507DD67B7B43E2B42613916557919462E15E1B3572A6DE09F01518C7C384AF9F6740B9DB2CDE2D86E152989F4322180A3871603FCBAD26E86C00137E871FBA1EFF0F1D6C6D97A0F77939434DEFA81AF63272B115285A32C99E113A9E3B33325A01F7DB5DCED56628BFFD292A53E7157EBFF12D7C97F5F972AC37EED8889C27C98B245AB34B49F50B20D29F6CA6901EFA0ACBF3C077783D9377DCC80F8DC20B08DD36AECD4E8E288F406CD8445765F5B33FA61E17A1CE0EC321A6527273D5AE5D651471188DB88D51940EBFB79608C4C1592276E5BF69A82D85EE42C9230490268756041003129368F6960C1920D7EE1F4978BC9F01ADED7B79E24F9CDBD9C488DF464025655DB7909EC3BC16EF5BF3FB64D012AA4C576ADDA24D9915B2CCC741B491FDB7E1FBDADF7C0BA2AA8139BA5B890308420D1E793F5620F0C8C85E3494E1735E849024240E7CC933FC960693766B2462840851B12A6ADED5A1858A7AD30B1382926FB65346BE6505BA37E2EA0D83F17ADD64EFBB57AF8A3539606ABCA7624CEEEF46366305D9CA67353B5F5CD92FC657870A4AB95731D45E45C96050A0C8346DCEE881284B34BB051A3AC54C8AEAE1A57EBEB728FB2D5184410D09B899CBA22EE9209E48448C535A48E5CEBED3523D5A138FD48AEFD0B42563F9AEE40E1D3C7503AF3AEC70B0F457F4B3FC299E08A049174996F6E787294817686C6DEBF42D69BB8547ECF009576CAE664EED1E30A65487153BD93A3E77F2AAE465AEF5F9F0D9282D1EB6A974FCE203A92E4E1B5FB6FD099EEE185DBBF5ADEC147E2BCFC2277AAD7E7905BDF0A328585704A65290E4397D08C6CFCB1D8A95950D1FC4E03A3833886DED8EA6B9D7B2C340CF04A8C5AE008D750C40FE4490A879E04344433CBEBD399E881F4773A9BBAD42ABC4720152575E692C3E397143CCD3B6994C360EF92E037DEA19095B64D367E476CAA57BFF5D58574C45A789666DB4C645E94C735D218DEBBDFE60CFB16CC925FE5A226E0238828EECA1047BE1B403E97EF5E5CD4C637C43C33A2FB05C45436CC84D76781E5CEF8092CBC440C2703BE6AE651BC65C76E4B7327B4FD4A53DE341E3C7E03893C6BBCF57D7E197A6B926EECB68ECAB648BB879DDFE3DB31A01439778507D01B3B3EA279C49D0DA28083D2AE9D4C27648970372421098254F4B83EC11535F6D7EC489F2BD2908DCF9165DEF03678B0E4D8C80D3E65ED9E096525A24F0B00C6A8DD5661FA6B097653FA2EC41EC886B7C283B48CADC4D4E027640B3259B71298C02E0694D3660EB2C22719E1813F065D6B76C172C2706E29A19FAFB55D515230A369775CE4172FFF57F4DFA5D7E90DC34393FA6A6156913C86A6641BD5A0807C114792DD656E0720621669333A53124137EC4127B55EAD307EF28282BFB33A10D5BD326CE865C7BBFB60FB5CE5E5EE9DB430BE7A44BDFCF2AAE550CD7728B2806DB21B329608260DEC763481B2E75BA6E818EC237CC57B4885384941DA4B73A778588973D78C98CFE48BA0D84E9C2B7DE48D8DE1829250C1CE4854BF57D339727739DECF075DF3945A59845E112DAB123B84FC19FFBE2E2116AF14D096F1B1D32CB720F40BCD9DC5F2B06026792EE6E4FF30E3BD2CCD51094B57F6C33A790235B07889983713E51FB40EF447073AB1B4DE407933264E6DE5B5716082A71493B6DDC95DD07945391E38DA945CAB6B8D5BA19B31E2FB0BC4CAA5CDD17E9998F346A2F400CAD6D981D62D3555540A9F43049B0F2259B70D11375E503576E86460F0011B0448DB097C0FDA0362E4754A4D3FCBFA54425106B7984889DB659D49229F1ABCD7CDF06D3C15212A5D0EED7F71A5C7925B1B60F7534030529A859C98C3493D5AEDD728CE7BCE19B86E0CB33AFBCF8BF369E1388541B840AD1630C05C6A3AD4B6F1350ED69CCB00552D62C7D98EAD96752F3ECCF785269A3496987D31FAF74B11B057A0CEBAF2B95C186B97409DF8C865E0CC6FF36925ED2BF14EDE41C4DF045EF0AD28C5746871AAF99619DAFFC8C8C77BBC0D48A2B32678D2D53F35482D34C55AC9819AD86D00BB886631C463146C28B69085031B7E0049E378FFEBFC0CE8810448FC796871D2C8C521D6E4D9608F43887DE5ACC106A06D0652D67C5FD177562D7CA4E2DF52B819F4AEAB94FBC4BA3A38F3143915FDD61E27DC5BD648E993211852ECB45C69619ECB2E2379D0928259AF5D5604601297E652FEDF084F15CF666FB89EF0EF78D4C7B4CCD07F8F474F657D92E318B41CC811257C454C30E8A86C03B8139A256B6044B7CCBBF5977E4ED5AEEC1A86C89BBD76092912705BAABF7AAB98BF8CDBE81506E931ED8DF0928AE16A6455A7D81C719AC0375295F1703C4BC6C0AF44416164EFA2B46F63CF20C7563109D4CE8C5314115861B9EEBF35423B23A3185A9655413DAA08BDA6ECFB867BA5F982EF70E25AEDE6C0959FC573E636113DB30101B4AB5C2DA4094C4C70B12D05B6CDB4BD9E31B0ED7B57235E2392EF1321CC9DBF89ADEE7A2038A5DC5D5477C4648FFC3B89E00257EF73E20389A041E167C2A2C6A81E6D0C319F47851D2ED0EF3074459778BB69C3374888366C82FFDF19601D32E692074891CA5847C3EA6AB5BC41380BE3ADDBEA8EFEA506A7FED0A5C46532FF69BFF498F6006874E39F8815F3A1CDD6A1079D0A75DEDF6D20840EA0F630FE450A1BF9133A2CF030AC784CAB0DFC2F567C5A78258EF7E9B2BB49B0AE3DEAD886E4B6456758D85E35DAD0617ABDE59F47157BA427839EB705ADDF869694BCCCA571B392CE5A640A93B347DA83F42613BFF3AAC1E231318E74025199EF6764F55727C525BD13219DDBEF335BE3E084018C610088CA93987627059B3ADAB8616F9974094F3DDF5B2E952B324259D1B4198C059E1FA7E7345CFE100F6BDE173606CE2E939DC8DB28BBFAC6ADDB06FF0FBF2CC9FF3DC4953577AA4A00A3F265ABF9E51F55FEDA9FD4B3EF8D6D5019837BC749DBE60DADED9C0D9CC38790AC5955D1C3C7A4AFC7469C694C0AC63871DC801086C3801F7C12E37B63AC8C988BAC5F88DEB2B27D62B80DABA75A5ADEC94B2985E14D623B5DABD809CF61F8C3F3E640A24805260E82DF6FA7CC5BAB771C12E5F72F47854D3EFCC62E0B316B7C980D3961AD9B054B7071980CC70AA0699A7AE9F3FCB90DF0EAC8C8972D1D445E88258E6EEEE0DA9FF878F743A4DC396F41CAEFCC44A4ACA665CBB4FF6FCB201E40CB22B9ACF31F88DB6B8E803805F0A72EB4523C4696611492EBD6A9F21953E90399781DC36D3575563FA3CC525279D3A3B7AF33D608B4BDF299ADC9A34849B7BB6F17339FF91659310F5EE27BE12598A42A2BA9496ADB6DA700FC3088A0910E490D6CBCA19DEA091B021AF94C9A452D9FBCDDA9C5F4E12D8977BB93965A9A931F1B81DC49EE999B217A3E138A1CFD9B3B795638735E6B775011D4008C89DF4E831046DA90D78D9FAAAC2D1BF2AE6D5BB51FB61854BD17F0234F34EFB5B6CB7424461E2220A21EC4B38D3DBDD3018BDDCD236376070542A4B0C2AF949D58C50B866CE76B6DD7C365D92B716286B5FA95E9F1D25625AA9EFC4227F29381480452578B6FE27D7AB21778F02B8CCCE283E30470A2D38DA311B66DFAEBEBFF16257D8BA135E6AAB4E26E0390A1D700BC2D5C28A458E083B63EE98A01A0CB1BE2438AA4099BB5D79414FDFB002572CFB3A5ECFD8456FA621FBF02663E5F41F703E18BC5775217829019504852311E3D557E9C4F3D98ED6CCCEBA09507B5433225C5F52DFCC1C5455DC552E5945FF660A855FABC88C6E306EDE7F166AA6AF42EBDB04B79297712026FE79963318550A19100E222CBC641B55E50784F5B049A835601C7839F29FB0FA8649EEF1F206125FEECDD0ABE160247810E80DCE2DB84BFC1535E622169BA237703B04EC6DA9970E9BE1B99902D07074CD7B13B8BED195753CBA10F9F64174AB491CEBC5C374751F919F3119BE7DE007A7E651609DAC06E8898404326F53AE8D714C32144D6D7E79E4F5DD19B6B498F609FB76E45E228CD4B3D8C7221F96BC221442873A0EBBDFF240B72DD22DF75A02D25047980ACCE46462B858FC1671FB49A8ECAAAAAFFED609E1489CC6D9465BD1E03EA3ED40D4A31E988AEED501944827AED4B054D27B0A6E8AD722B471E398EC66FB401015BBF2CE5B3B8702C617AD558C7384CEE65EF3DDC53A03E558D5543B5BD51C8D28646A3221B6258406AD40298919474B30825023A31144652622AC6ED30B35D461338E1F5B69A1CE004888EF78F693A9AB4ED3668963948F7C009B4A0F48C8B25A6CC62F234ED6A429204C5FECB9700E2904E4D5F340F90FE4B6D80F0788850B4D74F128C3547D9AE72B3EEBAEF004B23072A88B39DDD0F1F16DE7232D9FD450667BB38BCBC7CC06D92D04997B9971E044B5524BB6088181BE89341ECC9270699FA86087AD96739D960AB6B3CB924BFF4D68AC25D5B5EE1286D5E97EEBB5E58BE22D575CBCE7F5E7B5D09A413F1A186CA4D4E6E790EE1641B468859C8DD3FE2634BAFA9FF4CDD017F328E070E7551B43C6F5CB2583E227BAE550526B0FE9570B4A566B1D7A0FBAE448690EC9C575BDA53D75CCBFD4743CD68CAFFADC1A5A7F580F033C899FEC78A5C69826EC49FB732EC27542BAE53DFC4631111B2E14C50DE649F3DBB55138A0F72D94BA5D9787EF5FA2D834F1DAE5FD674710967EF81EB9EDA14DE89962897CD75EDBE5D7D45A05FC74ED00B10776A67A1DA4F7249535A013C3D9B0E2B72F819B22F594C524F6A7FF3BF325722831DCF961BC35D58E87BBE86126F7B122ADE6B66F1D685C0F415695EE36BFF4BD421E0D1EB0CEE4C9DE448413BF28CB16C001DB5E73D2FA4F761ABA4B79CA7D1E47CBCECD3C6ACA012D030DE1A111FA0925EEE1AB5E582E3226B1189AF241AEA0F75633CA33CFD87519213CF2271453B353F1B5EB2DD03C220AABE7E9A0EC3969BDB0452798BDC7F6BB189775D77501BE57CC26AD02B8DB1157A838B0ABE873B2C77C22256740785648617747AA88E0A2A69021B479FEE1288C2482E3A2CC147ECC0D45B9A4DA2A3AE4767D278A33A0DC86ABABD7EA7AA0B800A84CE7A3B32D72D1FC30C8F6718BB2BB995813C5E7CD840E501E059DD0403391000A03E7133727871AE59E1A210142788FA7369B6FD583D117C9449FB9EBB07CCE941CC3FB449EB08D392EE1FAD3BD7CFA6B08D0E79814D06B6679364E486D13A42716D8A19D987189431B69214C9533D42270DE54760661FB966686DA75FB9A3C2085A201AED2499F27B0E0245B68619F15475A865E0FDA86D87CEC70387F417E20F4E82329F79A1750EB978F8CE46CF0495DA75F34C2D52CB58C26A5A5CCEE35E5FB8FF23A2B127BFF1F6E97BE1A8034AA639CF496417C04E55E28420B94A12389AD80ACCD107BBE3F107CBE01092362CB4B08330A99F0C33EA1E95C446E5735B33E8EA574D36F59765BF7B9A28A1127833D2957E46DA2F833DC6B29B083C11AB860298394467F88C341C58B62B0E33A09E9F8D8D65E25C04CBD3A9DA653E0E7E3612B56DBB700E26BE0793A6BCCC8A12B470CF4A06FA349786F1AFFB14DF21EED0690BC8DF284E2F485BACD4364F42FE3BB88635E007F6CB68F974E180737D77FA0766D6A1DBF826FAA73DC77026B55E234CF107D30489FEB2D509200A9112181B27E4E110E7F4FE974D926F416F5C7A301BA76C4D89C215D5689A2B07D5D1F7465F44B4E4DA4B5DC7784FFA478079765E470B4D6AF612BF32D58A1A2F564D40B9FBFEFDD6E6018383CBBF49E5AFC9125E6DE6CD81B78396E2E539F5C66CFBF23DE0AE6AF162D8E5B2DB974380CB15EBF09CD325DB03A8C2838AA7E130916CE2AC67C0DC59945316E46945C6F84B5845D649BE67E63ACFB2A8E8488DEEBB09E84A7F29301457A038EC7F3CCC8A3E53CDAA4ADD188C6AD77243DAEB707E67F07FE643124117F4FCE4837CBE49239F1628AA49AF720C9EF53622B97FE90AF125E54B3DE5D53E84FDA84AABFDB1E889DC2787463E4DCE8D04CB58FB6BE38FCE87011974876C9139AFB171907F0F8B89210D94DE9409FCB4D4FAE11AC680B31E3B39332458BEB69853AC7920951F51FA3DAC6378D4A6117EDD0B3D7404903BD8E20B67F1E4B5290AD76ACE53023E8BC81B9CBB776BC1D5A0BCF67EA946BF3CCF26AC276DCF24C380C5A5BFE46A2FE85CC0F3449DF921ECF295C0ABA966C4193553ACB3C1C1B8F68A60658DC9769E79F766122F96118ED99582EBB03922B2E922CF3DC3A2FCF8C4CDE8B7977EBAFC5C62304C2593570F6EE79CDFEA3209B23C7881C5F10A55E3E18F1420A8B618A1646564FC5972E965554616857C079CC1384407F70A7A575CC8269349AD8F78D61B4CCB5A57D88F3A8872FF4905BEDE464624A336BEEF2FF94AEC5BAF3EB695E5A85E2E3684476B77277971BB86F44CE88C9061F1A86D35011BB4CDEAA0CE67455150EF696DE4C8D3A5F8ACA8746215B2A59EAC9AA4581FF91D6B2A3E434B834928F8F1E4B3673B02D12EBE3DD125F04FDB03A22F6168057BA603EB2ECA50208BAC9AED056F7CC270A8B9290F934CF7D398F7E935A8BE2CEA8A953FA4069D6351DC8CD2FDE73D63F62971E7BFE4CD9EDD46AC1A2B7BD82151851A5FA045E907D54516F1383CE868E9147949BDF73C72CDCE1D8884FA674EF376BC37A44F7C3EBCC35AE0591ED54C0576266F0A18268B1EBAA3A6DD44BC5D2A5771C4D1B2FDA82A015D1FFFE8CE4454806965E99BB3F2735018572D795302AAB1156DF38591A74DD80053CBBFBFA102ED94606B6B8D0D764C74AE691FFE1E26F9774AB90910162D83D796D30753A5C37974597EA6D55E706A2B0F5311A0551F05A13DB96D1E99D9FE22C81623C48D82646B90F17E3C725B63D91721BD2D2A1008E35410962FBC3598F53B7FC96D5BDD8B85FBDF4576A0C7EFFD652E2F83776EBB3890F4C4D30304161A13F00AD3D2A64662794DD6DAA40BB9E982C74EB35FDCCEBF1905E62D3B20D5E440CBC5C582C40E506EC353E9402D558DE2622C7F22BA9C9B047699E186FD87F18AEDB875B76814FB350158BE3FE0D1C73C43CFE0CCD718C7673AE64C976159396384AD559776A7F6CB7B4A70E20DC586C8AA5592B1D758F97E2A4C9057731FBFDCEDE8D93F937F1D6AA636DB2CABCFC584A7B70357683FCE7588AC82D4664C6F0131F30DF726F1389FABF920091A3FAD9E06696B174BB0EA2982173CD0A5D746E878423A8F68F1F5580D75AF85634A63EC1C27B1F82927C8F8AD8BC7B4D9B231905967E71CAC273E0CE7721B1095F620D5731B441E4B4750CDE63A2A6F39343AB887684E027ECDB9C73AC47DAF5BFD5EF9920B54A32A7F6EEDB8C742933F08BD975C1037439629B0DBDC29B782145BE86195F39F1023EDD3DCDEFED0B84E4B1DB606664437600D52AA97FBCE3B18EF1E303E621D43A7939434B00BBF8340AD84DAB02F3DC479ED202CF362FAE752EC57E44F4FE41DEE9F60D7713E1C152BFB808FDE530629077B1376534D7A9BDE0D99B026A8E183512323C1185E0EFADAEE2B989AA9295285631499EDB37B5376BDA17F8D916131034D9498A21C1BD3EC1EBCA032778230C532A3BBDE65AC56BB0F405F211E8CFC6C5D0B3AA4978FF6C892B8A36DA80D23670AD1005D2B97069E4EE4CBC6813D318CF89276205398E8EE7F9B0C194F42B6F938836DABEF6861844AABD6882EA2AB5B010F954E7088EBE97E2008077DD347C12C0F9C297C5714B793D1FFE335F23C82F611B1ED0B2D01904AAE8046A1BA26AE5FC2B80F6E1C6B5253FFAFFC008C12EC465D6F212269957B628486398696BFDFA99513AB3FB254F0CFE9CCE70B03162F11BA935199A71CFCED6186B5E93B8F13FDE38B906248CE135EF9DFA501704847994E24927047E43F671A52FC3AC8F70F2FB74556DACC8F266D196D32E3E821C8B3A12F599A4D92938B0566F4318F88CC636BAA8FF0ADB575881537AE9EA400A0604CB90075D29F91863987D6404FB0ACC622887515073EC45169D85BB2D2742053A4791E9C6F12274E0C0C24C98DA82AB77FDCE39A6D110450965C69021DC7A49B58A07D4F167330BFE59E981E27AFB9DEF9CA7601D21AD463EE251C673AD261BE5BFCC7969E75D2481BD7A00C18EBAE25989F11195D3DAF665961D6B42B70FBE195A32BA082F4A35DFF4BC266C9FF119AF9C1B3240766B9415848F9B0E26F20BF2D0B023E53235B512841C7CC0E8FEB58C3E1E099E1CE99FD6DDD26309300FC116F730120307413EE3D84EB0530B13E7E4E34EED5BC9A1C21EF35F239BEA8168491DDF0076FF9209B8920900FA1A2F502033CBB99E3D3DEA72CEF72E54C834510FD59C08747F03810BA02DD94B6A7A6256850B7129964761E323C53CD39D26E69BF146885E8C0143F2A61926F5DE83A5561A30B2C651A025047C2DFBE9525DF3F2EE10742BFA6E8403F86862CA1988ABB5A53C71412E2DF9F05C5B4888F6119276AEE0A5907222850F7D825F015D6667498D1A0E9E53D0A39C390E8367DEA3A4FD86FEB861954F0220FB2EC04E2ABA5C75BF4924DD9B4754CDB38368C2D432B09FEDFBA918CC97F6979C974D8C5A5770BD988A2F40768FC49F2B56C7507A9EABDB5B4E723BB5B06E96C57464C3A64FE09C62D5783B0492F2F2891C4098638274CFE24CE0B0DC45FEE79AB32BE8C3B5049C782F11ACAC0106FE4EB0361E1404582EE0FC8A80251D3F58288075A4C35EFC0A06C653C86DF92F7479C8CE2349978EFEE82DCA36B0401CB61DEC15E4954D09417997E928AB64BDB1B1625030B02BC3210EA0A097430ADCCD589129966F20CF392E246575931CC6B7258158A1356CF350A2734BBC88C8F3EEE1EB6FC9B66B1E6DA353131FA24C48D4F6F25313417F665F52C91B9098E5475B9BF2BA7ABE51AADEEF0557EB1561C85D8230DE1B9E8F57C6634FD1EF297A861EC842BD0852ED69F8D71F7C9B749E1B20595A53861B97C9D7B382C08C4CBD93280337EE7001BD33944E851C8704F3A294D8F7130401E4B823DA1E5187E294C15488E34E7086269EC530EDD11D9D9D89F56645E6163E6A249AB16E342C10452DDB7A1E27ACE8F3478A45879929F0EE81A467EF09655686D40270C2CD3BAB943F11A8E85BBE2A45722D9CF80AADA6E2937E7AC199D5F948F3DD06ABA7B3297C302C723A08712440CEF5ADD74404C1FC085C2A54125B4524226113D6D69A013F21337B745B8F9EFC4CE444146875CA6B65A50B45D3537A0845C3715C06A07658D56B17BE47E790428076B1E98495D3009B9B3227FB3F4E80F27C5725813E59E4AEC4E2B97522284AEAC3D78DDA2214F6C26664E6C203D19E89BF2A7B1EF7EB746B6ABF4A74BD8B8267EA9676141DEB6CEA78CC0A6C39F5821A6250A9DE31046B4CF6E120FF5F1653ED50025A84041A2A65BCA29ABF5621AEAF8ED17D2D2FC4CF2FA628B324914E2BC5A5EDDAC38E7486B8FAA1A2F6D332E8A97CD0C2B8556B76B7E18BAD50678530AAFB27DADFAE80EA51B8F23C25D28495DA11C46A9541D154912524DB2BB1168D3D21FC6016E8AA40753FE478FD397418B9E7A4DF5E69225D2D029826CF9D01CAF6F3F8332948F45B9B936C82108FF8319F8AD457DF7A7F1C2D31C6404695030B97DC26343E899BFD359A576179886B882130991A9C8C88D886CD52D5E39C3B48FE2F70A320FB383AD37CFAEB263B4545D92F9807AAC56F439A6BEEE746D9B7B240B1C9BD7B8655D6BBF2287B5585F68B5C387FF2151063D0E05D18866B989CCFCEF772CD097C138511F1C47C3B0CA35716B142265B237B75203687970FF4A8A84D7FCB880C8C3CD673353EF2FE799A6298C83E4116A2AC967007BDB3C4E6E024103574E6987081600057297802303454816305CF969A9889F991429B10354B049AE367DCB519DA60BBAFB84CE9D75710D1F6444062511ADE167A41E3A67324B7687687D47F20EF9764973E5B4278FD054F40BB21D563181B2E6145E2D23E38E7CE6E5FFD65F9CB2B6BD59A123683C287ACB3A817E89DFE78D2CC1A468D36CD39200C16E69B2D7463D0D6FAD072975791B325EAA38CFBEE031CC3ABD550C1F57349ED897F719AA3A13E48C26D92C4E4613F4FABB9C9C003B63F40CA58F0DEA32B6D4F1697B01C7F3B8B4F1ABBE8AB1C346D3AC8BD5BA0E37AE5D7D5EA8AD9E800A2DFEF7EA08AA1FD67D4E2CBE737955DD8894AC9EB5A316AE88106F26D94A740558DCBEF7D25C9A107C0359B4F771E83061F4013810B2FF8B1FA64F8ECF589DA73282E4AC5BD6962F935D5C6911430268B12F30A05C9B1866B4677DB2737199E013755023B34C874BB32D70E67A1542CFD32A1C393747394CD27AA854F1E2F0D72254F3B6244CE9A67D29B772BE10C482CF5E57ACF2BCB299A11504D23BF34E02CABC8C5906DB700FEBA3E282BBCCD9B0FDFCD77C2AE339F9341457BEDC79D132A1F15B95A7A3D67FAF4DBE78BE0871C4236DE1AD33511BD1343077F3FD04820BC9298655948D554343B8DC7AF0B2CEA808B4C577987A9F608D85370DACDCE6AB5924B7683110E5946361A172B7BF11546F4F70E2BC5CE7012EBF44178C292A876D62A5651E7E31A15B8721BCAFCCAEC4AAB73836B9DA4998F0CCC6D2FFD20C70AABFB7FDB508FF9ADB8057B70A72796729EE60141151E1DF2EA2D8C5C6F82E2CF58B9247D398E83539B998EBCFDE82C04848F8E0D04CF0630416424ADE49E934229E420102BB49C1ABA40F3DB80EA27DC0CF5F649F47B22761D76AF3B6F2EBB61719C324AF5EB73941557653A4E01544F051DFFD0961C85965F264CA6641D9C7F0412806DC625382D7BFEA06D70D17FE56AD6AC185EBAD78A86B6B0654362ABEFEF3E90E8352273012E750CB69BC7C5771A2A65EC54C6DCD9CCF0B2DAB13339A60C20261D01B4EFB71768CDD2673F3629D4616B1A4645E5FD02F6F8A77DDF734C660774F7B203C5A3538F103CD920D4FD871D5D95D5C763A80B8C54978A90A941AA240E5A8B88BB0CF26768E9A0F72F67EE07E425DA838B3B61B9D2083E75406BD72B33B3CC0C463CF194E3ABC9B7D3024644D84B70A4F01CD8CC3FE36F8D8929D4A1E65BFA2DF84DDFE8AE66DC21A94D4B3D6344850C516DD177190BD384E259C14BCCD5696A9022D8E2FD1BC38A59A45ED48CECB6E4E6874C2D0650C59B0CEFBA5375583EEE5054097BB670E753288264629111AE732AAAE6D75323362136EE1B7A0BB9605924F556CCFC5E8AFF8CAC7A31A87AA35962E7805C4ACE289644A7084EF1D3E290913211D9496D997F0DFE70CA7BFD014A27E6E506CFA63CE137EC387594AF52C1E86B04A106B0A6F47137393B486A865BEE8DF4856316CD559D50A0D495B106B48832F2124C4EC9DFBFB9C406C76F86272A57F395C7D1BDF388DD51C0224B16B350E369ECA6E6A4F9B84BD41F272977A325192B6664889C6755865502AFBCFBBE41F507AE45D6029C5736B600C31FA84063528840976FE40EECFC9F8041D85B7B6C6E2E7F1BF638EDC0ACB6121A5BFA4249D0B63922D3B887939239D007B79AF60CD79771E1CDA1F46B4250FEF863ED21E33739F4713B5CE62AD77E67FDA0E4CD5CC6795594930B6F5A1B52526428A48389C928D52D452301B867D0571322F5DFBE5A4E46D5F22D3DEBE04384DE0B4CEC91A2A54DB9351D6F67CC536C6F1371F3859836D0539F73020F9C7509C39E9BA5271DD627E723953C6F885FCFFB7476490C33135672CC1C1221C2D6698918C4BC1DD4D0675BC1E246C915134B16DA036B63E7289596B65A73FAA873C6E3F79C05F68EA3F93D8B0CEE45C82C81C6C5C2C894D3808B9538B63B2CC13F4447D429295FDEE457CBE10F804E29BF316D2FA12A9A46E170C07DC2AB09190112DB80E6EE5136A8C82C27DA6EB1C4283C669B5F544AE1601865DDD6D22065EEC9CC1CF8E0F2610A78062E87B8CCC17E21D3C18D722E2EB7B2F128CB791342C26B47BF6D7C40635C2A2974157B4344C076F31CC29BBC77E725E4D96EBAEF12333A411DAA2A1251B40EEF8D66E108EE7D9CDAEC01C22154F76E9B670EF4A5124A4CCA991DC7B14093918E057E8D6D0B79FA7043DF88AE4AD36B3DF478CA078E48382201D7DE38ECFCC4513028EC7D7A8EAC56D8F8BB3C4BBBC3421D27CCDCBF5BF423D5DA9A3AB605FF61BAC171D02F4CB363DFC12708338DBC841D47969B0850E7C0F94617B4E50AB040289DDC9565AE0590BA9C0BC4699F351D11E232DF64A5C6345AF908FA962E2725DDD0F5F9A3E3A0C85D91C376AF27E18959387EFD548B41AAFE4F56E0D029BD408C3F0CD208C09D82ABB385EB54C0E0217A11E7D345072D2A60DE4A28417B0B7B778A4C87220821167B05B6D0F738CEC59613590270AA1C3B8400E5E596B49F7316892B775B568DF74EFDD60694FAF23BB9A088916EE2D05022420A4E64380574825945285D93AD6EE50FC6AB5D7063A182DAFF74B8E66846E69528F894D18A275B3B31762F9654E1DBDB2602D473BC7A82FB65D32726B8B953EF1BD7A7AA3AD8FDD8892F2AF15969539566EBAE00DC5C20CD40DF9A9A48EE65088C98F0AA00BF528721F1723629260B84E80D3A08616EF199172F80F24326A16215C755B614E6DC88818B3B5A034E8DF3B1519B8C54FD5D298149524E39B2DB50453296D988F020602A4CD2DE8D4939CFA41B98F334029C536AD4BAF929DEF5650E8810073F0B07A503FE408B93D09D3F7703E4F24C7848891D21241531846F4F222BCDDBAC468589A475CB835624C57FD770F63188068C373CB2FB951D6FA86C47BB0D3DC4D4C64DADCADB568EBD67C08F168155900E8E512593EC5998A131FB9667346AE644E27BCEEABEF63136A211FB3BB702FAE719399B48131B4814E246E3BD0F80EA77F9CBA5475C0166C79BE91F0B1C1F991008B962B4006635A214E82AE256452589BD2E4270C3BCB03048AE797BC87A638421F07EF863A19E04A4DF66A5683386330C0AAE967610D332EAA750279C1110B20CE2E8C50E37110153108B9B8B56599C04762B2B40A03F64BDA6F8567E4DED894BE2F75BF7F365F9D766AD98736F8EBA292612790814CCF53E71CE7CC7D5EED5573D260CC9AADA37CC992930ABF65EC590BDB26407C19B08E3CD273E507F8D76B9FBA96269907AA5E549872C73A4C1D2C16AE8D533EA5A18349190F5567572FC5B580A71D11D84D2D1B463373B3AFA4C33B91241D1E5715763B69EE168C1C1CA372EFBD21528E9784E9819FE31FB6C772EEC264BE5E2FB5A61ED712AAD29CE09C2A69A9E774FF35982DB902DAFF136B285414681AB6D77CCEAB01FA71161239254D95F0382BB1351A6108BF681BE4130788AF311E234C93159E6537B1FFA72751BA8B39E0302B1B0749C139A5C54DAB4B16317F651F54FDBFD8CA0AB9037212CF770B6DAFAC4B1AFE8E6753CA6AEADBAD01CEEDD4CB4986129FB5D41460C6A3FEBFECFD175DDADB20C2DA3823E45D5F34D68AF1665D369411B9400E8D55F03A7EF88D7D38D2AEB45A93D5A37C10EF4DCD221F5EBEB01B80B8060F3A740F601BEA5D00979FE702A51D29F6E64B49C779847994476DF60792F37D44A6488A315995100584981B7F432FC6B4C9E1EE95374443DA8EA2C6A1DAE03334062665392596F3215F339FD5C02115D31287EA48BB8CEB60E514D93CC4D661B29812E94FDA8BEE8EC6C130C51BC887E55152A5BCC840EEE7F5723BED13C653F4A5EBF3DDC0900EB2F468284CE49554AA880F5E4557F46C38A4A373CFB993D8EA046186EF37DCFDD905DE17B66994619EBC0ACAD9D42172C6C46917A16117A28C9DC0E2F9C330AE135F1858218CB8150F79305D5D77E4F0A0BE2672849378E26BDC9EC0A21CF1FF808D6E4AFDFF00A3528153E1FC564DAFC505792C7481195C1FB80A584E4907757E93A7DAE231C517698A4206C5508946D2C4A1BB64E6CABBBED6402D683D74C04133A5FD0B09194F2146EC8F91F4892E896F2B0C5525D605E2CEF960257C7CE524727A95ACDD3FABCCB71B8F25E1F8D67D71BFDD55EEC562BB1218E20487C75A8590FD82815CEDC797D3DA2D7CB00CD886CF8227D28530358D264903873BA74D4998E0EC47441A5E0C945E1F0C6B2585997A2201BB1BB2D47BFDA38F84465567A525A2341F95727D2552FC7CE938F7B238EA73C35B9BAF8462BFD65CDA8FFC91A9FAD46DCC6E84F81A1C6AC033B73627A7189390FD0CF06AC26B370BE756BECC5BB3AF2941445B407AD71BDB901B30A9B22B979C170D2DEF1FCA3AFFB265528D59585D34FF60F4C8DEC94D1B68F1F583C8E920D1C12690116D1E34DC90A616B8501B6E0C5469A848EBE69A9A0098BD214DAF119C8BBC9896093C30F80E955B9A540A6B863C5076D4D954E1AF8BC8C8CA75AC197A41C514E64F29FAE91A994FE38B6AAC75D945CD9821AF2DE46F2E96627D814A41C35F9FEE78CA62E5797B908B9D4305A30BD8BAFD3D7DAB93246E1418CED0437C17488B8C4C83EC62D20007D2B84A49C809E0B69A121DED6CF0C3A0AD314E67E07BA371BCBAFD0063D5C2A767CF9717028E0A5925676D576A3BEE4238EFF229B2DECD37A77EAB6C8A03FB9D6A6A0784084B0992E458AA4BCF2A652B9202DB324B8BDDD26DB98331D255121228A0BF9FDB4B7D30B1A9C3292FAF93D94B2668A7A29D44FF9C29EE09ACC98671F84318D1D5BE93001DA68FA672C31DFFE12D43E26582A6A8603A551264326B27B5A933F7F9F65559E52F2ECD9C66A7832CEED83A7750B32174F9CFD5AF8676FB2E147D384051B6114EAB20D3EBE21511459EFAB1FA94CBF6021B271BBA7F1EEC19C8401F8B0BEC897308C80BC33FD65D5F69B05096A4A3C17A0963C9202F194F23B0544933D067F5F7C26657D888CCA4B0C0E84A65E1CBC3741686F3EB9A5054F312C545FB48362CB33D3E9BBFC5AA59EDC57552FBC169EF4F8D4CCB6048AF4FC3387470021DDD08056F8BC1C9C70312121D14A108032D0CF88010304F1642517EE9CCEEADA4298DD20EA672F774C45DEE7E149BD1F6C95976E44D9660F8CFD735AE03312852B9B0C26DE77F4608AFFF20A2E0884A737C07A242B7F1D825A94DBD0171A770A1ACBD094033978C4EBDDDB4BF4B2E16B597F990A000454791CD07CF27650643A2162EF94E5139E8C4E22AC3A7A29C429DD8AC820AEF7502C5BFF1F97266B2956B04D3324A4872A678E0CF63F29339562556E0A51B52435BCBF7C5CFABAB34DAE30386D40E68557C5B3444901E11542B1A17CCD5A99B403DCD7F13DFC24C2D9B3AD68B2CD275CEC7E9A593F7824F3644E5FE9CB6FFDB4780C5E83DC116944219A78AC21DB98FB72DC8C003DA7E3C5CB03CE119962686C15D2DF1E79BE8815AC2DC34B35DEE7F483F232CAD42C9F7E2A2D739A628EDB637D0D7FF91F5AE52AA02EBE02A686D7FD88453B7C5507DF5691A2AB852C9411876941D5B8C6BCFF9821EC143BAFB2E2E90341D2535C5AFFE6A8B7E5A8A5B28E33424DE8F25E963243F7B9C1DC31365860F30FD37ABB2B938FC77D97AB9B5E4834625FB3FDF8C1BF85999EA61C1CA5B4658E5DD6D180D5E5FF0DD11652E7550F474C003A4723448C06BB2CB1A8CC9CA6590BA00C8205FE1F17E5E629A1071C90ECA731B88DA992C46A7DEEFBAD86F99306E3CB481DEAAEED2F6CFBB9BCFCAECDBBE5B36D579780461888C775A9897FDCBE1A24D1ADF7DD8170F0C5D8A027E2195326E846A0D1C2D2EFE09ABF1110792923BB488993648E7C57F53CEC00A7B49697B04DDCF635CB643204DBADB85E25E170BA74FD05C341DDD128233FAF3B0F9D812D47E4F4411A148834F245410127E1593D1D04B9E359526A314E8766C3BBA61A8A60E537ABF3041083B0D6EDBA6CC46728D9C30B273CDC527133F973394EFF13E1E65647DAE218C7689A74199E8F5731FD752963B7426970E928429509C747462BA4E1314E36B0E36663E4A797E1C0D02ED5DB30C1BDE4CFD3E61D14A872AB5B852DE9B0A05DBEDA9CE209ED2D6607C8E1C0E2ED649D61D5C58845CA8B169C127F55164B2C8616CC3D5716B0EDABD3A88602EEF1B190FCCE6B29708FB40956B3E3653EE2E0BA08F33136A7CC7FF945B21F79757EDC14263A2FF1AED51153EB77CB63ADDA601EF88C261B3395C833A2A982485070D5799E0A5B015EE54E9D1FCC2A4844A731711575503E10664C58032DA5E1A2F37809C9E35C3F7A33584FB895CB03702A20B8BDBEF68FA173F10E5A4DA29AA214BE90BDBDEF88772D54C15A7D2779B34777E11616A7B56F5A8763C4C7521370A3F37A2D865628966DCF329404BD02C6BEC5D046B154914F4E442B13DDA575EC71A23332347EED914DFC5B8085DF2C1F8064D304A846A143AAE3EF67CA64BB6B49EBF0EA55769DB5BCAA1931B46F9F2D40330F0EAD64E5FA0DF85CA207F15AE697D6E44C363FF0828ADC475508A993488BD26BB3D5879FDD05C803DB0F46B66F2B841648E465B26E9ABF7357E370F1F72C93970D68F86690782C5FEF8579DB45574D350E66D819DF1B5250C80B49B362923A5C14F6C4B1B48F9090ECB5AE03CC27C70443156D8EE3D4815217C7D5F8ECFBCFA174454F623E22ECD80DCF7E43C5DDC56D9C27DB2638117C1493CA79A25EB2A8E146B273B29DA0107E007F9D44F08A480DBC2FBC721D55330E2FE218D5253730847107DCC32ACBFCE8E312FA1A4F680C646B736F93025F0DEB5DAB8F286A4C3BDAC8DED0F203455E3696185981A29582DF5D1BFAEBC34A73DBEA6EB85811BF05F1F277096711043E91FB36D45B066493D8AA2E8FAEA33A06989F31BCF52E97C05B97CC470DC8321FBDCC644E0887EBDE8136E38434ACEAB0D7B9ABB7C6B0740CD54DF646C7977595BED83F8E29FFC2F788478E96DF4BC3BAD1D293FADFD516A9004519CF0E57BE935C3E3C0D559D54653FD8D741170F3BF68B212D9148DFFF85B4A0A8A04F4EFBA3422B1D5538B1D9991C35292967EDAD35FAC47336458B7D5C82A96F26D6D8B58D111CCBA962FFD1806E74B994444E92AF6CBC5E49F58B80A1DF6176D7FE403AB37B4AEC4E2059B271B7BCD30E3A046C4CDAEFCC559C3D92485CBD904A7F8AA41B17B3AACA4AFECF46B3704428CC3A48D619FF7FD6A2763FB24B1CB74ED4FFFCC84F809C7CA952FDD243447DE1034B807DEE5F30A9ED1C29DF78CB33D73A66D0963FEDD36219D93F9C9C3C7BB1695026D14B403B100F9D9036C3DB1B5E5946594B8DEF8D1B60249ECD884BCCEA22C26BF4E2C855806EF75CA8D358EFAF80AF5D050E44E085050B12634C4800D43824FD8998D5636CCC727962639457692F3B2F4B28BA22E5FFC7994F750999AB7A304BD5CD722E1E3914DA106DFCA85DF70C4891E335837F27322EA109FD74CEE44B6766F13DABA0150E0578407F350703D01E3650DB6C93EE92E30F79C820A5302B0162219A6D1723EB96C31DA0D05707C0B6DB3507643EDCB7F64037C8EC4D757260C312547AFF5CB77FEC48CA7124D750EEE27E20BB536EC1A2FDAC872BC51B74E1FB69E5732E896A6D573AA6BF3F75A07EDF5CC22A008FB0FA3BAEFFC52C2E7B611560BD2173EE5562A9B6F70186BCB8D6DBC524FCA512B444D6D64D678E523CE98BF0BB39E64B6ECCCB6D33556BA95176F2E70506A8B247066C963AA12F26B042DCFC21DE2568F6BA6B8B4DAF989F538112A67B003D410023F7C102CACE81CF7FC0846D3B2F2E4122818235478C48FC24D6546F69D080D2D7C54FEBECCD08184B1B0C408844390FA826DD551547386392E79CDA6452A2541D74360BCF75DED36D5C99820FB65A15B4615A0E8643A23B4BEEFEE54AFE1DC1CCA446DB6B064ECEB7AB13BB9D2458A71E39727715390237FDA3696BF815FC900DA7303C24712EAE51DC2FC71C406E431A51EC36ACC52E6B6BFA3E06DA7A46086421538C0B8C1EBE9710FA2E2E5B75DA0A284E133BFC7BCBD49965E67D758F080F42004A049BB9102FAF3A06B9A4193F1D35BB2661CCA043E18392C381312DB94DDD3977DBEED9D9686CEBE479053C4B3CD93CA1CEA4D5345B4BBFB953F27259D5978433F6CA70B26B110218DFE2D2E6467699A8AFABE6A23BD55088E5B489F42D240A06A4D0E3830B3D0E87C5043EF9CD097E52186335ADF3C9A5080A0F9CC9E3611FCDFD04C38A76F3C9734647157BC38D4B63968D888BA2D13E364EE42AE3AE739771C98DDEA0B455BA659FD7793AD6E1B860A9FDAF48243B2B54C6573268C466ECCDAC6EAB2B2EADCE5CBBAE0CEC14EE9EC6137318DC114031FFBCF20CA9A5AA5E0F6012860FC5761BCF4BA13A2393F91D3D04677EA349AA8318B8F77DD791F42E9078F123782ABD259CDEAEA31E459CFB2ACE7101FE45942A45838A7603DB82ADA8AEAE7539C43ED92B4D466ACEDEC4E4822B2C3D78D70BB2CEC071AC73092240570BA8FEF5D9DAA63406AD6DDEA1D33C68796F4277AA6D454BCB8D10AD3E50FE685B63A3B932949E8D4C029E4C74115C6DF563B6D712E4E4DF1563145B8FACB562F2E7967191E6707FE18D07DF2EF3A6B430E6518F9184606F76207BB94AC2B30F50D996433BD7AB5CAD1A9613284DC0D30299DC638B2FAAE5C8D3479001A1596B17DEFC49B12F486B9415D0C18E6CB4A271FE8599B38FA0909F38CED058D27C30E75B1402702B2B94FA4CC19F35B4D08E9C5E09EED8CCF869B34577908BAF59B0148FE0CA4E325D73DCCF63E68C2A7DC0F5B3394E45E9964649A2A60C335CEE83AA58E1D44C7A6882766980C52122B7D3AF2D8E9530570B5DD870A6F30C482F7CA0C59A6CCA57E0EC02AB4969B9B579F4AC0DAAB1EE2DB00DAA69724D42822CF649BC8A71209177ACAB5105374759B0F5393AA32E5C9AE5F51E0C9E5321ED9CB46C78040DE5F185E80C50D9466CE92AECC34DF73617AE723AE57427FF0E20DED2F13B3AF57E75CD2069E94CAC6885FC6AB098D6E005A5942E3A5D142AB8807BB99C1FE0F22EA2192775031D2ACB2151C3C8EA95FFA864816381F5B2F5A2942BDE696853A6D24F6B6FDC08C95802CE133D7D5A46A310FCC73B27EFD0ECD846D00FD8E7003217AA962EE4BB4CE60136C5BF59EBC558762CB3F3F4DA36239967B27877685763CDD6EBC061FAADB8436C7B8BAD21FA83BF1378376B702A3147305F6E6A100782F386BA175C81B363AECE8F16D555B23ECA7D2706092B0D200956611A7FAADCB2A30FBAEAE297F5E0F11114A304E0DDBF3D1A41631CEFE4B55ABC4D3D93118BC1759301E0F554B04CEF8BB23CE878D5B28E29E44C7FC850D9FB46D5ED80CF5D3AE9B0CB5EE2CDEE9194639938D329085074C7818040EC179F3FF9A997B664629AC4E73FBEF05DFAACD9C224EF168E75D3F20E35A3D628385382D6271E725186A77E597D18F9236DCA9429B82E41CF03211830708FD61EED812305337F92A8AE99A79F8DD8AF0F5D77CAB2C8B778D3D2584315430A8D32B2400DC9E65FC41D5A122F735B75BFA5FE1C2E220498D095CAAC899AA441C805B39F88D5B861B5A6CE3FC6404447B080F31C44A6F40E10369CE13441C4E3FAF4B4B00EA245EFB8B80D6C7738EDF4D33AA1B3C8512C89230AAB2F40DFAA9823BA490424D3933F6612016F73F53245BEBD4F9FE7AB3F2C7B55B5F661D9B25F063D9255F515482BA9B35665D4AE09252DA8F305CB3A162A1C06B1E1AE466A68EB3F852B34C182A17343371AC33B270EEA5B8C1AD842CC674F3103786C419018C9654975ADFC272FB24713EB97652523B94269876F5D6E347D9211E3ADD3049F349E11FA7FDCAC742175D08962FAB59EB731333A506ACB3FE7A7AE6285BD02A14AD3A4E1070384001C225D9203A813FF43C8DE619D3B82903D327C3034AE3BE37D2C222EAFC50486FD2D067DFFA7387D546E48A0B21E3675F8AEA197F6F72FFF1B0AE2AD92503AD4379075227192EBC62D23D0459DE1690D605D380BB13309DFD1C42AF8CFCD58BABED0BBECEF9202ADBFF1B68429ABEB03092A17D4034ABF7382F23C1750B221AF80158FAC7C656663CF24CD4F5C52CEF7F95F927F3975498F50AA7935A845B63E94DA1FD53653BA0ABABD7E9AF43FD0B084ED07F7AEA61EB6AAFD9DE548ED6A2928E6CE28D1BE842446395F6C09F323663366E00C2734FB68AD812A5E19EC8A4B003E6F53CA46B53AE4059510A7F3D712ABF39A38724AA2DC1B8627FC027154B567065C447EB95AF39A68A10CC15C48890EDA1593B711D94F141811BAA425DFC6F0AB64FA1CE75F4A8C9662ED1EC91C390AEB8617FDE357D14B2D81C8A9D23FB00A52C5008F845CA05EADF588E01A79888FBEE1E05A819664B6BCFE44B4D6C0F27C37D17134740AA3C96EE99CB4AC0348676EC529FCD6443ACEB04EE43FB173651DB02E94010CE22746CCEB50BCCCAC5F7B9C09C507B6DF6B32C28D8E34F11BF40BD8F1E1163EA6C9FA07F344E3CC09E8042F9254879773C8594B72F7D990B6D7EAA5BFB3C9D95A6953D267D3D3F55169A200CE4CB7979117848EE34434D376F94CD5BABA75AB0C82618A31DB337F437FACDAE4DFCD76D2F60498641520E6E103B4A1FE2470D1EE273786731D061F78420D4E2AFB3AC66AEF41472016FD08F270AEBDA0E7F5200B54F82BAF33AA3D81EFF4DFE7A964479B4660606EFEA07DC70DB7BA838EABA5A6D5E5C5226B48795EC453B915388A3749C4FB343F299EA2D542E449260BBABDB068FC9D86D3AC007C4A68963C947A61392606A6D475A7D17B3A845C8C499B5FA607C400318CE8D53854A9A6E0F6125798085AB5123CDBE9853185275CD57FA9769D3BE038342D2A2B34F0CDD4355EAA1E60AE534F8CAD453933DFB48A766510AEC054486CA9D71EE1B6B101B3A857033C2A4CE13B10139849BA89609137742A083011CB719419DE32C991D331B719D1A8B35A144DA8FD75F22CA923E7C3DBD8842E95AD3C5F66856416EE7D8B804D493061B432278A02A970D456C31A99BAB9E7BB735BE4CEC63FC8E4EF56D1ECED809DB097396DAACD63848B0EDFCFC9BE842DAAF283C94929FE32B10CFA6D4305939E1D435D649CDBF17180A3782E903232546F6DEDF3BF0B2565D260D4853D8C9A55B51AA11A23807DEA281A2CC965B83AEF6F6532448DC1C511EA27B420AC43A63E639E7278ACE1FC8D522566BA5183BE4F51045F15C79E723F5FF9D9E9EE16C70A00F01BA6A4AA71B7A9B8C912EC85D3DB2C4266F9986D8190F3C0ACCCBAF8DB1BF34DC8ABB4890A27100A59D74186C90DFB427AB2B8885E28318C29DF313187FC13E6245406BE4469905857CED13E6EE742E0ABC5535C484EB3A05BDEE17B609DB0ECA84968D1FD84F23D25A3272C0C8E8D36C442E8BD73D22A6A37D843DB6D27E66F7DEFDB17E2AD682605913032D4E0A7D2B1C16A138188BF4E6C664E39B44E21E7137D3A592493FBB52AF041A78B03685ED96F3B70F3ABB365156ADDD0C803455B0038CB78C3F2539C5F3C4469652F2DE704970376BF5E0F7D33741CB9F0C951A9A2D1E5E389229407A507FB6DECEF321E7445A6411665E15B55AA9CA302477BF654C542BDBBFEE658CCFB1280B31B3001602F83496A36ACC588F8D30BBC42830FCB839C4EB37F6991D7388A36056693AA705967631A5B31C9E32C3F5487EB851D22BC1AE7C52015E52D9A8564FA2A4CF95B1BC396BA2683EA7BC966D46EAC60586D70EA846223F598069F5114CE19E66CFE5E0C914398F6568046A754FF7808AE1260E88716C37591F86EBB11B5C4781DC2AF8CC5E7A69CAA2C95757A6E3FDE44B1EC3BF2828FB141ECC6D818E4B3AAB5DF7370B80AC9B6CD31B641C78C939673247CF15EFE409AF385161F6826247E04C41B570714514D77A339E0E5181BBD10DE589CEB9C0D8DEBF11BBFCD8470B7F1643EAA8E5B1F03DC8D1004A5B15398CA4DF0CC7E1D21CA6505C63E97B1D1BC6BA15A79639C53B27580861C6F05C3C27DD0C49CE89FA9E06D91D4B70EA56BA261C7640B77F01DD032264FDD7441621D641F59593A688883D0F73D0611ED90A094E540B55DE0EA852231F89D66E7601C5B489F46C03BA59821DE26A77893509D4022BE166E67F4988F908098BF4F7CD56FA91970116B50FB9144862190A4509A21F9FE2935104751C398EAC656CF3DFBF08B7019ECAB72971D196A05127DE5508FA829E729607E122436D5516DE6AC989FD09C610872A4ED75C0970066910ADC3B1243B5B7AACB5B0078C30C02F7FD4F1DD1F59213205375288CC068387204402790A4F503F7A6F95021BA04425639BE00249CFB48BBA39D2BCBC56665ED7FB172A7340DF6D2CF2DA98B5AACC0E7BA952AC21E636D69E211AA73D48B750AC43152097713A5C94B75086754353566C3FE5740A8E9A1A9E7A50F1B32F234F7C33428A5ECF1A7DC08F84FD232B23A7EA9AA8474747A731205D61CE8E868982127FA52F2DB26AF432B82D9BD0F404DD758AF127DD6E37A10DB3F33B6BF7E68F185144389BFF3A108E46BA5C78A8744C3AFA23E4AEDB480868CC863EC2DCAB906B2CA71575BDA2CF92EE7CF7D0446ACC2BB615BD1C22E6DC861DB83CD7F6CCDCCD52A7E2407D787913F9A7F682A6D3C9CF67176FEF10FB8911C8B097A5BFB76778F7A112606730F2BD723291D061C43F032862C0CB8D24B9F2A607D61A14978E81EAFF79DC316A2BE5D3505C407CEF26771A1B5712601036559811C0BDAE4D1E690528D37EAA9934D6E3B947446BB3DF1D2A203CCAD9D28616C08FDD0DDFA93C6A9F9A4203D883EE17694F4386BB677A2E87BDFF50C2FD65E5D3FD3EB0B640A64241F1DDC08AACBD6BD3B6C10210A22F188596A985584DC1F4CF5A9FB95E8396A59008CB936FF220CBBBC951016CEFB66D5575BEDDFFDD24B3B1584C78BE23785D155763CCC407544FDB0ADBCDBDDE8AE7311A7A0B74FCCBAFC77853263394021720A4818FFF81ADB8FA6FC566E13D208D0EC952D7E5770C51D2E7BEE3DD88CA5C86BB86C70AEBC23471FA7AFF35F4A9CA336DD605692F5F514A251E694D353564564CA07351EA56D6B7EEFD2A91F42903E728F4E28B4E4F8E179119A6F7AE7DE29CA858341D67B4BD3390497184C3D6B5E314CA4A672CD9A6433162F1CAA8F99B951F8A96BF9FFC9AF868EFEFB80C462899F5BDA20D6D0FABBD430DF3A406D033065D54840301508CE9C1556DBE9C0569A048B07E7FBBF2C713669436721A0905A1F2458558AEF2D742389DD553A0B24B44870984F6CFC3EF702C9B334ADACA31058C2848B1973E8C3B016313FF479563FB83D98709127187C5D9F1B8784DED7BD71EDC37E56C673DD886013767057F1A0828F52A1245DCE5A2CCF9DF4BD3B3F7D48D3DDAA405AB338118C22401D1F89C3E880189165682D263E5489C2F3B5B49C7876C086D5B949B9FCAE5E8F45A70015A9D2816CD6E2D8930FB91A2777D82ACAD1745EA5AF57753C94027AD2FF7C5B6B78923A4DB3A90614D45DCCA6972D26257C4A56D8EF49818A857C782B8C70249437F16582AE3F75815143664C3C86A0B5C1D535D49EDDE0D24E1628702F8AD1CDF4F93B5001971E4E44040D84876D4F4ADAA08122945D51D10E97D8CE9265D6FB77EA9FEF4F3D4318537FFACC6F27AEB6D776571A409F10DCD8E7891C87D1B6578F49BFE193E237111883F8CB3E53211D9942435E68994D1518C76AD6E743D63AA7AE0CDA06427BE8CCB76A24613788E445C95ABC17440AAF2664087D5F858A13B540A990A9B1EABA84B8CB07309B4ECA20661EA1A1DCB6D8AD898E9CCF5D15EF4DFBBD8FF053EF10BA5C3001C24535DED2AE9672D22BD22A60807B0A1C894C6413D6EBCB93A6581AAF564938EC7D22271E265D033FD5CF29688AF3538C3C8E26F089247BB34FED26ABDE4431FD9A546F6377BD34138D9D79986D798CE69188907978E02D2F323A32FA672C4990B0D8D8AAECA0BED60AA833FCE874FCE8442030EAF5E27D4027801866B7E814A0559E34EAC2589DA1A41959CA3A59470045B26C8C936B9C1A73318CA6274F66982108EE3C02DD2A60F0173E7AA852C5C2653234C0FBB1280361EA0DA98C49A6796B47E304DD444AD4C6F16C0F41D79BF2497FCA7AB3A0C0E21924542BB631913F6D20CBE5623FE0E454203D084710235AFB00607287F0D80002909E39422650FA4EB3FE8CF08591CA2401200C6C582B0D9EFEF242785FA45A5AD794B4B48123A7BC79E5B0A93824707EE94D8A3B47E3B3264465DD6FDA6E9ED08B80B229B8F621D0416751725CA512879D5BCFA96BA3B7769832010D652EEB4618798557AB93263418CEE75C21207D010CD89D233F2BAC24F7DF5F2B81C2FE10EDB7E36DF226A2F3AA26AF5AFFC401ED3970AB9B2CA8C6391BB52E6982405841879EC2FB73BBBB9A8C11B7963C44ED198D593E13797A3DCAD1665F1C8C264DCE598E743DEA5C2B27FB27A4D9D4DD6D7C36B1C28F7DCF2507E73E7AD755E455ED2FA21ABD55AC993C7A6FB9190A9973C6374BEF1E90C258643C6E24BA0489541A84B95EA8B7FA408DE51451D2B642067D818A0E96118CA26CAB27F5364A01C40B2E7D8EF4C37EBE22BC63D92CA6D9B0347C9B1846233B49E8353CF271FCE80C1A02312BAEF90ADF96623BF29C853BE88C1E41F8C4FB35B20E49CAE3250E13D5101A7208C8AC08E533B238F4FEE97EC21AC77E27BAE3BDFEC44B86822A07B3FDB4A42CB74C99DF12F20B1847DFE782DD8768CBF0E46C77E8FC00E97ED88C070AF002DB58C633A5A2AF9D687171BD281A57388F1748E4AA6491FAB301D34489785B969F1D5F9C2781CD14663FAFCAB26B94386B4A45C78AD0FF7198118ABCF847A147EF4BAC12BA09F64A76BF9E6C332AC71D2E8083B75724CF250EC74EDDA014F529D73BDEF0916556F71917F437A3C8ED543ADEEB3C56C57A4D3D57D1A43349B398CE66DEDEC224E3421316EAF431753F42426201415A9341C384618CCC7ED6C62885148306AA3AA6478EC5345A6E3882FDCD2FB079C0D996D63131FA8E2878C9CD64E1E6A1CD39E90A21CCE4F3F490ADC4FB7D9A5B5A26CED5A724CB85BC3E96E076AD20EDBDEBF6643E215310A9DD7268CA385A0396BDDC5A30ED06B99CC4AF44181C2FCCFA0F5DDEBA6FD1407723B9630D04D8AD5890F010D004ADD683D9131ED100F67157B4729A135CF18E7811C94CDC8351563B02E0A75B24C3F7FC30A0F823E2C2219B0C83AAE9253E4DDD56FF3A825192B2632D8A4CEE5EC7A5C26026967630FC3A06053774E41BF7EAEF1172BA29A0EA2A978571FE26753D813F97EED592C37095ABBD67F5CBD3022B28891DC603359E1D059B118D136120880C1B81E0B83E52421992E267364E6C195AC1FC65E08F101C8B1764DD8BBA2A190E5E4A2086530185AC74360D3B57C72CB81440E54FA3FD31693522DF080BB252B8DB5B4D0BFBFBD7378D7BCF66ED2B6C2DF3EE7BF36CEEF9B6F17E4BFD537B7B606DB7F38E85129D79C1F7A0487FA282E32F24138966E0C57DB8FD583C5A70FEFEF3D7124E12B5B66EEA3C6DE23F548AD14C7AB3042FEBCCBDEA4D9FBBD64D1D42AADD570852B2D7292BBC57334889EAE6A4B7289369AE1284A7150B3605A664BEE34A703170764C22604ECD63245A70E3C462F80871DB42514BF4786E331B84A7B314FC10C11376680945034DFE6CF8DD722FF35DCC49D47F95E137CF80093CFBF36643B4DBD3743528E8B005DF3D5C0B4C6FF67A945CB912045106F35B4171EC21D8C8660F336B812B1623A45FB8E633E4CD72ACB92106AB885E08222F5268B998D870F0C2FDB851D2944DD0111AEC8B8872DFA8F8898278DC93EAC5C4890E150214FAACC657E1BD1409C4F51D7E024028CB353792E255C805A699092741B18BEB7B5CA9C1E97E89789131DA56432285225B1FE9BAE88232BC6C6555AD9B8DF26C51F3E12E6FD7639FDD2179BA5232601BF85F3A029424A1573D64C666B09D7E2D1AEE3D3F2C9D5FDB836105461825514DF2F9D4F5C085A4F250076B67CF58974F2B4E09D3924D73EE3B45BA4696D0E858106872F119668FD6E83CB36D4921DF89D517E33F8238EEAD273D6610BA1AC1ACCCA7AF876F29ABD43943BFB4B8F44C3181E0E4835B80C6DEAA8660474A95F13736243F71FD2D015F85742687F03C9CC6F2CD71DFA3899A04CA1789EFAF251FFEB90A4404178E2B699A970C17BB3E57015D670F604478BA4203EFCC737B71516C0BA651B200876686631AE34E49E5449B507224207EFAE2A1D49E33E488DEEEA85F0DAD79C96E26D6C224F50F63A6A7887B998AEB415F69AC5D93D5CB774950DF8DFB6F30753B1B8BA3A23308BACC40604ECC02A9AF101FA2A907D78D8D3E57D6DB67AA4EDB30815AED62F747AC15B43D21B66DA744CD19A4D03945BEBC3482BAD072BEBC88D6FEA009EB500DDE67E9CE31FDF35C5542227F3D9A479BF3E71D8B3844F88D7D0D80BFEAE6B06A260CD39A44C0E91A2951DAF74BBAA3A48089BDF0C327146F848B40AFEAF9801D9C06C302C4C9C138FBF38DF94367B55CD1AE5DF5F05FC8B9574C2DF59FAD674A85E47FF0D31DA6D2138D40A51ABDE36FF604BCFEBBFD641E1A7CD34C70AAF158C0A1EF483ED9AEE620FFBA7F49E4DC84AC3177937072BF4DE343FBE729C5D98BFD0E33790D33B4970EF27110A362EA5783A2588D8911C5EF2F2A83F3A2DD1A4EBF78A3ACF1A2D9FDC6C45DF9F99BB39D736D2EFF527A2B49B3D71357D9AE66BF3F6298372AA35142F266C83BE0266B4BB187BB82B6AD5C6CDDEFF110DDB21D4CFB77F86F81D05BE23D2A8616019B9D6B7624DA3B9E64AEB1516027CE66915BA644634C43698D89289FCED481AD3855402BA13B3AC37B6A627AF982FA1B49D62A86393D655B138F2B4C61024E5C920FD896945B53F5B96193EB6B49F78F4AF696F21765A2DDCB797420391221983178558D57DE56E35839060ED1113CE8AB9BFF7573586E7DB3A47173FA3069F9DC1A1DD5C0BB4859708B318D8AC668E99B6B9D2612F6222E69EE14A0E8B1297D81632A9F27E18CEC882DFDC3712E01785585D65DF71210B2811EF369E070DD2678B0F15978CEA1409E274958E4D0BAE4AEB18418C30F765017B94FF403AE55BF26D77FCD2996DACEB675F742ABD3274A1EA1F5F00A69834D59F1C6CB849B3B788FE09F69AE0FD91ADC800DC4C4FE70803437950C4495AD07E4905BAA5D732964EF98E787F0B33AFFABE113EB21C2EDD2D964AB009BBC707D2E47ED4891E8CC8E9AFB2D3AA62C7CB721F6B6FC0D2F910BEEEED9C17D5BB765EDEFE04A79CE453B591BCCF1B2EBE9AC08430A2AAE157B27DDB66E5E1B6FA7BBDD38748342429B563F88F6B8F56A48DC9AA578F1F35DB6A5778C8F7FBBD39DA4CAB1D0DCD4125C93F375B2000A03077431CCBA5442ED64B9C567155B99B3E40ABC052A19E7AEF368E9423CEB6DE339FEE0301F8E36544EB482815DF16B7B08446F8FA1CC8081461079BD67672D0B8E1B92EB83497C48153C37680263FCB41A344F4FDE9297782FA5A14CB1E2FA5132E27A36067C3F443B41BE2C21229E91AEBA0E961C7339D4D0C7D3E5F089CFB28EC88062A7F6F157C040DC9258F5449ECFEDBB97F9F0E05A5A35052BCCB6FA886F539A5E9746CC1D3F3BF739B224DD478876A13B15D22CB85F45CA7935A733B1AE92DDE42205EAD6C84397D5EC8C082B5349270BEE2AC661EF14340C50909D9BCA40FE7C939627310E35168C8BCCD1EA82B383AF777528E3BD88B71287893E32B1FDB6803E12156DA7F2A01A557B8D44FAA467F5402A9951E74CDA47D0571776C9284F537FF733E2FDD86DD64C93B64CB331599ED9ACA76F1B29FBE5DD92B01C817B6D21388EEC75B5000C1D827B7B42D9C6FF20BE88AA5DD344E2514312DD93CB91E2A07EA1E7B4461D1BDF084C8A5F64ECBA21AC5D33E59B2AE9E6A12775191DD200182CBEEC396356C7C2389AA41E29DF0DA30163992D3EF814726E6174E4DB2037F836ADBE99F16D136239584F4E50D018EA3FCAAE1DD329857DC49AD2AC6CB33AD426A7EFAF972FF9CCD2728E2AF599BEAF35052FBA7F8FB147FE44ABE88695150FBAB7004A64238F62CF8AC9573699BFED15516384A9C9D7E2512C22D4997B8515D4AFDD1201A5E67150F6F0C8D55D0B4487C8665662E6D79882DE700B9A8AAC2BE98CF0CB83717090A1A2DCA61577AC1D470E02E44297724E08A3E35AE125411F72C334C3A3ED621C2870B222D4CFD6A77EB638DB20BBD290D86B7212E04315F716CED87D4822B246669AA69DCBD0548604EB5B9B61C335B797A53ABBF3789B579C8676BE758B30636C661F0BF8D5A9FC44B259B3FC488361F7D106F87AC72450727172C252BE6C87B07B774FE5E28398C19E77622A515D3E0E3D755C172B2B4A817DCB6BD21FA6803590C06C83CEAC0B48D2D96D2FED23D33A4862658D1BCAC7C5C3C688395A2205403948E42BE5B444E3190219099910CA1DEB71DEF3CA110E744A5770F072DC790BB0BE13A90A16734BEE066C76C9FA07240468314F2723234D4831CCE4065B36275AFA798C1BFB192029382A923CC45C24B40D5AE9E3F96C222B244776072D2999AC7C9870C42F04D0B8CEE1DA96D29A2797BD65C6C9B96D7A6F6B6447B781E769C64F1CB515503BBE8EC7C7E6E1E36D5BC269E345EB52BEC8889C9A5200D708BEC5AE859DF42070EC0DF3E226B41820F1E4E9601289B65C02951F2BC9D2E880C374780D8B793986495EDF6E45FBB3115C9494CE7BBCA00E9CB4756E39DABE13AEDDF629F75C59310E2F7C7931F76D761122CA38D50AAD4F51C1E769606D1E130A5F26C61C3C4803723A6C39923939D30893480A434331C825160D584BF0389667517A81F5C45C5F5AC72445ADF646E25163BB0EACC8DADB3859486EEE68F5FB9B8144DE95B32242E9F943DB57DF6CC24BB3C406116711893CC173E6D5C4D76CF9A7CF5CD9BE4E33D8816E8A7C67E726C9E9F97AF9382C92D38A25776A359A600CDF7496ADCBB0EB6DCF3E87B9010C9D19ABB6A511ECF8A8A240F0CDD8977CEF58B75EC74BB1402F4465DD9EA0EF51DDECC6326CB4018008440737A578B078BFFD83840769139A748483600E590AE984BE0B9484A9B42DF9500971DC5C01CDF75614861BB8C6E5220F69D20EC505AE1F273054A37C177A2D68CFCE1C20F0E65E821CCDD7DC230A57B86E42086A9ED4F6791E03B058ABCE99929021B975F44D339A59404730F8A6F02DE07E9025C62A4AB12436C07EC50EB2B1DD22BE6C00F9B5720DC481006BAD76E73DB21F6B72FDBC6270C0BAC53477AD4561A0CEFC35EEB24775B80A10B80365C17CBEF3B7961EF109E0923C037A5D90AD7290FE21F760C315F18DF26D92145FA45153EE6A6C7A4E815B53A62205207509027CD9DDD484E2CB072FBF450C2B32990FFCD49ACC757830DE6B377B435B77BD406CEBB0051F4C45B02227AD797576E8489F6A95E705086BB9B53E0885C62A519078517E8E558FB322096786389DAB7F3DC873ED91F44F90062DA89DBAAFA2415435E22CE08A7138C7BA39B681321BAB11D144FB95267A8DFB6686139B4DAB4D322C3626562FD01C48E1E0B840549BB8A23DE0FB200A29611CE24CF1EEA19A85F8AFA26284736D7951509E036CED2418627A89352680A83B360EC6DC68F99A9EAA4C0DF5C715DD18E80C321FB76104804A9453CFC69F30D8C7B3354DF690735FD56422462D01240D73C0F4328F964B15C08A1342F4541142DCCA57058980E0D1AECF865A25A9CE3C6D6BA3C9E4F921CB62089AF780E1E923652374DEBD8DC6DA9B8A669848CB1843EC9E961FA0B2BB444145EDB33D1450E9798B09D09D110B4CE5B01A1EA5AB0A626968BEA0FA77DF6CE3227D8B862FF2FF08F49A8C67E5CBDD00CB208111F4B64620DC8B6E55EAD3C7A9B7BE02C835C3F2E5169F054759950CD9C6E922540C75223BB066F49196073E6773F154041CFD35C6AB5B368173EB72D62A401C597C5A5BDC65F034CDFDFDB7343E570527B2316BE395D35F6348D9FF65F2C39079082886339F5C338621F582AB1249479E557306FB2A9EE80ABA15BA600DEB3206A5148DDA514800FFF9E5FA5C8D3742C398C7DFC46BBCE913084C867737EA2629BB7F6E6C44512BE17BCC1C7083B5314CCE72C63B78A9EDD473A41B2A68C2CD585F36BAD9680ED5D92BF056C4542A63DA49B42081B3585E42EDFFA1BC5AB3EA4BE5E7CFDEDC3C86D2E73F2B96D2CB9A219624B6DD14F6C9E8B4F95AD9B4ABB98CF4DEDE07DC38377B730DE58038CC137B248CFC77753641F62F7347F9243E31D5B03312D33381691A5CF9CA26BEA019168F749558FD3D4C4DF0030813D8D4C2BFE3DE0641086760BB0910717D6C084FBE3FE9BFC806E96B7DDA17A1976CA0043C989525C7D3B36E2A4479742BE8D6285E88C4F6124A7CDB31CE9C1BC8E472F71F9B9C06F2A1E17DE105C6B696DBD21147AE06E06BFD9885E075A26668C3734C059A893EF0A980367564AED32702F6FC709B9455A99036A8B86C1183E160BF6C68975F1F60B7A62806C38AF4ED72B07C28CB3DF76289DD3D895C9B8838E80DCB15AF422E295E4F50135E7542D97B3BAED2B543BD645F38B18DCD508D518775F9263C7CCCA963ADEAD1E4DC04D1184AE04B64B37A9DFCDCC3E87493B4FD051D89798CB6C60FF9FF3B3D0A3A14F7E3AE7F785EEAE16C54C1EFCD9A2BCFCD5D61315F39CB3342FEA236E131735A70AE200A45121E12625520961757F4F6D4095F4C72FC003D2E0C25B3CD3B0D67ABD4662E7C7629E3A69FFB4E374436296FAF1DFDB2270D3D41765A3E26A7F4A70B079589C76288637E8628DEE7F24C27ACB94BE16EFD644B8CCE8E4E3435CC734CC72B61E20860A425BDA10510C4B42A7D0917B79FB82175EC09B2BB460788F885C7D1D9CFBB34704002B0416F32FD8AD4D6D3D1D4888140258745FFAED3330B415BB97B0638B2342A1DFFF200E994E3CAFB7169BD07B55949393DBD3AF76C1A7DC445A9FCC1228DD1BF6AA4DE332A4BAAF274C9E34D0CD296666D3D2EC61AD5EFC06548D8A3ED75E4443FB10383B2A2A0CC9BFE6AD785E5567FCE0D25E009C36F77918E28366712E3A74E71DB439E5016EA307EA7AB9E9295404F8121981FC5197D0F5F49D9622862CA3963DF5E417E5AC799BF36CF735BD13986D0C986F684642D654CE0E12C37EF4FA8436122162482B0250D7AC8D4153310C60F5DB0D8409DAFB7ABEA33AD0BBCACAA4245B95657F004186DD23C26DAEF0F19BF04BAAAD738FE8CDCA200E26B56CD2726D3E234B885A5898580082EF73C34111C4E938E324850B468CE55CEF70626815B45F28F8F46762478FE10AE3507A43BB12067560B111D334C13991F5D76FB776DFE554D11A75C2264373F1F4620741D03704A15CDCD41AD71B99DD95860A580F2584216F16297EC9EEE982F0DA563AA850F8A52B68247F0BA41698B64D4E0B45EA877D38CF022C3B123DBC38CD8A57B049726B2D36B4F070BEBD4ED574E594742A02BF32AB3C3F7F93A9FB7B4FDA28B7EC6524A5D3B7558F61443327EB0234F514589C4B2A72F6ADA5C23792C64203AF575A0122D540AB8842AF70712EA1799841382E0E1C1BF1F3F6025A0D011A3FC5B3FFDB6EAAE68C2DABFA6F7AFF53F392EA2A443382F8056A1DC5101A63E92D839A3C4DD2DFB96F920797D54D4E76EF3895162BF1098F844621CB479B0C84F2141B8C15C5C869397DF5C3402DC4DE00999BDA394295C8C4C0E48DB31F8C6056CABAE26DD64458C484A5E3A14761EA826117CE40E000CC557B7E3C898FF866CCB23617C8046051B6161392629D3BEBE0F087D77F13FE42192C3D8EBDECFA0FABC2B8D6D9CD06795C931CCD8448B8E62CD6FD77CA73A3D778EA4D900A6B5E838B63A3FDBE0151D2E2DDF4396EB39A319C6DD136FF0BB05D911670557635D8ECCB4120F16347528E9936985EB03172ABFA8198E3BF9B0B25060C7F98D8CF1D0135607F21C0142720A60C60B0E17E91938856F3E6D64230EEF4FD2077381D1C31F0D3468861571529221E618B70DBD8571BD8F912764C665EF6F35D9E63D7F0627CEFBC4C049FB37AF254908602A84E944AB5316C43CA9B0B8377CB970B251D49B40D37567E48A9E43E84DBBB9CF82ADA6D36261B609A2A9C74C9AEF89E53C1D7665087DC1B7E49F4BEBA874168BDDD4925EBD2AF9757F2D1AB78B5B1D0D4D8678C7C2CA3D373CA2EDE05B099362B0BC0844566325F5A23DA20E5091CB0F6BCC830DD439658933794D748B9444D0181318EECB07A95072C297C1B2D89B89A5BDE1E758087BD6915A7F6CFEEC92916B629D9AEF2462C0F221D0CF50B960346142C05D1699517C8B690B612BDBACD899BFCFC5F93853267726FBDE4780D76EDB01D945C06E68C36C25793247AE0E467D2DEEA330368E3D217C4310A9E95496A68E8C26DFD3C46B91B192BFF168A69DCD01E9C69080638D62A95B3D61328A92C823C7EA3B24B153D7E98130B68A911FE506E55EA8D13B9BE43DF272AD2413A108FFC1582831CE6AC0F5323B7E4B4DB02EF5316D5377C0784B27FADCEE462D6EC8C2F7DE9ECB42579B4CC73911FF9738B1943ECF4D60D2ABF1D6B947400AFA95D3F390E43263AEB32FA20AE7A681263A92E532D9853FE8BB484193654D85469686619FDD3D1F3EFD8BB5CCF83C7BDE45D1F3E22E889AF6983BAC80737376C6E8168175D08DF42B71A972D3E71D1DD677986045C14C2CDEC9EE0A34E1D3A0865244F471C58225220290C81A8115B6013138E7277B1C0C660A3946ADA8596C397318B9BDFCB8C6AC25DCF88D87389F7C192BAD7F9CD32EF5B6077C10DE212E9049C21CE53D3B7CB572C8F222EF7950EA5882C1D5CFFC2909160D3C812E20352FCBB2CBA3DACC01F1818B5AA9C87802160AA171052BC8EB0B2C3402560E182D1D6E81FB967C980C69D761C8A0E9B17C24283CCCD0EF3BBA1FDF33A5DD1D2B650C79B7624C720BEB9D1A4E840B7BF0ACD99A7791D02160A0C85EB2689156E1DB676A10B12DB76390C214F7F404C2060EB2B5AC8C8B90645A4DD28E883EE31BA1360E9423BD642A6C385D1210922AD77A645E0342562552274785F8C14C3459D08B5180E2608AEAA6F6C65111841EBBA66E65B232254FAEFFDD26AFF711446C81526F28D9D7A678F178AF227C2BBC814B1DC11901DF09F1E06B74C072E824DC9605211BDFB3724B55E265793D2F98DDD1D5DBCD4EB3F2B77DE1C964024848A744528CD8A1901E723B3076A75543A3BC27CFFFDD1E4AF13EB15ADF515BB4F9C5617F5B825109FF940A0C9B2D13F61A423B96414CCE06939E56F27F9AED3BB70F7EDED859A03C39F1DC09831CF036303EBC24C0EC4B6D27A9779257AC1AE137CFA2D2913855DC03F4B13FE7FFAFC4D8577C99A8663333228A8C34D6117F4DE65685FA59B4F29069448DBDAF1B655733869DF19D06A098B452EFC875DC7899DD70595C56B472B0DC2F6241AA21C6A7404296BEF57826CB6144DE8E9BE79A35ACDFF638ABA42352E7380EBDBA36A4D623BA598FD374355BCCAA3D331D7D18FFB0FA7C5BD25FFFA863B6792688716FBAE4900C5335AE3B631E707ABDAD87C433E5F6851813AECF3C65256422C09576F7BE4FD04D359E98E7A97886AA27FA0B75446E4500C1E73E46FE74D1ED7FFA99C4E0EC5FFC52D1A85AE6B53D10C7D153D96710439BE0241E483D5D1A3D082CA085AE5521F46CC17F816AB02F3D2D6B5166963E5AFE9CC8EA00D44A139C4410BBA8056D9A4D5DED2641E87A4DA06EFC4F8F7642F88B63B7913206889DAC49576681E704889BDF5DD87154C939B7AFC0E9F542B8CD331432A9388704193A028A24FB6903D28A02F6AF5F3164C2F1FCE92BD35A7BD444D4E1EE7F66153B3500484D80E9A5348375B81ACA4BEC440E7C08F142F494769D21CD2F94F3D9FF42BE3E7B2F0B901E26D9EC9AFCF90726E1FC73DB68A8F10799EA8F6F2A2E076E5BFF97178E1B7A0DA6D687AEAAAD7E73476AED9C557F126B12A965EC07EF78B18BFB02D3806593640F458B243B2EB37F56ADFE585475EB56DFE8EA962A99B18DCC50243F6B802B060EB6972EF33F726507AB3B4713F0E53025179714589845D286B0C767EEFE9533C5613805F4CE848816BE0669815FC2A08E342BF64F26B110A17F875750F84F4152D9EF4EE4A6823A7A5FEC0AEE9FE3E71E532E4084000EBFDEE4D62CF7E64D97E3BB5F0AE6A90E68275B493B17BD645EF6F93E602FCDCEB42F59A36030AE8D25C031DA78A1F40D397FF31CA63945F9815494B45F68410F57FE737A29D4662C81C5953D5C8110A312892D797F04A473BFD2F49E444E1E1D34CE49B56B4F84EFAB03A4EA77F3BDAF507A039269F234D9E74EC49BAEC210AD1935981652DDA1FA59EB5D1806C870175488FB018C9C6F13168B97029B567B9A12AA3134363D506C2E60F4923CA7139BB966DB1A488CC5E5C293C9C29BCB961A897D14E0F2618A02D2A858845FCC1CAA5F7D5A4C175DCEFBAC5B0B34246A5B0C0874FE0CDA82CC29AB6F3E5C8C313A6C618F8A74916DC64BA880A42F573B03BDE96C2B2283271F1E131F30E026BE3ED3480F409A9A3888A5A199EE41519C0D8122E18EF680E9D4038FE22E495FDD4E29B010C52C6E38F8C5EB36BF9AC5D356672B845EEB68100DF4B25227AF6265C42BF54190CCA0483CF2681985F626AAD2F37C983814BFDD6A56028CC041A9FF23DF2D5339D6B654F52AD26C049D6EDBE2A1C01DE2FE9938A7A53A87018006844A2E71BCDC551F667E3CA387177F8BECC097612F3E36B859D07BA09E7AB9BDCFFBB25C357DBAE196B2487336BF97C926B6E5DB7F8F92BD71D5B7C880119CA09266BAE6FB1FBC2EFA9D68503418CDE58DB14C61682362D987784B0E5E53B9F4209D3BF6CEC66500FCEBB84C5E79E3625453EFD18AED6CC0637C8CE79769ED60CDE0E48ECFBE9063A2F99A0C34750A06149E6BEEF04E7BC69C13DD94FC861F7286C7EDFBAC36BD012C1676D4C7DB86EF1FBEA05E9BE6913AA2C2C220A2198C7D110A795862E532C66E71819A1C5EE75C0FD4AFD223FC70F30D389D2715591284CD5E938951BE767F219016D902225DF4930B7D95019B200EE4F315C1252CBF9D0D7E7FF6749A331D7517BE98266389A3F36CCF224FBDDF19CBE92E3F1CA35E51894F51BAE7F18782AC64425033EDD15538B8B83B1666D22096B3268C38C6A52D923EED96526D02D8AC0F872109CD5B5C045ACA82732FD9CEEBB719EFD64D179735F4588105E98FFB8C2228E86E23D8BA96B7EA96F29306CE00F731CB985650277BD57E4F7B3D3928A93F68F5E7F57FB2F8CB5C72C48C705942DAB5932AB9AC63F0BBC666157BDD331B2032C31EE6B7F6DA382A1B21F5B1D6D8BF4BA31564FFBE08E9604F437D27C57C158BA0BFC47F7E9FFEC086A4CDF3D0340954C66D63DC4B4F56C5C58331C4BA04AB67E7D6285E7F8A0E00F6CF7A78CF1B5B45C0FC1AED07C22427313E9B2D92FD43BC12588871447F670ACBD26C219F9CDE40E37DBBBF8016BE4AD962A3548ABAB36C4FF22BC52DC18FD9EFD5B583490031D7BECEA8B83C3BD7D2B31C8313D415686EF791ACB2E42C5841E797837D9AA8B7741DC9CD0992FB27107900ECC50D9424BDDFDB44AB77B69504847E95D725D1DC4A59A9BF92D7C9606C4E75D741557B76AB8A2B1724AD4B355FE031959777BC058562AAE6FA310B9F6C5C5FE4E85D25B73BAD139C6240B38E2A4086EE575FC98AAE4784569DDF5065A080110E3842A2CB5B952D7A797C4B5C5833970AF6F417440B08B5C7F53A7ADA37C114E8C9BEA943D8116079399DDD50DA6FC53BE34340E6323154D1527A7941A393686028BFFDC64E891AE3D50B58D62F8183D230F8D17FD3F3FE6BC255329D0F2F6FCC880BD1845E06E26B9580A9FF2D48C244C6F6419E33A577933CBD111A37628E86419DC220E93A94DD1406D1957664105D9AA7C1E3CE1F47BE0737005E5C0ECAC76D836789E2557A4730FDA832E23ADF9FD2FF9A4831A7F28FB560D47A2E0DAA65E9F5BC491C5C35D606B4AC6F1B13648A2FDF6F13AD9B7DFFB885B782D91D2F9F962DEC07FB917626988568CD8E6959ED7721B38B9B16AB39A3AFC0E6ED6BD44AB1352DBF32E369BA8053BF0BE10CA8B7B7F1844F976B094BE340A12C67585F42E22289D50E83774B125618EC7A85546FA25A09EF305AC4F2EFF291810521A5F08963878BC03251DE97D0E132FF545BA25A5F0486F9E0D65C6DD1260B480F96BCF4E312A2465E38280E7353D7B9F9AFF50ECE83F2DC842A71972A9193C2FCDF79A621C1F95891E4626FBAA0A6BF488DB9429DCEE4E404E043B4F5D62B4CC741E006C597D09D80603C06D0DD5FE390F64797B18E7840B717AFECCA83FD19C44A6C3306391FFEA84058AB2EC8A40C1059C10F85731F45D7A4AAA70EBC34ECE02E8FC2E7F80D496E3A3B472CA6F4D7079BB1386BD88691E9FEBEE3B8653298C6376FEE0D77D55529EDDE44AAF34E96F61836AD4C6EFAD6ECDE9ACBA8E904B9508519D2A7221ACC3297B4C1B02C8495EE86BB3A3648F35796644043B2A157482FFFC5A318AB1B64C59A2D1D28079006BAB8911AB8034C0306B8E2E50AC12FFE492D8EBF62F31D5AFA84830C61AA9B5E26D8211D4CC105405B3C36951024081CA8701DEFBA7F995D31E2403AC87E5B86A319ADE88463D259550D44C4C6BA29866DBF05BD62E88B33401871E2CA3460EF87E7C17457E0278452A30FD8483C79D6158AF1ED06E6FAD27D64CB5BEF20F1EA86BEFB443C2D0B5A3DB428B0FFA20FB808DF245DFB3244A06C27E61801205040A75BC525135D1C327713454FFF2CD654F4AD60E600A467ADA304596E6C427BFCABC992038E1283BC7879D9D9EEDBE8DB70A234782DA230E4A9D80170F0FA85216CB49253D5E8F8C774EB8AEB8D003408F723EDFB29DD82B53249DB16CE7777C4ACCFF7EB8A65C26F2D4C3DE9DCE9E1EB791792B818A949616E57194C3A4513D386CE09D94431078BBD274B45EA9EBFAF7C6125FCB66E74A4C27DB6EDCABDE70F72A1CEBFC110B2A12FEE158D168BBDE90EEF668629B6FE8D0CBE2E7DDB3F4A999A7A477F4BE756D818CD8D0963D3ADBCD3CA5F3493BA6CD3C0F3F161ED7B7B1A43E98D9E2D4C1CA09CB626CC26593F537341D09F1A6B28831567D54220267B364ADF9143DCD9D6AF0BDEA1EA43C4AA098D6A415DAE5B769F996951F1CC57C108952693B3D006FA4923BDE4CF0A9D8EF62FF3D3E2248115CCFA5A56092C46EAE3B7F17505D85375804B207419AFE0C2B0AA40A09004EFAD7D68EF22BF90195D2DB5D96989E05B638E2DCB272078681851E3962B19565EA3F3773DB4CB7DD1CEA9E14B2124D44B298C19FB873C6B4AC91D77FDD3717A7153BC697A5076D08D313A12D2229AA3679F62168031835190272A1F0F04851468180E622A08782B392DAC570BB67F8000204952610232310E8A0B50D2FC2548905AF4EEA79BCE69F32F52F453953D5D8A8356F5E8CBFB54B4FDA8EAB710C8BA6C812633630679D42F5E824421A79E99539E908907CDC1FA5BD3BD20275221A0CCE5C552E2C30D6DC51B45F2C7FD6BABF95D7ADF5F792EC7825AB71F3764A9C40A9FF9F6BE00239A6554728CEEB74ACDC0ABFA7E368F1A4B335618DF4798997D24E0C98D7D4A6A82223C8F5EF33B6A1041FFD4C1C089B2054A6AC3C3F59A67DF349D03F7936225CBC622D7EDB7B1064C56D9FBA674FDF9534D1474B99DCB83951CA4568FEB174022F9B5A78E1C649424443B3ED0E0D9D0D67F285B83BEE459C8133EBC117607F0FA7913A61881919C3963ED1DF3CE24CB1D380EDF2A6606B3F34B1C9B765F6C1796295DE29644A8B37D06953BA629780DCDBD22953C368D112B35D9D7358891D238048EBED62448ED12B7ED672E2231BCE3F23DF85682BE2B10707B0838002FDA683C2B18C6488C7ACD1CA4E227D6F86FC47BA0CC108F1282C64E905964A78940101D40114C81DB93FEE068921E32275D5964E71B5ABE5C1F8C3F70DE7A30C9B14FE857000AA5CC190782D85E2EE110FE2712E09E5EA1530C52EADD8499BC5A010A4CB9EC57547C9F943EE48D48ABE5A59D1D5783B97A44579C181A21FE6E5BABDF61401CBF1ADAB35D42A9A17BCFAE8B759D300D4A9FE80DA497E23AC47E0E6100CC6271B464D82CAC7F6BE316C2614AA883BADA2B360127820229F7890642C11A3FE10307B5C9C63FF0293478AE2EBD9411DB873E56C19973F1E2EB295E13216169056AC3C159905CEBEB3438F6B45821DBB3B9EFD03FBB26D11498ACEE2424A2143860A5F45C5AE2BF7E2FF8C8F414D61B8AA1626CC11A1DD709BEE41A98C8336D4314F03CF10794DAE3F71F51A523F6A15011DC186580A464DB9D1CAA5CAE50A2CDEE55B68F6B353446B9111DBCE635AE253313346A1268DB023EB7E29542EFFCB346E5F58DDBF13FC6BE1AED1A5F19882B3EF60AE0C672BE916FDD97A07B8924481155EEF0F1B5315AFB4221808BB1874FE449E8F005287D095AD2C8E11E1DE5E8B7B91B3541E9078E91949E829C82ABCF05907215359459A089B4D966A7DB056A39E81F5B0C70FF9037C784D74D0D7F7D4097CAD0F303781550300629CE57F4404CE34378672903750F0037AE3182A9529021159103E9D2E009431801DFF5DE94A4CF041495C9D7B8E0A779832875DB149DDE1E1B6FF3204EF75191AE27AC2A5E6A62E1E4131E1A3D633F54290284057131A301CB1661F34AD1C7125CB6DC6D2B35E0E6D8204C82D7A0836FD485F1681DACCEBFBB7CC897FE026DF36FFB39CC344714D64BAA42B4DEA16FCA84EF319948A011DC4D4D8D1EA5DA5B9462B303820D6F5AA6835172B3D0EE0B9B3808ED237C50CC6B8B199E0F8270EE06273F52EA0C539E08508A1F4620932CDD069C20F1577D80EC9F3A15C668E47CAE90A233555B657EE6F0EBA1BFBC72A1C1605166B6FC2D616956A533A886F3F8118FF841A9482A09D26F9F30FCA4B88A724F3466FDE338B442BF1594CB5AE299594FA21E67464EEC4CB85885883018CA735F22B72C18CF2EBB1F64A0D868FDFFC4D698B429147ADDE872EA58D5DDD5670DC83B4135260FB0D5E65C325FA60DED80EE1543C63A3DABFFFD6B7A68D4BF5E4F53A0A728109FEA5B84B9C9BFBA9F2621DB1A874CC956604F73767D75F742DA2D6A9F46274664A9B7911518280C0ED51CB50AA5524E0EBFA328D1A82D217534128CA22366C652D7481D6B61C455F896FE48B562E92F0BE201E498E14556C6657B309FFADD78F414093D43B1675D75EFAE0B900065F0A213D3FF719EC15A39D8988C5566212719A3E7BC3C9BE2DEF872F1082282B64ED4857895A620655E0625C29C43AE2FCE59A31043908A2692110C45ABEB76ABEE451B5DB24A6AD108A75331F29D7B18158E8FDAAF880055BBD3AEC9D0CE5FB1F3F3DC6B0710F67B4CF11DA11084518425A0611A492774BA5C28558B5A16DF308F44CDE9C7BEB34985A8CA90A83F4FD5F6A915D12511BCDD0A307D894A0F360AC316618F61E5ADDA075A46BACA18D64276962D085062BBF1F8FABE31EADD990858317F1DC351ED79772DA1C5788C00DA3BCB8C08EE4A82B3ACEC33B214CD526AE6BF5EFE5E1F4DB818D93298F667BBCCF8B03ED4BBA5CD6FFE764D411F3E4F5DD95FD7109D953E3570E9AC863903D093B8FEFB0E29E0F0FF0C3F88637DAC14DE57A78DF4EFDFA8F8BB9CC3FA46AE9E348C3E075A6263A59CC11E94D08A6F9814CA5AAFAD27F171668C2E79AF11F6F71205756B80B7C896FF2416483A24F86E6625D3D036A1BC391C62443C6E4C833B1814265418BD8D06C4F368A24125906DDE9865A50217E0E2B3D5959742E7A2C7EB1512A9B50C90D11EAA85E3FFCA9A4D90EFB8C090FBF02BE04C8E53319395A538AB4E0B7672B7F92057D033AE21C2BB694B907746A8E0E4D35B6AB8422BB1FB9A05F98C4B81BD6456D68EB9CAD25D94BF597F7AB03ED3844F40CDC47A89B0D6D6C5ABFE313A6864B4C5550787A866B3EB5D199CD9A06754BAF6CF1BDBB2D7ED2B2CD112A28832552ED648F73FD582DA13720B20E4564595C8105907FE7911D8CE26409BA5B1E3C0598B7137056500E833F6B2C9C9CE5F6BA972BD6BF65319DC293405142146D2819D641ABB98E1B3D1B41366D7BFAD7F59200CB2C8969846D80646ADE746EC77D7D900F4BDE69A01634C2522AB0279DFA62C08CD813C4E1AB8C78E95CCFE369B77E031DB96B2E3E49A195F819A8DCA0F6D0C63DDD2BF3AAF104683819CDDE8EEF1203606B16DB5A564974AC4A144B0B24B3DF83838C015BED509636F74A789FDCF572FDCCE906AC3A59508AB012196B0882CB64EA945515C79BD55797159D22101E8968858EFBCCDF961A75BF13AD73D5EBD97B5ACC05FB374FA44030E457E945DA373C4BD066EDC52DD33B603DAAFCBF3C1FCAC26E92DBC27DAF25762F5FEA8BC0042340B4F0817A1A7E0AA353019546DD9487DA133F90658B1A8C711568270457EBC833223DDECF5BA3A517470EB0A7A7ADD9CF29E0E2D29C0D39825F982B80CD471394A67AC1DE9C47A925475578A23B732FFF7A4A8349F6B81315DF089CCBACDCFEF1B395DDCA82A21DF26425E1694E93189480614AAEBAA94A903A1A9BEFD198FE89331BBCDED4FA9B0D85062C0D42BF24D8F307C73FDD467D2C3626E09A249CC2A95604DB8F53FAC2A90092D1BB1B618882DA4F06643FF15C2B3BE6D8598F6935F4181EF7B30FED0471B8CA9DD7A8B606129E1D2DD1FC6E5BB9FAA693318F7E627C2AF80CF24F67A11F27E851D4623992F7099E075582CD13DBD29DA81CE808167E4F5EEE781FD041F369DDE1C847D9911A289A1C0C81BB38C279FCDC75434563D613A186E05676E067BB51072AE1070377BC1A8E54634876B1A87EAE57B6E54EFDABA465B5BC8F3B08F62D05E0AC7CDED8E25CFF920684729961961673D44D4CFD8CC3E4EAA650FAD81F0FA41695B666E782299C8951D98D40ADBFE0B2ABB20506E3B11C8FE1BDAFF12C0EAC70E754232A0753D69B155494233EAA01FB222F35AD6004A824D4B8E673183E70DA8C6593B353FEA535F6C22AAF3ED97054A74298B7430520982E646F8ECB1307BC81F600C367A86373AD1CEAEAFDE4DDA3721DB5AD4A53BBD38F54B42CD03D5B34589C31FE146CA5674BD839FC37117AE32277DD726428630601C0380250EE1825E85A7529B19744A2BC617C3B51EC368728AFD37ED24D709E0CCDE7F45E9DC2A8D67BE0D6285DB75739DBA70C865FE92F9B9E38A4650B09F19FBE544BBF85738715E3AB1DD50F371768EE8FF7D48794936F0FA1ACED34F93068200FBC4ED2889B83130A0569EBFBD079A2A30C16C304AAD442914BAAFFA1B4ED1F215F2101C6E64B562C1B90F3EAA584433566B15F85FFCF7FB2E9BC18EB65C897B9B08B99F560B18884D4A4B5050E5CA6B44E0F1370830C6BFD28A9BE22A1BA13E5929E08625E4D1C1E03DC74265F8ED076F157FECB5FC74749832CC8F62BEE905111A0E58B693DDA4CEB8DEB82092A4E818FBD8911F9113F546F188A5BD4E603AFA5021EAC8E96B7EF0FB20EAD383565D377B0F95E67B34BBE1E120F3FD15FAA344124AF1644D381F0CD829BD965DFEA6EAEB18658101C7592F7AE67F1FC39DD7A0F0A6448847180FB540E40697162A91947287A3139A1D662EBD1D6D84B31D7A8CFFF99433957F4254A6598A5FF01671EE68DCD2924A64108769F7236FEC67369172D990E17261ABA4660A46B99CDCACB88169F4AED8B2EECD6B570651B5F2D52E98CC3B644B5CEC7348A5D5CFAD56F20F50D56B0B9BBC5ED67C9742463CA2F67EDB4DE11AAF86DC593C1800E5A928BD9ABB663B3B1FCC298481AB610468BE28782E4E99B35B1DFE654776A33B101ED42D220FDF46A1ACDB9BA6F1012A3C904D194FD68429D8E8DC8C6AF2652D0AE41CF31B979D8B428EE5878F190694245F8944253B0C7F9B95299AA497CDEAC6A7BAB2B62B73D9CDD335918B88B5E635110DE1998FA87347AD37A8CB90742975D095720F7BCAFE077FBEE941A805F65CC1D2993A81BD08B435E07279EF874EC71A9E0EB1C223D1FD8CE188B188A0BC97F18F6F612198767AF3E492B8511A669AEBC5ACEF5CA697B600793348EBE124F3C4E563C651E216B6BD594E3954F1CFC2938A0DA83708709B371F067EBE0698623E116C6AD2A6E92B76D892A78BB2B945B3D9644770C3E8B2F4288E39E23E897116DC7B02B29FCF1767A07666AC16A59D96E932F45A3737C3B4E34DC711C226507336341A699F220794BBCF43F2F85CA157688F3A857E277C049CDEE2019C20302DB5CF510A2B16FE9657DDA3A731E2F9A7F9EA906F37322391814A404DCB4F22E786A8B9291EB27387934A0E42C2C0E8B16E19F09773148383890082627C58CFD6117776318B451A4937B109FE2A99F6963A4EE4FB1C0756C77A65BF90568A684E864760C5D83335928C32C52B18D353014C60E628FAD479E3C7BFE609F52C75DA0577FE07822DD316E71E36BCBC2A9190BB376CAD8DC0ADDECD19A4F00B1266B5D9A0577FCCBE8B2A291024B841EEC1D557D8D132623E065AEF2F345A8637B98B829C3C829CC366EC203F178BAB620E50D385D5B6A166E2423D694B199D80F6305A0F774A028A2787FBA1545350BDF3741D1A93A9E5FEE3F75C36C84CC876145FB50729A714FD9CAE1D3728A730A7E503B14EFD7C8D0327E4889361E5AFB112B2A6F37AB767CD672667277F7D9A5FFD1602CDDE4BB3EDA0C2EE1025FF20283DFBF9E9270E78D587AF84E03D3E3F664CAF3AD7CBE341357E8AE55D253A2BC206D256E75D531BB39E0FB44FDDD420857EA121A0F564AB2B0B09F0ED03CCD7E5AA28C5E1F92BC7E5B5628EECE630B3E97EBF8121E6B528349CFBA473AC04499F89F551D7CFAFD8EAD067EDC3F281C19A40CD68412E75E63E184856A3EB392A684BFE54205D8D83BDE77393DA4F7085DA165C2754522C8EE05B5193DB3A0F72C39C4B68D2192C7A4679E934D82B9319C988261240F1AE1BA97072037A780BD0FEB31E69351146120A8F583183FAA098D12CBB4A554E185F1C57D90A54C2C09339D332D9F95289D3B7EACE9D435382D7C2D0ED9B8CD7C7FE82B1D28C370B5CBB3D9A4B4E3AF5959BC765ABBDCF2B7DD99D8DE794C07647539D5DF44D1F773DAE156B11CA683FE91D114C0A2D9065EBD5907D4C4F34ADF62098BD69A341A3A546F55FCC8CBB9307BE2A64DFF770AA093E14EB7F3E87E5620DEA6C5435F14C8B583A9749598A1CF4867689EC485C61FD220ABB513774C62AF2830648DB0544DF1196FB682681C8D09B43C590EECA202D442EBCFC328733AD37D772325F7F72BF7B18D38A90913BB616E12021850C296184E7267F5DB6E1DDBCF922BD480135E4FAEE80476641C2FA4C4F6DCFFA2852DB2AC84B70EEF1A520F2EBB1A6388686D5A1C1A9EFAFF2ED3D7B3F7202782B1B0A85C45EF18F40C448190EF9C366ED7B11D5ECCD62690F3328A5B8A45C13B96DC35A98752D50D2910E63DA52590236B80FDEF88564CA51D9FFB86900F78CFB5AF253B5A3E4F6062A50F22103FCD12AD6481AD95AAE97A988BC47EAA196D9C16E0A12D1607D6EAED62A7E0C3EE497A180E54C39F8DE674AC701D35B987ADD0409EAEBB63665F08A8E340C29EE6ED08ACC650FCC26181E337B659B0A56118DF83F4345CDE8E7A900170A46AB2C4340A8D1640E7D545B2F3B338D58012204EE9780AB36EAF2B6A9FFD39B05C93751974C3FD88501543D9E3ED0C7A2F43B5B9B6C6725DECE03BC4119A24A0F9850615B447DD998D1D25705E4D54C287DDF09D0ED27ED52EF7836133886D0840AB7AF9BE58C1E1A72AE6375E9437665E2CE60FBA6B11089D2D9F471EEBE7558C8E312F43D0653AF80D9B565DD29AE7AF73C4DE4F252D8C330E7D2A9A4C34FE011EDBD09328A8F5712B800A53F2DE878DB88EA09349200B3A044C655B1FA375F3EE1D1D0374B3D67970BFEB6738B55AEF9EF4F8CD2D314B64C323C582CECF0D3F1FFFA49F8A3A3355A9BC2ADC132DD9EA23ACCDCB5528195AA61808A2459B20AFB5E22D129BE9B5AB8AC5C0334557ECE0324279234B54D49D93255B4B778FE88E7A0FF4F3509E4EC692A65FAA55B8B309009BA8DC2E654EAA594C39087A6DA9B29D3A82A8E3E9C9D97425615014FB354CC519B1E5DEF86162A29C835BFBA1AFFA06958C2037A0441F9D6E318F46CB0078717B701ED6A88DD405F3EAD0371AE511CC64F28AE3985B76F4357A04B92576F848B65A600F91830241609DC59B689F086A08350CF2EB0C4AEF65FBB82E1432901C009C648003DD64A8413B61D73AF1ED713FAF06B7315272A6246672D665FDF90008C29CF18CEDA72B82F60E52F37D3161C0D051BCF2FEC64F0EBAF710374AC5336934BBC8531AF774E42D341E59437F0E3AF68039941034C71A6576D86AFB6429E83277561DE5E99AEFF577D8740A953887610F696D581C49413BA0D6C3056CB2B3C3A0C1888A47451CC57BD1479C86C7EFF837C73973380AB91094EB94A7FA8BE68CE28254DACA765644EFFCCF8C8FB031620A2130CB650F29EBF34149CD44CDC8A61E28ED887D10C0C7E75DF9F588022AC2C4C40A8AA404FCFBDE3E5547193FA0D128174D0562001E6E34C31AB5F74C4FA187B09DD71035EBCDC01244B29E25AA996931536C643805D6532414EB295D6D8317DB6BEDC3A135D066AE4E16EBDD81B97858BBE297D99207FEFA2D3BAF6E9030E4A0D6C141909F140FD357BDE5FF781B8BBB7F2D608164724592FDA0F6374A3431F4BA0AAECA0E1EC1956BA4947A0A4475D4CBBAEB7717CD0B8541EB570962588F78C30311211E962E13388C66F3E8945F5DCC9D7CC2A7519E4B1C806342A236BAF6FFAE724B9D0D4C82E4BF877BF9AE8FA68A20DD757FB1CA545CC61B668F396A0B98B3FA1DBCCDCB64D6047D53CCC2303A2B1E1C14D9B76A1764962B49AA482A9C5A7C505201DEA3CDFAF63C69904510547AC0F75B5C7668E3A29C5ECD2D2F44BB8564C60305661B7EDD96B99EB4978E4AEE706B3F70232A6F55331F48FA5E28053B74DCEECF6B446E08DD7B62AFEB53901FAB52D9BE4B19ABC2E9CB2F3D84FC4EC6983E0D68AEC479DD0545B59BF8AFF23DFEFCE1481F2227B848D71F590C0298ADD20F82F9589106D879725B51404A9E6280D7896E767F420F721F98190921C3A690FC2CF60EC66498747F49027F50923236A401D0CAE120BB581C11D5FD2A711F6B7A3E19B0BFDC8813260827C72FB6985E066B3C29AD6B4F2EF0EAE34CDE4499B952FF15916BBE95DD424360EA1F9BF55C72A126C236569AE7B480CC58E20B695052B647946C4F0A98985589EA6EF566451409D7E80D9DA23ED7401E478A4BCF915353B33EF8E1BD54787898E17185B99EA43E5624184993190F14545727C32D8F96BD6BB3D4C9C46A7EDFACFE496B53549C05D421D27E008D17114C146B25E7B4A9DF6A2BEFBAC5C91D7E89DB5C3AA8A7B02AD00F6F6819FFCCFF14671E59530E37821BF81AC4A704EA019A77C88F043C1E2E74BFA5BD5EF85F01DFA9B0D165127F6D079216F8228F2CCFBEF746A7905D1D6E69C5D8204EB0F194118ED5AF1740630275408BB57B2B82C49706DF6C9D6EEDFA1F18A1F89F72DE5E7094E46A150A591DFA414D76ACBF776737D29FC5F358491FC3C1509EEF71663BC7C16C30D722D20063B2E6111686F3FA2D69CFFD6447E943E962EFBC8F8B080CFB01F96F1D5C18D6B2CFE6287A674850821CBADE11EFF785AAAD62AFD0CFC7B79A1CC3A035BD4AB8CE4FD75CF3B353B859C967B7DCD97D095E42374F96AEA82B51D00870010B00A0D0E919618EACAACF962196E167810C57F7C4CE92E0AC43E8170529E807DE4CBAFD6DF08C532511C02BEE687A75E7F51DF2AEDE100EB403D037C444153D0F50CFFF508E327D64364A8A0F14C2930429F59F940326A9D6600E73803B165CD080A4DF4458F0CEC66AB741DDB4153EBA18BB4EA5B16166BCA46B05B32EFB762077BF553A1AFC5CA0E17EB2DD236187D49285A464789CB61C8E4E1E532F10E77413500FDB79CC422E8CF1DF776C25FA71E2CF3CF33CBA4016B514EF49EC25DCB7D80F05BC8FF9F7F8CF177125190112C8BE7CC4BD36ACDB76557F4C5DD890D5CF1ADB2A0CEAA2CA33BF6C32975E50797BC6EBD126A20B18846FE1E66C00EA5877C3143007C2EC0402E4F9AC0412488F558CC4A66517AB74B5510141EAADE879F4DCEAAF7A5F8D1D6BE4AE9C0A0268F88CE8D74938AFC56A8983936FDFC704113CF7C03FB601D09235971E56EB8E3E7EAD2C14F53BDB22962F5669C27E3A057FD3C87417688D46BF0A651225D4C97BF6094B825B6D5C9F50699F62B9C6C093AEB9B4925F405D8C1FC19024ED2FE8CD37772351C4A52E957F72242B2A796BBD84DB07C3F29EE0BBAF04D4F03D81C797938CB6C1260828494AD5C497F19C6743C0A07500DFB95B25B1195D0386415F9D031DD870331ECADA3FAA26F33E5478C8DB7C38C931741E99204A54040A44270ED5ACB80107E057B4839D8E16036A22D3EAC33E64E091797CE5DA38C66F3BE19A8FC21492C5AFE572DBAD06BC32B7E3204C1170321A819DD8839FB73FFF7EBAD409F5FEADC8770B619D6E603E7C6BE7F5D094C4FF458CFFA9BCA3BA6A2AD4E8219DEE371F425A58ADD2A606CF3123FC7FBCA54407D64464B10707A35EB9D42E3C9E5FFDBC6D300A8BF47077944CC9906A6D66A0C342F9B113430467B9E3D5779CAEBB5F21BB221EDD8C61D7E91F54AD88065B5117A066C331CDE3FAE38DB490480F607E55E68F567946C06EBBF0DF5CAEB518681A5823B58C8A11708FB923EAD7620041B944017A2BBF8FB04884F6A68D177E981C6DD10F7BE426122E93CABC0F16CD1BCD0D4768F2355883BE3D98CBFB3E9F933E91229812D77AC624FECF9F9DA7FE2FF1F30B28EBCAFBE6A811ACF934D30CC1F1C8E2762D46AD4FF1CD152A3B18D7F09BB75A5AB31976AF8EBCF0C35811C488C62A93AEC349397F888129A4646431B3B1A1A5B543B7BDBC457362EBA1C4527FBF9DDAFFE689129E21D8D47304D3A2DE38B76CBB2466259EC04BD9B72CFA38A43A5A1D0580310A95DEDCE7CD2C8C573A0F7A0C6F1562F115641674B2CDD348A4EAA1055CA05C4ECE547A12805D7F8F2CDCAA9B97F9792C13FCBAE2C2E939C69EA93A92A90E725F0E4D40E0F49073BBFC02FCB5193AD00180FE0D5B9D885F19E431049D985B4AC5921E337E528E472B72051D8754B7EEC6121CA509D3C7FED00D836B083952A836FB56B3CE1CFF1D5B4AF84C4D68C248F7FCCAE187D5D6453EDACBC1E4C971B911C7D98D0A951138E03A3B2B5F949FEB674BF033A3AC6C5814C30A33966166DB0589D7392E421C8020E10B4C2E506F3B3CF6D0095AEF3CEFAC595B12A7557B711D8BD2B79968A4838D581F84BDFFC21B850487D5C461CB8843D3EDA3EC2C7F100A3D12608BAA293AF4FE1C4AE8EDBC92E9C3634B3688C2AF5BEC9E7C3D8FC1CFC13B863F6E67D406200C7FFC6633ABFB03B43FFEC31607B053B5EBA94E797EB56173D4D5D28C68947ABE203B584E7038D7F01A8DE6A31EFB27AE87D21B222758201BBF6D4D0D20916E89CB793FDA83582BC16E211D638C2EF54B9B9746CB4C0A7A0C48AE38629D5EE07207556AE82C7C3708A5786319FD0CB0F3D0603768FC8D2B1E8A4711AFE851C8F51F8C33CC7E558C91724EDB2D7CE0CEDA750591F9C230F1FCB7B4D5CD7BAF0C74E4059A61D8BFC7010BBB161D70935D0F063D01041FBF0EE90C283A78B99B6E05795ECE31583C666F7756C3FC374F7AF283762740A7B0B1F063B89D84BC7E4E01869039AC4EA83173DD992DB861115FC35AF9D6D2A7D3D978EBA765FD43EF857CDB2E65CB51EF90717AB704AD58F17D9ECF954818657A70B6430C8BFBD20EF27FC567274DAACFA0237E7DBA1455692B61751D520930813CEFDFFB9A8C59A2AA9287D237ECDF1FBAF77FD199178EC90C5D0EAE60A9F88D7AB72F738868405AE288BCD8BF5404A73EF6AF08142F8263FD4895251BC0914177647CBCB31E9A4F92C2F19D2EC381E0E5B40573E058B09F04BDC3FEB3395A2B82E9D45D7673F932D3600FFB38191A39B49F80D9EC5BAEFC1C829060C76AB125A5F1099000BBA83A9E933754A4136039D7D56388B7105834B3060C4AA4ABA5F05BF4068E3C3E6268AEF47F52AFD77A1F38BD7F72C1DB1856CEF2FCD9E4482AD573F8BA0F2725FA6CC6A571474B58F4168CB48B2CEE1DBB60AE246D8A17046F5DA4BC430A913FB5FD11947F0573FD970CAF8AE6126690F1E9FBACB49EF0EC1F15CC688FEF09ED34FF257A71EBDD19C11E1843703479B60622CB31EC77629A2A0048C01357FE732E751DB5BCBAA02AB372868FF45E6BD84B7FAD0B3A02720F6A9DA560BB9DAE05018D47B016A6265C25EC1BB39CF94B071F769D5846BB5BA6E7C755763160115E4CB543FD2AFAB507C22B537B131D93612E248B973BE5FCE8E6DAB365EA013AD4B42474F512E06BA7EDBB4210B624B3F7AA73C7CBF9AAA45247F94239F132D0DC39718F0FA640952B8B19D154559181A0E8E8F9022FFFBC4253F1420000892B297E0AFAE7713B0F7A2A8EC077A3CE842C7D93A7A70C389FE9FC9375CB0A9F30469195706999B9EF5CC802419B54435E275E46EC4442E7ADC4ADC5F4C12CB7F5D2685ACF07C30DD1F7E54014458BB6589B2682428E37B667AA59B9D26B33EFDB0DC9994DA9D9450512974FF55EEE040A243FB926C39B39C731DF15B728FF6359FCE45E0CB756C8CF659705059102AC1688A4A8FA164B371B1E77691C3956732DB4C328BD38F59E466C0675F684694B51E11B9A565764BF2AFD0AF856977CF9DAB9FA90232EFC455A66D4C663B28FED0B2FAC76C047D7D2EECC71376979143B666CE6027368FBA5603F745AF75565F8C050BC87651B22939A65A4AB460379688FC6FFDFA9A2D608A54CFDD2771CF2BD740A245A6C80CC6FB086A80906748F45FE4E1BCA95E35F3FEDA84D9212B481FE27BE07719936AB4968C25C1FE29C3CC683E15B00EC7E0A196C1AC7592C73D15D3CA4DE2ECD61C928DA6EBB2F8DEF6B65F4AAAF1DE36695B8E29245A1AB239D1F637C584E0B37FAB4FB14D7A4675BAD72B8E71467259464B6D4B64F4F3F2A215D6FB62203160B6DD7094351DCFFDD97B00BD8932C808B14F699AF0B795F1877F34FBE1B5449A644243B811B215E3F304752DD14786812243DD033CEF222AEB72CDC249FBA2AB109DDAA9FB25E8B425F1B72D67D7581DE6D70FC6CE7194DCDE5835134A8066BD5341C0D1583E1AA555B05BFCA3A6F16AF6A5E7E1C192748249944B504D0E9197E1DF3BB1433CF8D3BA4F3051E159BA9C25A5A3AA6B7F39B014A4E079C465A97071D756F9D510BB01627662EDCA146BD09799D1AF1916934D18A0DC76A059C4A13F0301355F192D38297C3DCE603817AAC28A9142A511CCEA0D44E3BD73ADFF265243440E47B41064AFF1C02A4A9DAB87DAA1DA5FDA29AF91A0965F94199A1742982B9BD3771E6F7272C84A42F5DDACD0BE67494F9F03B431FD2D12C7CB0DC62B787213CAA671FD65E04FA96F48D95A82BC7E09D54E3E4C2970731B4B8FFCE44DDF4B77CC51CA5E9F5AC1ECD70DC44886EFDF12DBCB59376D634F2D31B137EF5FE83840949E88F79C8E7D56C8BBA8556B3DEFF4596A9ECC3A646B166504DCAA3DC36988B8973118FCC47B70B7D60449F3F989D49B01DA8D7963881B716131508D152C19FE499BB5120DBAF23B6B575BD808BA2D31B026BE0472906104FCF64F0180A78BC9B54CF8D2C142E36B6EB453CBF7BFE73BF8E61754D901637F05AE05E4A8BA7D14A07467A28F35C4DEDFB08877860304A3FF5A4D14AD42521D137CF75E8CAC5D9240AA87685C67EB1FD44684B018DA1DF6DF553ED1B61582D9BDDC3273E3110AEF3084D34960CE507D8583EA41EDACF2C7026D2342CAF6165874C1D4E0668F91EC0DA71A641786B542A85E274C16BBAF346A0B1E95D2F0FDC8B650E819828DB3C056A6E1D76D143417B78E88FD25DBFA0C7007371D860A75A922A31EABB28363FD03B126DB21274DEF53CBB348C53BBCE3352C33941CC015FD594C2BA2E412B074ECAB36F74E0B8C7888657748228CE7E4237862BF1052BF8E06EB7057995B2F4E8A93BA4961014E1B68F182DE2D9DF2C58FA9BDE52B421687C0DAFD21459D8FC36FCE4503C0CD4912B425EEE90BC2F8BD3E3D77E01955A6BDF29FBDD2C401856D8D2A7E94CD4E0061297B70AF0E9AE5C6DB7A1BC9E59D6455D259703AB1B71EDAFAF2C696E708DCC341CBAEB63F55F74112F379C78A8CFA17CB67973674D7CCA5B2F5C400B6CE58C9138AAE64EBF627AF6AD71A48F06F5FB90F21489526BF5BE4E894599ACA3F7E71549F32125DA19C8D822A7F05DD34C88A99135475943C6F9B07F00EA521F0292F87B6C43C50EA9252A077F482CBDED9BDB76F4BDD5B602CFA026BDC9649A06996DE68528F89F2787A6426106A8CED4FA033A376D12CB3E34D8AE844C3F7689F60D9DC5FC3E402F70ACD5758EB6EDED67905CABB5D56F01CA0274F221A21DDD30557CB7F072DC253EFF9762829483C2AA91D407E97B0CF1D4429772F55F7FAC04928DAED535D96236749108B7EFDBAAF4B583F4B1425E86F5F9FC3089B762B606ECE777E8C17C83F3FABECAD089F5021117CF54DEB8B6945CB524B508CA7A3AE957E7FC494A0206845BEE1DD19A5069325FE48901769C6F5F29694C14B897FDED96A4FDB461B330A490F2791DC68D8B1D10237CC94043713757AD3337731E6575432A995B6761123D78959ACF77DFF293D3F9564A559D70A9BC0FB57D133884EF4F79F3AB8D517566D34A480D0BF6197A7E6D506EBF8E522B4F33599429C08A6A29400493B0DC39FCDBB1B3282A601E89419C1F937B6DAD0D3384C7CB5E34935E1FD8492BBDAFE94B9FF771C597265C17A2F3377914485E9CEEE7206BAA4184100BACBDA599877FB50B77E065E4D603AA40AA34306D3664BB1A71CEFBE1D75F23BDD081E5A2039FAB6FFA15DA0D012EE105CED14BB1A69EBAA1F9747AD507B1B0B85D17935C629CFB17648E1A45F813D727ADC238F5D1590210A77F946EB3D008C941A9C1CD3AABE2BAE6A4D1D4ED92603A3BC6276D2A90758A9BFC0F15FB1C036173CECC142357B857EF33025B2A4F418159DFFB73733E8A2D54209037F1217228886996C290FCFE144142B1A806F4F9AB6A0F7D56F20686551FD44F60267A27F827E46A9D7C2CBFE9470D35B4897C89FB37DE93D862B0F4F4EEDECBE0C11529ECF74A22E89F70EBEA88E4C9A58DFB34AB2C4D97B76F7651BB69BBCFF91CDA6F6D1E169BA5348BC70F0255E422D6ED99A293163970E42BD8264BE8DBBFC79798F9EB49E5A7EF96A5F1C9DECD7BDDE7429AE8F98B496D4BE2E8D36C274091150977375EDB46707E1A7713ABA629E8D31CFE07E3ADEA89EBD7F95B0E5B8DBEF70B4A8C47C2395DF5347AE04FD8467F7B7ED1B80AC8A12B2D31FE69DAE4C076BE36E42C4FF30608F70EB09BDE40315D98317451F25162CFEB495A611D8EA21FAEBDF0405D836FDEADB7735792BE694D77E42923682120292D9479E0160280EA3CA4CCA55FDE88D5B04B040892FECD5EFE566604A0E639E6E61A1CF3EDB762748686FBBB469665AEDDA6A63308876E760F55C557F412D78AAA31445AD5D6BCD96A4FE37E44DA85CB3D4FE854B1F0C31765A85FCADDDD4478614154C36AC532BFDA4BE5DDEED5E96F811A7CE6CFE03CAA65DF029C13C9406ED920B35357E4DC9C9F61F2B35D6F2A564869B1C3DA8C63BD11D18EFCB264B271746D69DA243F6B71BD16CA8F246451A76E9BE2A784FDCAA714E5830DADDC65DCF1090A710E505E0CC99C0F5149A2CEA7B6F78965FDBAAEDCF5D6AF0E5F120CC015E6B84319C703D990A3D2B959E1810B1EFD49B7F3EDFBB10B3E0C0DC605E5C81D37127E77B6C459179E290BD2E36613F11221AD931CFD79CEA61B4BA7DAD13E2C2B686847ECA6FD2C1F967C51CDE8E08EE9713BAC97EDD2CA8C682178F5455D3D580316EDF348A39591692A0D17F461EC52FA6329EB27BE3C084A010543DD6F82FE9CC42E42E35ECC0249FB2015ED271D2CC4E91032EA45626FEF9682C079D532D780962F583AB39BFC4B7DB5C188056A1503C9B8D077F0CBD7579B0CDECA0AA1A1FFC2C87657F8C863C3E2A4BEFA18AA124CC869427F765C0062359E3287B264DD321DB36F43BA3E664296A9D5B46F1D01E1CAE5DF7B6071483F398D6620518648ECA21554B1CDD146B4C7E75FFB507A38B15AACB2E425DA0CA5D13FE567FBED003B35DAAAC003F58AB8B2E05FE6CCD7BE2063E9D11F3DDF5F881DCADF08D439446450DF2B89FA10401FA05F8A7FC56D09E5DF970216C7FAA33CC10BBF66C703BA32A63B6B9325B81CBE74B767B5C1E703BED90AA634CCB77684D8AF612C0EEA3D12352800C3991E36C55C3CBB3976BCA05B3DC96CBE540E8AD7C3DAE9617B55456077BCF9710FFE290801290188C7F3E74157EAA39C3ED132620F456531362A9AC6AFAE9D4F8CE51AB8E05649C73BB94F2965D2520B23048BD8B001B786CB3E24FAC42274DA19EC94BCEBF479E7B43040D45A8EB97C0FFCD6DBD9260D252D27C6A9E6A21BF36CC1544C1930548AEA897940AF87836596AAFA827549ED196B935C287DB3BF8ABF985CBDB55367F0470E301F40279BE1214975969C9FBB483F7C2B20C35E6A1B3289F0FF701DFC506686392E2ACCB589295822E0D679E297D6D0AD40C8C80E93F235AD239B1A7C5690F717ECE9717B7761F293C823BD92ED4586112BB0E7B83BEF70C76D75A7781FD824FD398106224E8CBC27FBD88EE0FB244D00B7F66F6AC7037D60E9865E22BB923744D459A024CE15C2B444A18D591993E44E7CD9DAC248002CBA49B2F435E34160BE773440301F9FE448C52C1C860D198ACFF6DB83AE2660E2714BFA70C97377B926C75621310C28BCC84EC80BF62A4A05B6072E13894504A712ADD85699DAFB157B7C322EBF8A304A4096A6DFA33CFF316E91E5E28C55E548AA3F428C7881EC9F44702F4946443A31FD681E919259FF756EE487573356AB0A71EC553F9C03BE9089EA9FDC6C0AE5B2AF029C0FA6E7315B00B1B39F484D125234103E533A58459BB84E9FBF7EF7B0BA0743116AF7DA405A49074C2D18D2DDE6184C913014F8B1C887175A638637CF85C70F68D5FCE686F6B3ADC73F370138200868E2DE29ED4C21B4A41AA6F9A0CA892B5F25B6B7CA9746289A86FA6A0E3E2879D3388689042057B9EBF5373F5001B950EB3DA866AE959A4D60518FE478570E7AC0D974DC6EA9BCF538960D276D72F8FAB3B037C9EA8C78E1F8576A9B0C41F41112B8327140A712346FD43C66E89C8B4EF6B13043A99747F16FE0C81720CD3EEEDB2D8F32CEE22E2FD389495CE3A4D93D48EE1EF4CAD73C27E2C8CD6A345E53B6E61C91387AE10 \ No newline at end of file diff --git a/assets/resources/subghz/assets/nice_flor_s_tx b/assets/resources/subghz/assets/nice_flor_s_tx index a98dcb07..e69de29b 100644 --- a/assets/resources/subghz/assets/nice_flor_s_tx +++ b/assets/resources/subghz/assets/nice_flor_s_tx @@ -1,6 +0,0 @@ -Filetype: Flipper SubGhz Keystore RAW File -Version: 0 -Encryption: 1 -IV: 47 69 6D 6D 65 20 74 68 65 63 6F 6F 6B 69 65 73 -Encrypt_data: RAW -E3F630A0B38CFFF2282A106DE0BE1052A6A289D3504D92973305D5585B89F2266C3C65900856482523D7F43BF7EA8F0A343E09A4AAD2AD07729F82EC4568B8C8C93A7ADA287B34680AC809A2CEDABC0E5D6E2C45329B479D8534DF7FE06413892F5CCE0848B287FB1A86B11C067F696D49FA92877E75AEB699C176E60571DF927AB75E26AB2A70BC81FB79DB6FF766737C204EB7F836125801F97908E4DEFAADAFC7264B7AC66A4E3B83C17CAAFE1F25F40172A40E20501DBAC45EB588E31C53BF52F0E56DD9035D8DAA423A96EB3F475500BCF42E23E1872A57FEC2756B276F6323D27F3C5670E3B31A399599828CB9AF50CB9EF8453A9D9B2755127197E16BDCA32FF706B6FF899AA4C7E0DBD6E40B0940B59E1BC94E26536565B2034C3D6DB3BE9C2AFA9A88755D69FEAD528288893048395F2048C8C9A126BC4613B357DAE3BA68EB6C0329906026F2C4E751BB449C7320ACE61F018FF12112E0DB70B5C38C84951064D2327B93B30AF30D0C5CFC58D2CFD0726D3B8C4DF929911B0AA16C661B020309E0C6FCDAC520891769C4F52F33238ED9100513DE8735AFA1D60595175D8FB25BFC296C71B4A7F07DDD866E1F1CE498DF31F2B2832B3A885E379592AE5DABD32786DB2938741C3A669787A5F5BC6D1BF5AE8BA4B90353C8A4C29400CB14A0852DA6B84A1B70A8C0DF6D0DD9826771ECEFBF3D7F0F5273A809AEAD79D004DCA60922ACEF9ACC2DA690CF1E2BDA496AF9E93283B2B8610FDEB48402F29C29C6013E23108191D32CD26F9A1143DC2EA97CE731A290E1E0C9801130C37EBAF8C72F286DC1462714A1F8B9B62C3F8BE9C9B75471971337BF0AA09DC21B06F6C905000FDFBB9CB4FF64E86DCB9C08343F20196EEF9227995F9BB94B081852F00E769B1E0437D9DA3E12B720E703748D6461198CF84D28C3DA24C8285AB1283451359621F10E355C2FC563E8FA6C9609A0197A89D27BA97BA0C19188A86013EC800FF2D32282C7A4C88C61F1D0A1C84E33E5CB04F47518311DF5A75A600FC2FADD5BB2FAFA53E48EC68FB703021F19EAED761E55604ED46C99D2AF5D73D7998DB54819219BE5ED4882B04FC7939332F5D9DE78FD4CEAC40329B93AA2F3E2936FA7442AC61CB53C6436E67A965EB6287DDC9FA574A9A808F836191B6C2C1012CA8AF1DB3A901161714955470F9472622AEEA3C74FA22168C0D6888E36E96CE4D8BD215C0DAB748634D69BEB642305B33D070CB0D68D909B174F18B1EA9A957646342DDB48110CCE5BC3C7F175D2BAB25360C9F20E4706188703290FFD3631EFCBC67763A3F209C5931EA6D6C852B970705F066B5A5987F850AE8CB2E362679BB77FF319ACAF90DC6E1783E53E1A2086CA4EFFC86B020D348F36B0A348956C86F9B835293F0F59436A7833F5A4EFA61E914962A77B64A8EBFA2511870F475C4415BDEF63C961424B3628E3A537AA6528C8DCE65850FD57384F0211310D8C367247239E1DB35E54DCE6EA9E9357146017A592DD22C923A817F52B130201B69918EDA06FF5B073445208C940BD6111D3CF8730F4A9EDEC97089005BB51DA32B1A9E70CFD5F29FB03E4B0AC3D5C7C954BAF2391A318B942DFFF9243E32395F7594E9C8AE374AFCD8BC2C3837DC389BE916FBFA610B7DDB2045C3650CC321E8F9EA06B2E9C1536E2AE684D765A693C11169B795043BE07533E6D36AFD693F54F281425F00DC84B14169F82582941731309B1F4321BF365999F5479671066CCB75F65417148E0D45077178A239CC29E7DCC379025EFDE72C313FF693E5BFBF9491F138631A29A2A1EEBF51E842F534A18B955C4D1E284EDC9D59A723709D4C1B73A46D032994309BEB7126602180794EE75178313016EE6162EC196FEFB6F8F87DE4D8781DEDEB719846C6063D1DD2DEC26D8331C3623D1D96565994D96A4F453200E96F7CE0AB7D1BB17319B75F64E3AE9DFCD0A734AA6590094282695E5BF5E8A1E556D2F21AB12E8A16274FE6F1842788DBD33233A0D12B3C5BE416BB1B012F3AA8EB737F94B3B5DB347952202B86BB25ED3601C300172788347F749C183473B21007CCB131CE57BCA30426C56D82DEEF5FFEBC7E941E432C8BB56B1BD9057ECF2F08E702267EEEC408848455F30D7447F0C020CD273B2DDE8E8750FC2313B3CC41125BD44AE125BD53B48C731C0CC4AFBD391574F8AF4CC74B4BFD26635DAB915DACB78534ADDE515B1555D5979007C896AFEB5F9FDA645B64974B715DC6A015162A178AF37AB2755B4CF4A2C3BD7F73D6462C737EE6F4224179853CF7BB62A0A773A4952CF091BA1D5803887EFDDABD777065D2E1FC923E86FA43E1F367FEC620E2EF86B0EDC6016DCB2E0412B09CC8503B996E072566CCE06BD072209C21AEDBC74EF92E4E504C0B36F2F481F484C4FBDBAEFEF0E5157CEF59007B6867E10BA0DCEFF62DCA4A46034B2646156041DCAE4090A6A77C8202709589EF6D26B228FBA9841ED8EC3A154A84974A2BBACB3A9E62600F23E59E8AF9C68B448142BD02F2A0F221EED4BDECE73BBCD744187E6245DF45C43D5C072E848FC529B477E31D686247987CA8DD543C855637E1AC2DEE18DEDEFEDCA0C0E20A62982CCD7182E91BDB2BF2F30E27E0EF71EE6E7C13B03E9FA69567454E4668966BB6AE3F802CCC86C818D4A845CBE16E9199BB7ED08277FD47A3F8CD3474024AD642625C17FF56A51E9C3CD389A048DAAAE8CD65641D530F7C9AFC0B04407172FD1201F22EEE4299EFD1C60CE19EAC7B77D0AF49B823FBF877E53BF8F96916DB4F804F33D3E9EBD7D7459DEC96F1C96BC9D21B1FF8A2B8CA394B809B42C272EDE38C410EF38707CDA8B0857B28E094E4FEF1C227EA2E0659C2E69CC4326D41B62ADC48A22765E6429B28113D1837E2FB8FDAC806FE82F24DBA29FA7D37D1A08B7A11E8A63FA1B46753076B4DFAD0782B39AEF5993C57C4A4F9F484B2BD9B25BACE838251537A02F16FF721CC115B53637758726EE9F1D53E21AB0251B4EBE7C730BB0DF2ACABCD4083AF3FB1D8949DA6E6F835B225B92FF470A9B56BA0BE7AD37699A41CB05075027C272970E05F8EB885FF507909C52466CA80C6A922AFCE16D6B046624BB11F6300608D1D07F722251670743911C4EBA07BEFCA587B09E795FC9014E045325B3717CAF8C5BCC2DBA811DA624BF72267B9795337CFC23BEFB2BD57B4972EF5F4B2AA2AEB5DE5F20A1972D0B06814C78F0FCC8D63AFC4055694625CAA17815A41ADDD5D8F11C155524FE12C9DAFC34912B43B2F83FCD6E103CAAB8CD31D05094245C51D1830F071AF426ACA76D8834DC1466DF3422B1D4008708904372402FD8098FDCDD2145D2E46A3B4FB4C79C0FB5B6535AE888DA607B8EB54B9CD752B4274375FF6F2FEBF66EEC7D1CB3D7BC95BAC1534B81E864C14F9D32D216796ED84510B09F630B7055E83D262C8E7AAC9ABA8955DCBAAF60C191A23405623E82916E49A31423F8523B5CB270E4E0E5461CA333F9389650976E3C37B7BFD1D88D79E38F83063A5C3EA57DC55D08551E9733F438CE5002FF5256CC44D1B235A99E399A8B688207710A8D4CF726EB20DD833BA5DF5B079DEA644DC9164B945218F214F71EBD4EBD46016CC0377EB511E257383D0AD88171C2C5714601E0048E7755F7B9CB9F534FDBDAD52AD9BF8094F2F020B4EA568FE49007C7D4AE576A3DD46C3C7970BC07A2B2C6A908C2D4ACC7795BA7B4357B854D5B5CD7F38288934A79A7BA6CFDC5F3EEA2B765349B8EF5FE52E15893A796D9D3D0543FB9216293808C08FAA921142697AE55A3637404D7185E1116D4673097CF187AAB8B9C42D56584F47760DA41AB0B5579C67D8E014EF55B7DA318430D0C2B62EEEB4697E507860EB1EA05C7A287BA27AE06AC23CD0BD8C596DAB1CAB40B4B6E665C9624A7DA04BE7987F6BC691DEFD7E4C1E9C269A4B34F1FECCD92B7F4B4391D6055AD5AAE308E379787FE9ABB23A655AC8FEBE706C4977425D6A30CE65AEB13A85BDC45AFAC0296B2D5EA388ED2A3D74227E7DE68639441B40B376FC083798F1B26EDF19697300D8AE2DF082F2869344D6E0881B69A6353CA1196DA341E7A8935B68DC729318EE290AB4FAD33BA01EEC7B840A4978B9900B4DB7A7B828FFFB1CA1BD01E881474B0A1EF6A37C7FB7FE2403F133F20C4F7606CB3AA0172CC1024F1945D671433623254B5AC2469923043F3A28D4830BC898B77C6D2A6C412F8AB5AF708AA999E1C852FEB85F60F897EE01077314077345DD017A95CADD4F978A731FA9779748E0EB9B6B2CD6A9F35076A34207F2989FA33F18F04D9F15DB8986907900372C363F0CF73FF217B6FB82D226B15F1446EEA2C7EDD358CAFF2481A31D162EBC659FFC55F55E4A3F1141D6F735696B7322F238AC3DC009C2EF7EBD419BBAD2255DE498904527406E99B60C43B85B7B405CBEA0F7B0FE34E567F49BCC8AD4E1E10771320ED91F278A191AC771AF19DD0BEE3C2200877E2FDCB38EEEE6BFDC723A81BB478E3FFD1D12D6205CC7DA553CCA16C0B857E372C5B765416C303E68326B2B9B727A356DAE6C5713ECA2A483628A99B52269E673597C4F8A01272EDC6971EEA19B13B96F3E843B82FCFD2C95FA154882CCD0EFE2E35DBB5C2BE6B75D12D2988497A2FE762772905E65EADDBE9271665DC730214324EC823994369F97C2FE4EBE266095DCB310D21D73EDE56332D459F5A110C1C55EC9A4A77A9045416E618AB0538162D095D024DF0738A5CFE9FDD023910D6D8F9CF526AC5FBACED4B43362F08B1928A53D12581133D1F9D2D2B9ABC19209A6764D37A559D246588C45941039757A984BCE9D86F2407876EFD6B72C9DC896592A59342ECFCE526152B5DA6F41E4232BBB27668505821FC499D935AB4A4C7FE678C0E4BA8421996021647FF1EE268DEF4DC1715895D8478F9B7B305541824D3996F3B4763A809822350966A3467EF5DC0FA0247F413CE94DD0DECC4603DD171182CE621DD35A8326D5A7E80CA6A6CAEEFF529BD83A380B7CDAD100AC6587A11DB36E4EF1F2F102C5B2F52072E062A8D72334094812A8DF61A614A650E0BE652C155DDAE3E7CD943F13350A95256AE206C94C9E7D5240E537943A772909D2966A541F5C8B7E08FE90BEC85D11B9988E931B181CFD4257951AB9E54DF8643DD9DC2E786B7B9745B3BE89D4EFC291DAB338ADA0BEFD8D0EBBF23ADB320A8260F5932B5E95F00770F742AF457567F7409C49BD5E81D5308E4A5188E2EB67C15A7F6B597DC46DF0C14B60724B25328B819DA68C37B94A06C1A7EB1A03660D56BB12FA5E43C16FEB4E4DBA3567A27A1D9A2FA0D3F88168A04B958197DC7A42B0A812485A59FE7837AA1DE994F879959532DC6A32861C954D72856C690BEF48E8C8C6B637E6803299AAA27C75809A9597EF07A352ED106C33AF6EBD12797D9DE076180A78EAF5330E9C475E6D9D07A2EED757B87898EF5B51FBDAA9ED1E8B5EFD765997F1B4A80E6178FD72933E9B8923F7A2DA4053E5A297F7EAD938C5CE46FA0AD5524576A35A876FD19BBBF18E80456C2F058D4215D16C7AA6AFAC6D57F82B43B067E7A268B6CE62836045ABD62EB8DD0C6CF1CE8D5ACC566FC4A4E4C3839E247439DA30281500FE146EA3024941B10417DDCDD2CF7A57184A81F2898C34EA0CD556601C661DDE85BE1F7A1D88A9CA73DAB343B05022FF5DA41728738E29AFB6412C0B1F9AB78BE9CE81CC0491C07A7B349FA47CC5E2D447E43BEA81671369B0B5305C954782C70B03536553E40300A4D558B21536907B5501F53FC3F2866C9F07F6629FD623FE92A0008F85E0218320FBD71BA710ABDD9B9DF16584F2A5013264F26DC693F106D2E7BFDA98855C9DA648542407A56078C93155E13F6CDF32CF281A24BC2E03011BE8CCA0E9ECC675D3279C29855612AC21D1E040EBEAE074FADDD95AE127590377042AFC716E085C1A19B80723F7C3209C1C8EC7381733879B56E72A304C11EA0287BCC619F82D8CC53604C37C2645DD7FDAC6763EE8F164B43C73D4C397F7F46EBA5DCC24F39A149BCE336659327DEEE063C88915104758E78794E5A5CCF589A5E2EF52C0EF7D64D12CF7F837BCF88E7A188D104FDD44186D55135C7A6E1503301F6F28728C40DDDC9E90537BA9531A807B8C5BDB1E0FBA630BAF8F94216DBBBBC6E7DE713B8D40A6DC25087345810D57A594D663318756645983D55F681FAC95AA1689D2629D785BE3692872A6078962ABF16B51478A5E329B9C2C3BD149B741DD9AD64928CAFC432D40562F713E94AEBA04692414C76C9EC07773FC69A77D9551722A4F2B7683FC09F04607A40D234C80F563A4117710026E467FF87256CD8F4290CFE4F0825B96375827297BA33CB2C229CBB9B71A78F930C251039815D8D65C341134335478F351C8FBDB1B4D9731093FEFA973982CC51DB07BB3EA226036A6037A7C412A86E546034945C8BB3ADDE5333DDDA5296A99A554AA0190D0C49CAA1C9A659DB30AFB28EB54C8273F9D4DDFD8D2BB89E30DCC4508341A1857C43977BA76CA51D14F89167C5C38CFC827A017EBF833C41DEC47D8FADBA44B9A934A4090071305E3D6BBBDA767FD7C218286795D60E68833C3C48B0555DF14AD3868166B6808FAEC59EA59610ED6A7F44B53D3583CD83C0AE2DAF2333DFEF3C8B13565C1A5A32C51CBBC007B6ADC919EAB3D2C35872EB7F51C736AD16F568FA1967136FEFC492346A192FA78C8288A0A8902410C21AC973BD70F12BDC9E4D743D51AE46AF5EE4FBB3558C4D9A2D114D0BC5276963566D45A6FE7204C120FC4F91710E96BE5680F92B4AA29621127B1313647186547FB685969650165256E254F972240C0CEECA1AA74BF55C2416E703730D04C6C7B4691AACA78F9796705F68BE2A35BDBAA07A9B2F72FDB6D39283AB0FFB7FEFEDD153F4B4A32BD48B56594CCC2F8D8121979B4D01D67CA75DE74940538D48BCC1BBAA6BE7B4B767C1728F8E593FE425B6518CA7BA0FA5CB1DB7E0A7B83ED5DC1F0656DB49CB81ACC6375F287CF456C9D905146472B55D0547C856E897C068331D6809BCD3C6680AD27C50E1E4AA28319CDEE6E333549E474B6DAF27772501A8E6466724C8663C353DC6ADA196A60ABFD65AB4F954A0E36DAD3F9986025B2981432326F95E7242246BB08DC8DCEDA683E5AE1D934A7735A79E23E9C56B1C28D43EB33C134A892D1C722A7C6A006EF898F903BEFEFF617D0F0D634D832D6EEE9917979FCECD43FE293F5A1EF4C6937B63C227300882E8776A67435C148BF16358EA9FFDC9C97043293DEEE0FD61F7C971B2456361064CE23046C3EECAD7DCBEE3A4E8120F7BF7CF84F8798B9F498EA352FA467BB0A38672F92C95F22458ADCA319B9CC2D1C01631F6FC2962F6FEEA8D0DF95D40289DBC6E4738C58C98CA57CA2E9D400D19660922CCA2CD8FD7E438AE92F0D26B0F7B25A9E46555D7D2933E1694166F35A4D2733D3985BB46BAFC3169E178276EE7C05E4FA26C41C2F868E10B0C66B33327A81DBABA4130EF066CF889061DCF97736C8DD6F5B7915123B45B9FB7A89A7867C334D957F5B9A8BE8ACFC3BE057A503FBFDB7A47DFF452E57347B66B3ED599D62C1969020244B786175A0D7AD7A35827AA7049AD116318AE25EE56503FD62626F17693C2913F4E85340592A8B83D0752D5CD1EC9E0BC25C60211AE1F49138E457C4FB6B231C2833541A12B368BDB103695F7DAA7A4B2EE6ACD31DA878C20E758011C1068EF4FAD172D82703C2FB8F2D157DA335CC327EE19652E1DF95FA9A0013F912E204EBD9BE01D2DFA5DD3591C532B33247C6394B7EDD6F6F113FA63F1FD3047F954C4EFEA01E2C6A896A26BC85B0383594F214F51BFED361A09BDFA4458BE90006B9097BFF4EA08E96B2D607F78C08C3C744AB778C06500BAA8D598613B92F86A0243F70F5E72B7B3F2823BB837B341C1DCDAD0689CA01826CAB784F75E191BFAF54DA67AC04BBAD3F2E6FC61F80B4A781C515B8E551106E2F64A17FD5B1EE1B235AE53E45D59C0383DDD9453F2E6B9FEDFF33FEBABA134F26474B28F263225359C2DCAAD6200A2708490F5CFA38241626F2D7E2FE82E1D2C933A6F250F2AC4FBF5F54F6A6B863DE8F8ADD79EFC112DFD76A280F83E2F17D6CCEC71D944E324AABD43FF7AAAF75585273877233F1563BCEABD677BAB8C714E8CAC5295B023F0BBC3C4032E7CDDB0276420C0C03DCF3EBA72804E1EF422BC5E91B507851B2AB6096D682424B516CF5114A908AC0FD5E291CD846C98CF180E1E46ACDD71EFA6D2C9230DF51DEAF9BD79F759CC97C06ACB6D22311CFD1133373D1E31B3B5F7EEAA50AFCFBC6112F873F9D253477B12A5D6236C32534B7F2BD46D0B660B5DB45359B5BC3BEB62F88A6BD6D187AF38AFA9204E78D4FEA0A469DBE5AB8F502901D04577A5126C2746B6641517537822261A90B8403A83550542D40625D58B9262CE9747912F18C88550BAD545BF1381BADD0DED9605214A5CA4A137B25A045449A4256F70D3C37A66658888015B7D68818BFB35DECB58E1AD3A1C35E190871E6EBB5523CA91FB6EF61C83C33DA99D36E9E17B1B64C58639763A6024121EDD90F42EDD8B833A9676CBA7A6C0C20A356E9E29125D3E17545EA0BF8F05179FF901CEE11BA2577B9365CD00665A5317FF9DAD1150D361132148F4E0B7B4CA2644B1C382FEEA9831E0A296AE7C73413885E7B751CEB5F541E540A69B0207730CAE4F275985436811563B73C3BA93ED0A40A726C43C2A4A0B9AF05C025DF0FDE3758235DBFFB1801378557466B613C411A33FCB325243E814E964BB9165C04E40FB1F03E40A86A20DB9D33F0A533423E51CF273A789AA80828DADD5FA3FC4EB527A80E941E84D05E22C3EC461B82A360472A674D035D82CC0ED279229F45A098FC993128540BC420506CD628B1E5732703301FBF4E871696D524B5A8CDA9F4A70117711688455C54472D5FD3360D3B5C7DB7324F05B897CD1DF721744490D2878F4613E643C0015F0C870F325EE821FA13E3F08D42B26F51C56D19953053E7287E5A6F41BBDEAF70D06C31DC80CF9739568B0A6B938203601D28C102EF82B651422D1475139669E2E06ECEAFF94EB494C156FBFBC9AD7C7F9427BEF95C101BC2D9EF04F45BFC7870B0386792FD7AE551E4C7642F425DA82473007421F8557369F0BD647E4D47226F51A4C280D0A6640A5D87943D14E5C744CEB7305E86A84339554EE0D10F22EF015CEEB24CA368F34F4950BC8B2496C66C47FD36235D4853859D0246B371430FAF2BEC053BD1A4116003FFC053118CF2C19809EBE6E54D95468CA049815F2B550402D0B1B3ED0595B6D3BB30DDD1DFD4611E1339CDB37BBC263499C0CD45069C4964F951639E1B3D483C8A93A12EDC57ACFEA7738B2A37C7850FF387DB411AB3DFCAB701616CD2AFE95D9A94F85B3F57F8D8731FFA2210CBA4A26B37C05AD28DE368DEA70F7D21F0E187E4E0650BA4CB0EA13EF34FCFFC5283E2417533A781CE3FCF62B5484CA16EEC6D452E7D4D933490AB0F994BCDA468B435E10D4CA68CB8FD6B7A440655F55F6B3E9F737D6BE8E7987C7B02B5388187B07A43C1BF96161524B5D8DB37B766BC638A918DC0F2E2C824FAFCADD43A1007EDA49C6FBC0BC0D0AC73CD7E1058B804028ED3E4B33606E7BC8B2B330CA9E1919E08E1FAF011E2EBBF7674BCABE4E4CA599EB00EA33640EBFF823E6B051AF46CF6296B14D9770D129BA32AA14827A3FED7EE8B6513A7CBD3F5A441234A9A03425B75383801C0F9D62F649B69D13A860E98290488148DE91329CBE31219B19A1A56E0FD59D74879AFC30442FABF8AF40D73A0EFEEFEE41F995ECB715C6B69AEDA0D1ECD29C35DEA7550CE0BCBC52FD87279A24ECFBF2DE0F1162B7E524FF86CE35BA2A4652ED53E29D54A63A5D72DC62A88F611E5056E2481169410EE612F81F82DA5F9EBC54B7E3BBCF0E7055C83E14DE102E7DCE29D873EA6ECA825D8513F45BE3A092A9C0D23BE3DCC6CA70FF0494DEEBCFA3B122D3A3A95906B4CD70E6C051D8CB2A0AEC67387BC8D6EBA28887EC2CBC89D0177F8FC78CDE7EF48413B956EC170F5E7BB8D6F4E115556FBBEEFB258037920FAC8400BCD497F82B02A637C1E56BF7AD9CF9F6FEA23BFC88E72CADD97F76F01C0169B50F682695D1F0C981A970B092B24CDBAA8BC3BD7CC4E389FDDAE573F82D8A4A50FCCE425024B148D179DB2D2BA032A91847C3D30D32E5F87BCA478CB7F4158F1ABFD6735EEB036D9518896DA47D04AB0B692E892DBEB5E6DF71BF6199C91359A6618040A1BD5D318AD005D58B25437637802B3FA21ED33EE80A7D1742C7EFBC8A10CB8B27894C200105353E463F44A5A3F22EFEA2067DF16AADE72142CC427065439768F2BD0522B1B2FB3B6E84DD720ACDA2907E25FCEAFE677DE94B9B9482128D3A9C9BAEB6E626808E0ED68793E7D9F7B9939FF53544A64D3B64738BB3F49CECEDF80839437A10D401A77EF936BA2AAA365B98604321C591D0775AB95622DA3F9CE45B2AAEA1E309F027FFEE9A7D29502F2B00FBAF8FD429B3A619504F781FB8D3F138BD95FAE157D8B9A72F74FBDA2BCE38E27091B4C96E445BCA2873025FDFF5EBCA57F6A706106B5D5AFB873A3936E28BDE378353183FFB2878E27AC7701C3DE744D5D5E14C5B178A96ED15987599DC47B2A5AEEA40B470207D234A6C1B4647A7903E77487F94A4FC9C06FCDECF3F89CD0F61BF7F6E268C797597685DFB6EAF83340ACBE453F1D023BD6A9C6BECDB1D78D5D7BDAD5578DA4259AD83D0C180ACAB00963D58CA856B22B4B4921D11DF4B39F7EF26236083F8949A4CCF81B3E63EA0F4F7C5F585C41131027B60EA777BB5FC2391633D7425A4589E5DA514F4481CA86EEB466E4DCDCE486CA94655842B64546BA41DCD4F5ACE93ABE0AD27EB54D980A9C9FCDAB905F360CF9EE695E56942934B71F0227EABAEA423D4ED7D5FB731574BC4CA6448BBE1B6E311A119977E04F4BBE3411256159A7E9A42DAB737E0C06FCD46484A3B9DA9617EAA7AAE1F2218D630C8CC0F82AA4E418ADD1D1D6655957C16B83AC181B5E83B3D2415F13CD266E7CF8A1F47B010EC1D659E2DF891A1A4E78E3A58F3B4483FEC4C61312985E42AB83FA4DB3777E2670AADAED60C043BC6FF2034590309F9EC2252DC672FCFAD05DD9D7D796EDB7DBED69AC13B595B0F5993976A941C608CAEFF7837884D0D0C359A56A8A0EC407FF59C7F40DE9F8866264598269A724B0C576BEFDF0C57CB3DCD82F37F4CAF9FDE3FF29C26C15F9C0BA9FAAFC8CA5EC77224394523A2779BA6501D74CAE46C18129623929133DB7F773E3AB498A9EFE8C33AEA19B9874E6A0EB9E6E53F09730C54E688D94D0B7399BA105AA2AD9324924CF02F286A39319326689665A46EA16D64A544D6351CB98B7CA7EB518B64007D87EA14CAFF5F01C06C939DB2390394AA9BCDD417572EFE8B7F85CAF472C7AE2BCB623AE19DBCA6E3797F1CE5A2BCEF6AF02EF3365326FE0EB500F4821948E548EC6F53067C0729501260F6DA7F778A0D6CC4E47F7C6536AA5826B928405A491B9D47C1C83EF2B8B22E759BCEC610133CD9F95AD35737FAD9204CA46818FB12CF409584670035F6273970DC91DD68A1A2AD68ABF968EA84F0E0360572F1182D50DE7B462D7B833E259E2D5E96F95B2D64A74E46E00F54515C4EA3115088D7E42995D2F1C1A7ABE8C4629F1B3872230F464F002BACF2971FD1386DE2DF94C58C0C57B29AE0032FF8746DE14A3B8ED4EA1E49F6A57DF08032C9A92DFD0B6067510379A12919D3D203DF034B122C6F44591290FFCDAD0E4FC2B3A162119A5F20FAB27C49F025714BA41522462F8EB94CF5CC8489645A7617FF8D71A7CF0F40005AC24EE25E6B71CD56FB4CBFE776ECBFBE8A93891D071A760EF8BB9091539AEBB91DBBE797A1C061B3A93AF45CE6315B5628F677BD6823D2DC0E62E11FB39736D601F1C4DBAABFC08925DC7B407412F4907D675B0C0DEB6466E1F184E53DE9C6F5B76E89C6DE606F75FA6B3672E594B4F1E2B5964C730430C1BA8DEA040F18A43865F845EA34DC5C8E04B9DD30AAA67F11C6B7D5025D38061192D8F8C1C3A9E9681B360DD390518E5F21A6C8B9CE042799908CF6927071B328DC535841BAD1F003CDF7B36F9CE6B9CFB2A95F6468F38C57F5DA4159493C32CAEA4E85933279CE2ED6279E61493D75D69209E02F44F25D0F8A1E5EBBA896AB306D1C18407D1FF4B7CE9296566D09ED280FEE33F558829E442E07370774BFE75C69AAE2E1B37B2D93CB4BEE21F5D857591972606EBB6CAE03C0A23AB14593DDE050839A95263B7C405ADC4DAE730E95B41D6E26D84BBCBFC042947C50794783F596787126D173106FE11F39AC11C852027F8E027BC58B7B3A4A493308874AE368C69A892093A9267E3662FAC1E74D1261C4D05DBA63AE3537770B6C6FA701952F9F6AD2F4BAF4322E2EA051F3B34FBA93F68D690621E717EEDF4E3088639DE7C2B2E196CE1E635B90DD4A7FB71151E9451F8CD8E9D0D76F5B6DE0E6FAF28BC6A9917FD83B0883C0AF2EE0EF582CB6975FEBF76743F84A2F7522EB2243647166427C84EA85194B526B0BD7599B61CB8D5211619DBB9DDCEB9221567E86116FB913024F58704B76394E738223C72F65469C15CB7600CD14F3788728B093AA50C15E0EEF34D85CEC9BFE99E61FFDDE792E3C9C271C562DEE4DFAA38E21AE86F656D3EDBEA4D4C9B8E2FEC190761FBBBA699804E62E31D8971399B111F1CCCC1C6E7EBC490E2A51C6DC9B32F661935472B9FE7BF0271B2FED155B6682352D3FC2052891D2E8217928AA4B55F8B8B1065C6ED2269F5FC88B7EC826BD6973770085EAA6051F5D49C606DA4363ABE963B549F8A1B89380B0977970E42EF56459163747A95920F14CD52F2D0FF4FF397FB68E6AF6E6349C9355CA9EBBE24F333BA46031FF06865B8837D924007FC52731C88AAB98A2C065BAC7267CDBC192CE118E17D0C25475C64961311E31F77F4955AED1A1581C6FD695D9B27B107E2D82CEC6A9B590D51468BBED29EDDD1BBC106B0C9BBE5369A7D4A476D6947DF9C1BF7B078B618E147FFF55937A1F6ECCC6C710620D5F73AD6E9F5DEF92BC20F43B111999093F09C57DC430F3B897E6574E3819F6A59E7551BA44702CA887A8B429CA12F3D4D1DBD1D18AF9EE6CE52128DAC7914DEE2579E2608FFCBA78DC11B242B2105396195E51A8350590EF1BF65CCE4E1C91B10AF6A35F80E695C95651BC83651BA29B4BE24A59DF2D863820A1ABD2D55327C548FB0A0358C1855F18D28A8A75A136D08BEE67370B5485179EB489CF61BADE151C93695F9754913318D2DB7F103AC3DC86FACA8309DFFDB0C3BDB7702DBD60B965DA897E8C0E7A4F8366BA68FD72DA32487EEE0840D5BAD38A70941D8F4C5347355E76F09977E3303A2D5F2F6E7FC7D0EF69F2F050D633E28A50328AF1E84CC7623669B789BC56C5DF8E221B4F8AC93D7CF9269A95298EEA4A20AADA437EF4901EDC26D076E94F92CB808F6076EB8DCAEAB7E1CFA52EB44F5F5E285409589F06190E88A5E0767BED60B0D9C426D4619FE8016051EC3AE8548CDF7B6ABCB543A6DA02525957E9F16D27AE7306EDE54ADBA8E4A0BCC9524D0A1FC90870AAFC503B837136226C88D03670EAFE4E9C3449D7C14C8272485DDB16846A5809C0E4081765262F75E80787CE2C81DF940FF686ABEFE94E72EB39DB286D9C0830D5AC12B4ED49CB6FD96A494BA2BFB6C7D3C092918195496AAA3C37E908ED970CD91763EE03A88D4A968AB3955148A38C1345CD117D732247663E79706BEA65DA27DBB503C54AA14638D4C5BA92C8330B66EA40C5FF38F6E5EF99292C9019D915707C4C679D17BD35AA40A50E4F837DF9A1ED4F824DF80E3F01B4AC8C1E7E4D1EB0C1DB3F0A748D37AF639FACD6BC6D37EC57719BD0E841A3AF20833D2113119DF327155E28C5961C65FB6B2020F88CADE4047F0682BD70E0CD2BC9D18BD2C62111AC0C240C9E9A64DEA37FBEFA230CAA0B3A87977710FD9356F0B9D03DE2F412329E105CD8A50C1E67CFE0A9CEFE37C681F00D4D1043088A386D810CE619EE0F96C84104F5B21A71D4DB1C45DDBCDC08980C9D881B8A8E77D615B818314E9EFEB5FA10206E2A9EF7680470ECBB969401A30FD6792ADCEE916411EFBA6D8E9D0E584EFC1055523AB115B156003DE1946122488D9CA72124307A273ADD9877F76B7343C877A0435730AD98FC5611E6179DAB485D3522710F08BDDF3069DB67EAA187D9D4D501DED8B20E08A830EF6F62FCA86B588CF4E723D5996EC6A44C8723F293F264BC58AF1A2A076A1760F9B9A2E10CBA89CF8EC72AF7E4D0C0B462AF56F4A187C40CFE68A00113B5156217A335522E64C4BAB084ECAB1D81CE95F7D732AA225FC1B48E1C6973E6E4BC935FEA9BFAD64B6E1E17B908636F7F647AA9C942D13C4502E64E4A03305F3755E69C06608E57DA0D1C15BAB156E53C519E8616729617D17FA83DF9ECC1CA16D5A962A71950EC544823F221BD2807971A94B6B98F50F7AD5311BCDE176A99D30A5422837ABEBF16276B0A1A1A53B9A9BD5AFA07904D06C7AB5006C49F0A5CA9EEFA928059110D232DBB2463D8E13983807342C34E093B28D2EC9648B20BC8EA97DE15A75C3D23F2625E8A4291C921E1D88453EBBDED345B2075E39EF3600BD02D13E91CDA66020015DCBDD78621AFE6E0E68DA4A087CBECA4DA7D869AF566B8F6BBF7F676341E392111BB91439E4F3D0E2EA47D5EECB0EB079A909CCA29BE495D2D42C60E67DBD6C2602306609F9F22F58F0A9496B7581CBF0273578D62B2C9FA1AF14C1EFDC9508C077E547760AC635780981A2F7501E41717880A6D6AADF43D0C2F4D3F506E0E68990354924A0CE2BDF58E301102E9D7C87AEF8867F39A8B6D13F7AE75EDB040B0D56843ED305D7DD3B961E485C53DC07620E21639A90DC707FF413A932BEE2F18A0ABCB9AA2B262E8B32A5D3FF12BDCBB164E518A43C775E219D32389A0921C7C16027F5B9F3F76E274B9847FEEFC8A815B37345254242AD0318A79D0F25ABD2C92C909597A01FB606C71B1605ACAE8B3C9FB23C5759A422F7D14026CD8EE515CDD59F305E1E1E0A4D3DB5E7253FAB5AE2D2A8A8283C9F589F62E5F2FE1E930B97945D38252DCDE3C0217294470492CF7A732715CE37104233B172827261EE0AD499876545BD7FB192F16505A2B3E4C3B6E1DA9DC454DF8C81217C4EEF6338D89640126064935FF05FAC6B834AA0924E00B521066FF148B322230D4C622F6FE0825CD5C81B4EA12705831E6051160FF2F77A2342A13063A4799955AE05F4C8BA8486DFE9EAB459FFF25EBA15DA88B798E604D62B1C409C8EA95753A9CD2A9AFE9B123852E78A9AD58D522D1F7B0FB4BD47E284294EFD7CBBC261B0A9957EDEC2EA26E5ADB7C7D36D7D0B9AEC217FB229CB9D08130D97B3C4760E2838E03BF3DA6BEC98956B88DEB827BA2A9F47941120823CFE80E7ED4CD1AAC4BC8788D3CB8848F38D32A694E4F4A9EA5F492F2DC4A97FC45E6F93D303545B5A96588B20C3B67F84CEE4E51BCF0FD5F646A88801F1C291863E9A4AAB80C0698A0D7D954C4C843BD3C2BCBF722BBE15A6058184CCAEF04693C71F23729DD4F274B63F955F8D14EB4E63D5EE671CFCBBB06531A5EE8C0093D203405ACDEC91E6544A2629AE616694286C7AEABDCA3430733D871AE1C53DB1BFC09574BE1A84E14FFD970CD92BF6839F961B42739EFC0205538E27DDD969D485B04201AA9B9B2DD3CEA9594D1D9C0B4DACF86C23B2CB27C368FCCCF14DEEAF6184F2670EFC8D22600C7B39A705B648089CF3DC2ABD85FDEC35CC6FC131D32CF2EF6BCBF66B90E15062E04CF0E137AEBF128093692528D6846FE482FC8FF9E71FDAE5A9C40EB5E7E0CF1088E8BB20145293FE6F5E67EB24EE3E711CABB65FD09EA323A6F8F54D269D8D5AEE36502F2ABC1785475B860DF8DBA5C1B99F98C114E42F1825ED57A8D34F45BC32A725B14693E5298715B87A35732D4061042724842C897AF5EAE452416DBE282A5378A7729EFF183C64DC987651849323588E2FFCFEE7B871A9C3F4BF9CFB783042B00F069E666729D7C5CAE86534F6CB861ACF8FD65B2207879329DE1C7843E05BC6498C32759AEFBD3EC83D95CA435ACD1D649E8DBCB1EEB25A959FAEC7014B5E25F676B3F9411F9377DBD7FE40816F90B70C42A061996F3B7D3138AD543A0D5283E59BE32A32AFED98889DD5C53399883E2252EAB0A4849158EF07841CC3D628010B2E3E6D18D198FCA7D6F64782DF89D88CD33BBB01C1E437C70F945B385B72EA1B3367AA28160B905F44C0CCED8FEDBD0F806216F4818FC2AF9297F2CBA12EC0F60ABCB6F82D54129F0E43DC8A773A08814784CD01C10CF9A0F4054BFC7D739217476957F467C5B533C15F14C657D8B410DA43C42AFF55AD4CEB7B90CEF86EB8DC8727BF58D861C3C55D9D4CB131CC710D59A6906B734D1CA000EF19E117F2FEEBECD517D782014DA621BE7C96A03C190CDAD22846E0A2538F1FFC0ED22E98A714B8576A91009B00E04CAF803D7B027B01D865D1B464B3740D95CE9601858C9ADD5801B12F89BCF1BE5E93EA03B44025136E174104A0B87FC15C379F2CD9150B6CD1A3CD87E9D85F4D8A4F0B38ABD2317DD37D1B520FCCAFF33B089DAFD4955148D5CF0787AA71EA660D8C89B0C22CDA21E2AF94C1B1075B6D6507AD7694BC2A94F0E76693D5FC2752A77505EE58BA4EAD0C3E25FB0AAE2D1C35D601874447AE78EE1CFBE568C35BBDC9FF31442A3CD52C308E3F6AD641320A4800C40A82B9951E401D1A536F49DFD29206BF5D7862ECF25684E706A6F07E2A248D3C3E4406E01BACC7CF3E526E63AEC00022B9D90B25E80710737675BF0A51D8CA0ED561DF1349761EAD4D70DF80FF5A127D39CF83DB94B0E562340EC4B714A61094CAFAFD84A90457BE68DCD9420408261DE1AB7A00014AA137A12246A7D4142506F44E621882BF3472A7EA61AE82EF9468BFB4D9A61CF9883007D4161BCDC85BE709F6326C03F0998E6DFAC59E89CC439ABC06FA4321DC0DA46A19F4530FC0D9D0BEE089381DAFE688870985ED291760DBEFBED70DEEB573F004966B176F7B361EE01EBA1378FC37CE09D360FBE9089F4C9CA85EA36F363C2C7197D952665DBCF40F07AB9CC2D12FE28C5ECE7A107D4F5A830436F52015DED2D1B39204CA935C68D5C34D746AE59A6D9B9A7B5E46B91EFA0323D33E5B397613842A7D4DBE47F2624BB400DFE28C35D322B5A5F97653F38AB0955959BA501B429CAD33FDDBECA17BCBF26980D2A4269CA68BC4FA5AD6DF00C5D3E7F2653C72922B0ACA3454C564A89BC528D01011A74C27B606DFD5AD051166B5C1EACCE5306F92418A9A4BA170D8279676CFC4D3B33B7611F76A14C46E300961B47A798803B72E1E1318EBF9F2DAE26AEAA137C7FBA35A3EA23051EA5FDFD0D734A391D120B84F7BCB8A9CC1BC7BB5EC08BC9DE4CCC894CB47DD2463D2F47BD6CA7B2F5D7FED2184C22D2D858000A6F19AB4A9391770114EB4291B116E3B0B39B5E0C2475863CD5E0A5E8F2DC386FE2E7309EDE5D067A1053685A9C08B992203F5F10B39868561E22C76725BAA96586E104B68E041E00699DFAB585998622CBE7E27F278F5949467C3641B3EA1E3EB009873E92C772E604F07305CDC1C679754523F2E076718463883B2D1917F2250BB577CEEDAA3759DD4D0D199AA1B95C3C6839429E5CA827A29046CDE520B2C6F0FB8BD68059845BF3C44249DAB022B4479DE1D062C27452854038922A6F12901C14A8431628DBFD90EC9E29D5DCBEBC176897E1A57753B5CE61A86D4AD3B18AF428B2BCF50BC78DFAFEA37967C207AB6103F0FBD39A167AFF1A10948BA3B8A428B91530DE9BCA0E150F0ABD23BCA674D9B81E6C30216D6F11C27BDC3BB7C1AC156779A2EF331726BA0F3873C86F9580113DA445115582FBB07209E9ADC1AA57B972AAF1EA6E15581A44F8932A13E7FA4D4A545B0D13AF47560AD8CB2A792FE4EA3144CDA4DECEC6610E05BDE90AFF5EA403CE9D283A70F15D09A33FBCFEF371D1BAB43B92FF3A8C31B05BE368E97595054AC320F72298CD157B7CBB38E199511787B5273D9A230214DEC60D1A8495AC7BD3604062B4EAB9C4D2F88E95547124175843DFD5BC66B489CAB9B7EB30A4D5F7E2432EA25CA2669D1B65926A0D2CF2DAE0D535B0380619D4422145293A492F4E3282CDA6232914CA7D13AA6B4BE529576325DEF67237BB33F0DF704E02B436B7EB7044F0A76CE4E7A494E071016130F2CA133A2E2F1F9B3FEE4A0939C48BDE1C36DD4FD792F8E7369FCBB6D94C96A6B9DECF5A2B618A23CE22803CCBAA63CA6E32EE0904B3EC820CF84B5744E3526484CB0312A622856DB41D969C779AEC27D257AB57E828ED4CB40E5A917722BBEEA4CA441E5AB0FEB64CD2DE9BCDDB61A270F26B94DEDA3E987A56C6F8CCFC1A43C7D0F31BB94B38B1630F3583B08BAAB4AB4E94A78212FD367ED8B6C499BD88B0A21447414FB0B4ADD0AC63BE6497439FD724449ADE3953DD7FF7A1B364F38D4DDCC1C6B0BD1F65E17A1DA03B02D51E97B987DC1E31927D3497708085C64902888F3CD851155D6E301952304FC7F1EAA17003BA7B86DFE9988BD695A87670961120A30F3A6D82BC843B25475EC1CD296137C6F72449548A058191433BC884E9B5EF874EB55B9E18D5DD78E07D2AD8BAFBB9050FAD4015E9825171CA3BCF9A9F0EC6619B2DDBFDC7DD29ED7659BB623447D2C1AC1366C9833010975EE7C399A006CC1CF8C254757CF1994240D3BEE84ABC11EC9F7CCF40943065068ED7F9DB9A98771747ABE97612F66E946507E527658A487BFB0E8A582E8521C10F2EB3C0F2CC48A04DB4A243935DA642C5F3D4E8DBB2B833B535E76C2AC4E093E766A5E031F3F074015FFD12501063C0C9D76C0B64C14D70B8E561FB8DF19D119562C0FA5783613AB47282E2027F883204E0E6577C5B5F8A919E9F73F739FAF8F0857B77185CBA0C5B67ED95EA153E7F9FF033F289D86D9DC2386DD04D2B2A91345D4F15C9A81D1D1E9B9BBDFFD5EB3364C27AEBEF1C8C1E3746D373779D047729B1736124788A097A13C4E278A08518412B5877902505329F86BDBE5D6C612E924A88E3EC8471F08726765C849A9446D9F196EE26D342608610E2095CFBF02AAC2B7CFB881F3CD3594D35B3A10BDF0CF4EF6B3D050D8D1B81350455210D89D6E557D2BFCBE8D02DD7AEF405DA3374A0720A3EBFD484569436205EDD705897ACA2DEC2CEB5D865371C1CC63D7FBDF0E435163B3CED3DBC709D8803BEC24437C25F5AE07A6C30CB7D11457BD930EB8FAA601256FE74954FF51A5A8EDE76C9C4DF0BC816D03C7BCFEC30E235AD89E7645001649B87ABCED7FC426ADB8475451A4BDE88F5C770F468866B9BC80C5906E2910F0F52CD9A6EF3A8B22CF4F00582814937A00A6756EFE0985CC9F1E2F86241BFDA7BD29336F7B318F44787575CF6DF0DAE8A9878156501ADCDD36CF01EC5E4A23657C71EF7AE481A87E6EF1EE97E8F558A5D6BA5A5BD45163A3709F9021BCB7B9C75ECB05CEF65F1B76E032B1EF140F50226F6AAFA482F9FEE87D77FEBCC6443FB8BB5AEF06A0520ADF6688BE16075593E5CD1A01B636DB9D553D82BDAC25DF923D7DEFA2AC452BBCF74A1108EFA28E9461AE96AC6C58AEABC04BCE6570EA3CA4957A9F58B5E943D4AC2030C72538D4994379216F40949D23D06497DAAC54BE266E4BE315E129B3169D945D08E04C4DDEA9C81F00B0E4490C3238C7B3CF256A06CD188D32C7DA5E885DE5992EA726A618CD1A25A6D9B2AFBD4567CBCFF076C99325BCDCD49BC1E8AF57E172324D8A4A4094E798CEAC864AD2CC62867EEA1A69F40A39D80F431859908DB40EF68DE599B63D852DC2F7E0C1B0B4B8B486D78679E0C51B9DD12C9D764647CDB0DE323A505990F36A9692B9E5EA4171DD2AC9723FB91E0B5678ADDD6B0839847CCD8798E3B6F7E76B01B0D5FCF80804E630303D389105A3AB2FF472CCBE6AA42F2A40C16DA9726B85982FC81DABDBC7D9E267CBA036E443E5B4C601D1B7A44C6FB14E78A44033AD5469000B66945EDB9C9625CFF19A67D5B39E2B02329FEB6004B1BDB9386344E19194B2EA4613506C2E48BAC08CA8E32856C3208C3BD15B1C8A0698484EFD25B7C0A22640E4E8F3CC19B2D6A47463BEE7885E9602B3BE2D93A782C400CC765B7043431ABC5FB7121051EE726AC87D09510F017D64A91941C706384B6B98D2F6E031652FFC3DD99A558BA40AE152833025E1111BCE82FB66AA9F374EF815740151F778872B61B6C43542F01A59FE8887732700CEDBE5851A95137FD722217EF35A3860A7F3E55670959F5B43B95DA10E8BB7BFDC58D911B0E4BCBEC54172B1C63E739E64BDD9861E29E13097CC81D7063DEBA32980102F1A640D279D4DF0EABC4992111BFE5B8360DA61FC476291FDF4F6549B2CAF55FDA2066AEB38ED4EED347F268AFE17BEEDBBBFD5293F22D1F2254250B3C702AF84E468274B30A1B429158ABED08EF10E691C3E9834FFE8CD3FF56DB9B8060800A0E5EB8FF7BF26AFC35EFD8F51108509F8A74B00A5E8B0FC0B49D016CE675BAF98E4837569D8A36E3EF4BFD187D32B79068E8B71BD64CA24212C272331EB31FD4727DDD9D73B0F4ACB440621F94DFCF8698160ED112FA06C2B4B2F56365EE8FDBCCA9594B19CBE71F05B999175F5B6D604FEB47634F35E4795837346782E60067C42C4AE413657D01C3D401261C4CA91D456FDFD20CC719C09514AD3930A24DDC3B14ECFA305FB48D7780866D4F1C517FBA69222B8713EFA46CBEC70EC87E6861E7012D9DE24C7F6F91617E4BCC8CF07C9465B0020EABCA3B049AFB5B75F2EFA7D8881AFE4B60A47675F345642BA1CE5E0019F2E34FAA2C38305D39DE10C256343BF456FAB62913F727D1A0E41585FB52A46EE4E154BC4B8E9EB983D6BF63CA77F66725EE43EA782FF1549E014ADF0A513144A55A5B991B456C2605D2221520AC4D0FB5E86A843408EAAF0669B1CE8BC9111AB0E915F852507440CEA28B94C1F19196B466CDEBD345B61ACE9B963E3E2668CD4B7A5BF4957512EC29C5096C8DF54A8E392173A4FA678D3153A3531E212578F0FF46D33C9BF6FAC36F28BE55E85A8F5999B8F9523C63B4469BD252CDA351F2909AF580D769B64877F4601FBF08DA4B253E174E10BE895A6DE95530F84E2D021EDABC417BBF53E9995C489BD991C71C9DF1D5EDE51434CABCE3C3F7B78DE7F59074F827040508492EC28F41B6F3F502EDCC26281E26DB86F0F55E6808C597DBB6AB4DCDB0BDF8107D85323AAAF5A4A0F22D5738D464684F30D12E9CF8C7384C3EDAEC3AC9701208631696E71718A7A8F57101656392B51FA005774FFF605074734A129571BB17BA6723D0E7B89954A0B598C1799F602C3E94896125FB9C32D07EF89C7457DAAF938B3B378444CF75FDD39F22225B34A1C92A35A2EA54787D301F4E5830EA02B82BBA86EA0343AC37B3E522BBC9DA6634537531D8ED3246B204E67F513ACC93D05EC8C35CE5ED7E970BD585032CFC622C8EF656ABE7EB0495C081680AE32C70209850E4E9FF72F0ECCBF4C949BEDC5C4CB8AAAC3A6CDD0ADE08FDC0D32F307307F65AD89320472B557E18288E79E98AEA234F159006A5CE94BA8F29E7D45F41D2BCACE3601544D26B61054360D145871461AD8F5FAE0E9200364D32AE0CC3F77DBE08A6DBCA210C0F9EC04BE273EC3CFDF3F2D19549B40E80C67C925D92EEC18B654A8FF68DD1E97009506776E080E83998B0AEA5FABFA1942BD7D49C816B91B5AF2C5B7CBF577711D0044AE51BFDE08E8F1302FBCD335BDBF0548736497841D315E384E578948FA6A34E91B72CF14941ED6BDBE47AA5D0A7CEBEE6FB9156B8BE78EAB052AC6063C8882B18A56D3C1BADA35D6C5144FD76D16A134B46D989CB09B18520CC32465FE71B5EF7C503550F0792DDCCB25BC792D968FEF95C95AF14702885BC300E564BBD8198EBADD62ABA5F4DB6DF05CF2ABC5A19DFC1A7574B55AC836424E8B6F433703F735430B1B74B26C7B825AD331F92E0E16780B4C124441EC940905A422C0E719576F7FF6A1151E05D0C0D82A274A7DB8CE37E0473CD1A91DC73AF502162A2663D12F50304869AF0CAC991ED398712326BA2817949E2841F833D1CCBCF84E5E1B72F952F7F586109E4C61B237CABF0D71B759B970AB345CFD10856DEA7B59883060535318F4BDB14C66B3C8EF97275CE8BF1201B69D9212DBD9597B0E3D01D9B060370C3ED15E1B68E5D4E837144DDA9C2A22455D7D9A72AA299C7F23BAA921EE6E175C1A1625146DEBC0D0E1835D1311B3ED724D057F7932DA2D76E3B35BCC88E3355C9B508916A3C1663FECDBBBB9285E9BEA1BFA3102377A0747D9BCB05465E3DB5CAC183CE23AC7AE30E4A538DCEC3636BB4E55417E3C8EAB818E393C2BFE9D9AE4AF24D7A3929B3480FD850D5C443F3DDFA3E24FAD8AE01AAE4D51ADEF1262FB616CD1953B8F58AEB47890DC2188B725705F81D02480E0F07FAF6DB250926414D74837BC5E17035F8279A54990CFCE3C0F923A262002DF7187E5CD4D1690ACA890CE41F8BB7CC541197F3F083BD3DFBFA73FA6F480028F296FA791D60B870AD4F794D9844A2432B6610CE349EA102B5211C93B99B163BCC53770C69248AEA24644495266EF1B86D71542A1B98B3E6CAB02D117BDFDE893D1D0AD3BD22CB874DE3A2E94181A9B582FBD83367919730A0CA53758D46FEA9B9D07E83E5703C2F7C3B583F9F9B4BAC169C5DAF9FD4DE31806A62C202936D83DE25C00F9F226A24CFBB10A05060D7B07609B1D4C21C3739E1DEF73F3A02F19CC4A217BE57803A755ACCFB6F184B13BE3641B1FFAC9D5528A69EF55B250F8B18D9700A06246B115A32FD791CFF6835BE25BA35A9866962E486DB5EFF51AEDFF57A3A671E166B2E8A5DABCC5D67E9ED21C405F0C24D3B86269A33FD4AA9C7D1175BF70B158D975FFDCA790CFD51CCA4699C15A1AF364606455DB23E8A8F84B6C6A12400DADDEF43EA7D72316B8D49AEAF895A507CC8AD5EC8E08C764F697B4F6EB3BEF81F64474770557044D3179313568E7DDB7DEC552307F28898729ED5CBE70FAB442E7B9130E48F4BDB9CF4FF5FB4FADCA3DD7B0CBC8778FE3A2FBA9DE3FE3B6E951931D60854C04FDA7CE43B600E4A10A648B748CD9921E0D808A5AC13A361E48C42C3ACFC17D808A9BC12376BCD7A7E55FBEC6A2D938AE378C3F7CF8A5CF02AD49009D0FE5496F6D27F8E4E501067603CB18D48F0E7F23B3A09AD652A6939066E2BC539F560BFA836E65B19047429F71731B41BF2C08391D59910FCB9935BD115D79245FFC873976A8402BA445C9555CFC8AF05944B817809C4151520C228D7454F83978CCF0BF207D7092B737DB7EB0AEADA61F65C05B1549A40FA65F89D05E11A7AB0E39BFE31FA31CD1ECB77B73B945516B78F17487A73BF9EB434940016EBF39E3D630BE0D383F61388449394EBFC467F7E9A191BFDA7BC2BE8F86553E884708BAD508776CA041EA32FC8BF6CA5E979C5ECE7FDD3BADCBC5425DB4706B68F5CF960B6F03A3F4E62D75A613A943E26B6AB3C40C868E77F982D5FBE9FF35FDD07C0A051E9CBAFC57AAB79D8AFDEC73992F0C1C2C2D9BAD4C2A6CFE1FBFD861BFBB99F345964B6B262722A5507083648D803178A633FAEBE38D03DFA5DFCD2D54CD9422200CA078435AB33773FA8DDA49A5DDA920C2A5C9494CF8089235F2FC1A15BE2CF24BA9D166AB7F04E5EC94B81CF4E400FA454482F9EB1018A5DCF1B56C10B5308B9DA47CF98A76D53314258C7F55126756C3618372AE5BE56E762EA01C6CE41FD7D749DB13FEAF135E33CBB7843529E90130873A6E295E1D0AC26816E5A0E38421711F1F844C03DFF5E7BB2BC2E3B225F66B797160AAFC8B5F7EC7775F0007FAC2E3DCB90EEC2A1529394E8D0239DF80C98D63925FB1FDD69B7BB83EA5166F8E7A1ABD4DE86291B75AC0106E88F15F252FD757E147D5B870C1A4671977375B5FE0AE356431115C17737F1980AFB4084114D1D34B9C2EC8B28F4D489CC388BD95E89E87131FE44DD9A0029E48695866763FABF6FF32BDFAF5784C04F9B76F69695542B410BFC8D76A98E140C7103D6E4C18859F7B56FD98D6B1101E7378CD27E91D7BC26B78FE811FAD8B1C10DB26D3D5798100220269A0ADC48DFBD9E407708140014A04BEC657C11F594F1A0F832A549FEFBD21767721F5A35766621FE14677421EE76FF5F3EF7A89C8804DD3784AA9E5CEF45F62AA12E3B76C030752AEADBE3BD1113349DCD0DA873AA13B9C43089ABAF270D7CBB7D790409DB4681F0555F8707641DC77FD2CE1AC4ADFCE94DA15EA58A323D6B444BFD5202E44AF13C90F433991AD4F76F0078A9F5FAA676EE39B2810C05733951086CA3C4238DE274FBBFFDBEA435265A6F73506CBBB0C1366F7DF082B317AD559B0A620D91AF4A9C758B810D4D80D866B84C1B72125FBAE56300B480EC69EE375687CBD24D516616F8E97C0490632C0B42974F43426C2F73FF340A7A2A9B6135A46338AAB496BB50EF9164E1F3FB216075D321A91516DE37C5C36245C57EEADB88E349E5F09D6125ED98DB3105C3F17100DE03F8706D22C9FAD2A708A2C29220569632CC3ED0E29AA0608D876454A4DF71D70D9F281EF954DAEADCA51C314BC2AF90F1059E22C5DF5DDA7C5CEA385573278A272A08FA832F2C094BED35B387566FDEA1D01EC9D316FF8701A0A13B677F64ABACA4E6F5418D20D78616669EE7EC5AF092982C8301333CDC34F7D11BAF0CA1D459A766C53E800FF06D79A07A243216630747449F7B990C012656C54500E135EBB74E65DAB5DFAD827272AB2C430F05B2ECED1D1BF3C5672AD80DE5C1746D8DFC7D7C49D0D788FE00774588DFB6D744704447BDCC82335C169A514405AE81CCB6D89D994FCA5B8F945C083FA5A7D113EB757E081CE4A24768E3421B3C23EEEC9193F61766099913F9E4346CCC7C04211540B523A278C154473F2E87CF8CCB30A882653B9D0107CED561B5B515A7C72F67BBD682B0BAE0B7E4B16211B68842288FF508D87F95B4DEC506D7E99A75DA21C9EF4523E4D9DA01BF0FCFAE36EE288A0763A06901B370E2C325FFF74F4464E0705F108D885DB859B05919145C0BA24A654CE45D77CE7157D7F764CF9C59C7678C8C639DC5F30FB6CCF227D7F0226C643E7B4B4AFA76F24C9C1BDDD66AC46CF9C3A2DC629E8836D22A6A9FC224C9785E61298D6DF7A33A697BFE07878F373E4C97B95B884F9C15B2D573448C5B56C2DA6002EE494F300857DE4843B2B1BD7DF3A12D6E1715619B0D6D3C687CFD367D6112D9D606AA25E12C61E523D264F9DEDE3D529EF64FC7136FB81E0765CB08228465879AE9B1AD77794EE5BD333373C82B7B85EBB16E49FDC34C5D3960F7AF4B1610D23E6CDC71007CAE1FA7FFCF1CD7E03AB43134D927CBB1BC4882BFEF234FF1815A4F749A09A2E198CF3DDD5A8BB46BC16194666A1715AD8A7A876146CF6E9BF410049643699FCDFE67EA46225219F913ADC2585DAF52C6F8B9CF143627BDC957937D39D362DF89CAB25D9894CF1B5C89FBA8206302AC13A8650274C0AACA8B56A5395A454BCB98D02E0D4061F4CDFD0E03820440B0778FB9534EC77E401E122A449AAEF9A01AD03C02075143947FB99C2E38A5B99ED66C15D3621109B2673208B1F854957BAC81AACF3A67EE0B7F06F67BACD540D138F9C60576C47E2FE6A8554A2A5930804E743214156FE8C894321F7114CF8B8C261C1BBAA5E0534AF37E08E5D4999F86D930A24B2CFE87E79BBA69875F5E240B4F445C48C36274A4142615D6D2869E64D3EFDE166C7D06528976C7E3AEB13C20BE768A91B19689D0D7F1BDAE2E005574E1F8A332D4992FFBAE29F049A905FF6E00F1D4E6C372F70CE2224546CEBF9D12A68B70688C7A8F2E65F287F58EA7D37F83736CAA0773FAD6DA04605F73F05E324A2414170BBB0CB37661AC8DA279131571EC8E36288418A349D3A632C0A7E482441FE2841EDAE6C4314363F7E104B18D81DCB62F347FC49EF48A92B347F0625D47194506A4AE8157189AF903967614FC3C4348F1106EFF2693D91BB899368FBD81553E5044F90AAA34F9E5359C7134AF75057709E62E4E4F0E3ABA7F3AF3BD9C42BD8A76B4FDB577B07E491FB4BC90E071826F9A3205FC6D727E3F42D19943E29C8CB03B573CD3CA54D31E86E156CCB1C050443CB8526E95B4F677C65D6A7FA886031B2A9409085B6B5B901D247472D06868A7304ACB5479E3F55737AE58B9A1E2A0F055B9ACE1F6EC35463C629AE54320168A8065F5D2DE894860F85FAE34BFC6D26F50F8354DBF282F2D032414076B88532D1FFAF263A321AE770EB082921AD96EA2866ED3D90182DAF023F557D2EC113C2412EBBA2BB811B7AA94AFBE1FF66781636986A09A81049B98BDF5F8EA723085260C60DD2524A0497EFD02F5BFAC6E079B9FC699DE7B8BCF6D24D9AF86C591CF2FDD6737A08A094FA82DB334AE070B70BF66CD189C0E42F759497A988599D905EFE5CADB1EF5DCC5F6FA69B1024E1EE94583C53D51787AEB2AF23BD530564EA291A5729A499E1506E58898BDCE7943AAAF9C7ECE7878F56A31774AB918592247940EA9A6E62EE91372B1D17582C9B57D7562EF24F0321C5B8947F56B620E56E3B87C2A8D1121D86C44B55A57D3B88847FA7766A5B0CADFA4AEE047D8A97B11D6A312E9C2440D6A2BC2DAF918AEDE944A98E6C689040A1EF2B0500F94793C8615A1E869CA1F3250A10A468D4E086A13E0159E0B1C8107F3324E31E05AD09E151082AB5B532658B73A910898DE50FD646FFB6B5EBF10E57AF3BDE9B098B5ABEC9BB8B409A4A76FC4A9BCD6D21E287BE869A69E4FDE4859A79EBECC1BF2B0BB38A783179993548088394CA4AB26FA71C9360818A31867989A9E53DAECE67614E3C5EB4B9C1BB68F44FA4A069E3D62FE576BD098BE53689CEC5BB43DFEB64CF3D3E0145BD7FAC2D60AAEC79B14BEC58EF4BF75D122CE03699B1F6A3C1B28F927E59090037FBE211923BD346B8C714F980D3C949D3CCD6781F9D354B2C48A6442F40A8363996F3C390668B012A425F7C10E27BC2636DE54330A72DFFDCF61FC2465789F9E73A00821A13A8465D5D3D6974E66FB7218D2AE2F619116C6F69B8555A77BBF872A96B511D3AA4A91A6DE466FA4DA0A056FF0BE1AD2E7EAD17F0C400EEFF5505822DC3B8D10ADB25A18AED9078C3123B10ECBD8D6CF030BA5AB4516AA0EF6F71A053A631B303B14AA5D89C36ED3B5BA9375C46BF1D5E69E7EC2D076E281D999EB70FC309421450F4B64DF45FBC97B981D857942FF566269971F74C6EA9C2B3AE4E57C9E0CA8850293444248454909C612FA5EF02351172A3B0DF5EAA01F1E97BD6F388E8A61F126E0B1A2022144C216794187DBE7DB6F4C7E3F6552C3DE878D80EFEA29AFE75C9C768715C5F082D788C28C9FDB059E8E14AEE10A1F0A47E801E92F0F59D84D74DB51E7452C4C3D70B73F299DCFB106CF88E3A53350FE1226AF96CD5B23DF7AA78D79D126C4A3514B1DC6BAC59F41D9D14E6818CC6827D974530862D1BD201B425F633EF9602EED5AF469A275730ED57E14442983C0D567B67F2D01C6C9321C8CA14A397D2D1C32A55F964A9AEDAF8B5D5832F177078991CD43B5C788669203A10EDEA38E8AFB39E8D6B386EFAEBCBC272EC42A42CD411F0E9DBDC5AD0669853FC572CA9AF999CB6368EDC285E340DDF5F8E2784A0135FF6FB9E7D134C927C984C5F46FA6EEAF51881BEAB12864B4782967FAE5539335A810F128317127A6F423D17D6876CF5AE31B8D8BC2069390CE2E88648A5E754BB5A2C6E5AAD38514718370B12C5D922517ABF3F65BC845EE79CA51211A89F8EB2915CA98D32069C66FAD4324CFE1E697593F9555E27EA5CF72615E4E4C44750D6B775E7C9B8A0B3728A961EBB27251EE032C023601639D0BB0C6244B445694A2002F594019305DB87C740C2A5ABF960405E4D7FC6FD8CB9A8A1CE59A893F84DB92E19DA5EBBD57797C7D9386B3688F0692E44F2691C220603A88E603F3DAC4AF0EC2E44F746268786B65BFE45B359F43A050E58076BF759B7A946202F94B4F5E234288090875F92FDF006A9517D204E55B8BD33E6EC47DCB8B9DEF7B9D5ED37462C8D77A1D094AE5E9BDBEC5D18CB0C5FE6EA9325A9F213F35038C44BCD0615FCF54322C0070E1F46F05CD359117C0803B63DA34B4B4C824B9580D4FF765A2C56616F132E18895AAF54A8AB64F0DFC6B85CF323FF1E4B15808A8B65A171ECDCE97DEF9B4C4DF771C035EAB87A4BC2A2F5C4EF11E8F3CC84CB4E0115F1A5007453ECD8C9C41B7D3CC43C8F561136429C19AB6162F1F3AD0A06837B05C642E4DBEFB3E24E9601BA4CAC8EF666724D906260A62D6A94E5F648F7B79F3CAE2CA0B81E2C726CC345F046A864BB6F4E9400CC1ACFD3A99BC1C346A4BE9928A676CB9DCA7C20E836CE27A08C6D03E502FD359D2A85ABD2B6A6AF4CA17157D0FF7BBA37DF084DD3A724C25E1FD69B7DD8E77511C011EEDFDB49326DADE3C3EC4ABAE3FB58E1F0E27A733066323AA3FD9CAFEFF0AE3EF977E8EF53F7322C692B7E94D85AE2CACB66409A7D21886F4ACB9DA73671E15CD7999E83D1261D1D278D6DEDFD513159324EE79B8F8123B097C323A702332CCAB34F23B70BBF94F7795B0ADC01A2E8B895522ABDCE0F6EEAE487D14FFBC74D3420130A2B96F005914B07F3E2A0A4E94BAB38BB469AAC8C18D27441F95456FAFB539C895BAC7F7E72DEF1E28715DD3E84E86741D696A15F66226F0AE2573C27F931E0EF30AA8A3497F24F4AAE8DF5E3C8B97EB81523901431E40A837EFEB9B5025C37F6D8E6C0AB701E3A17CEC81A7D7B7C46C33B66BF9BBB7C5B70C7A2F0D1D23FCAAD88F7ABE92ADEC1F1FC1C652F442903EBDC2E1AA0BE4A40A56DB14490DFA087E7D8733EC46F287F07710924AE1C420ABF710223D68BB192B7E0DE96317B9A09FFADF5499903B2D5386EE274B86703FE9712E0EFF66E8479EEE8FA39162ED0CF0D705B0D903D3CC3296B1303C6CF7E36992AEB5455A737C70F73BAA9C864C281A52F870015B9E166747C2AABE39F4469A58DEF7F269A25C3482455460D1834407DD7C42A36F639E3AA4529392F682F795E51E9A6451CCA809603711F3DFAEC01ABE5DDF3EB6121B66BDC78727D71CE8B605853FDDE2F9E842F1117BE83925AAEC279BAC6F33CB59EDB221FD2B507B4A36905B1E3047A1A0A31D03C338B1CCB37AFBD1CA79A48968243DFEB3F3E51EE55D9909CB5052CEE3E3227E199C20681E1B365B9A0B4CFF51A0CDAE9F10B0B083D1CA6B3B786BBB9332A9A45AF5A0FA7DBF2E5B1C9B8C1563017EADB54F5894E3E35885FEFB43199EE8B2479B0E7813F3CF7A027D4AB45D1EDE54AFDB421934735015E6E67AA32BA10DDA5A29D42D5BA3FB5AC71127D0B52DE2B39C5BBC1554E3AEA0EA99ABA4B0855F9B9ACD2D30AA38225FE8E2818FA2D060F9D07B85C40DE0CB259C072552FCCE5633C037AB826C3F70B482A9DBC1162F3869200B3B840AB9B3E6ADF4F68A36D9B041D817FED980F908BF70CA24E5A1A4F34DD3BA295B0AFA7359E31E4279DA397822CB92EECC513307A7D56CA961A50CCBDC8639461F4E8EDDBBA365704DB61DFA6755765E1F062810450430AC69CEE11775E08DA8A4E3480F4E8E53A85AECA66FAE351ECBFB6EA6D7EBE33BE9F763CC30A8626CEDFBDE29383770BF4DE791EEEBE9625AA69F580A5EA3FEF80CBB556983AC2074B0DCEDEA25D442445E5B1A7835164CD76FE59141C0A34DDF6E61119908C524AD6C3CF519A7ED088C71FFD17AF72696D3455C8D8E491C576F90EA6E2CE6140CAFD834AF5E364F0FE3325F4EC64C1B9FA748DE6C3617E262681CEF67E226261B42D409F366194D44C598452AECB9ED1A70E661367EA299AE30DA6374304FD3F032C13A9E0E581AC7C7BE8E8621F6B3259808C60387E4AEEC033D3FE9C78C82F80B98B4F9024E6869E3D47C10527933C940CD7EA47F35DE2EFB85328D2ED64A25B273768DC9ECA8DB40065FF3C42E7032E55221184C0DB4232A096B3DADCE9362CDF3A69E5B84DE12FE8F7905F07076275D148F0C23C353AFF29683A7F8C4C4EF4E1CF27ACA4866A0C9DC3675A8245AD840C0D9AF154BCF94EBA76CE083DDBF42F4B87230D6DB2B94DAB2820083F39B5E27EFCF3A2E25C0A9031492491D3FF17AF00C29842F3C691E5C574C2A89C7C36B5326DC5E5A06993F3147516F79F6C0E9F586B72AE900EE33A1D1EB1FAF30BA9D062CEA4C5088B653FA4637040182AFD8A4CBEBED1E506577C2A0A6A6AB3BE164E99161AD8E1DBEF87A006BA272C09D0D3A5FB35680A5082B76959833E2E9C2C9596A7F83759B22275745B58FF1A5AC826AD3C5FA2D08B520A2B2E7CC9C1B180D09ABD6A0CC2BC633C56B43D6400932C0474EEB245CC0260D1B8C41E8679825E2BF6DECB3CFEFFA3E4E9E1A0E2461CEBCEF21129546845BC7CF51E7717DB9F400E9989AABB44FCF1315BC0C3CCBC003FBF21C3AC9E78EC1F20BD6F12D68F6A9A784451E57D8DEC9989F351173C9D2169E64112CC0854A191784B54DBE6C9AE281D2D6A7A0526258B49FD9073DC195ACB4060EF5DED195CF9BAEAAFFADBC067F2EA4317D98DF5E3E0E69B5045BF52A0EF7F0FDDDDD44BEACAE8DD4A98952356A23C580167EC51DF773C220896000DFAD136C120410530A0E3CA43F64AFE68F54AE6B1832391BD9CD2C9AF60C803FD3287FB461BE0D03441F187B86A23146A1FD7D3C812EB5733C5D104619420D05A5D0BB6AE5C3E321D251195E811CF7558D26E0BED7CBE90161F83743C28B54EEFCF933FAB522EB16D78E65E72BCEDECE333ACBA305AC9AED65D3AC697DC9324CF187E2C50FBB2871C74A7F8127627375BEC0FE9D24498C412843D62F38F958CC4A06A845C37924A1AD6575C23D810E0C3B227375BDD29A61D771D5F1D9567147CE36AD6C6D2A96805321B8E2122EF6CFD6CA5729D007FE30B3F6510A5000110422D5D4583929821BE34AF5DBC9221D272F7FE001CCDD1015D7D720242C07D3D002C7F2D83FF77A8B3A5D93B9435620CBC34830F67D31C81CE380884DD9DBC94EDD83CF13B23ACA30E6ACAB64BA762980F48EE17CE74A5225A4D5819D6D268CF75FDB1BE62740F6141FE952AF27BBF2C9E7092EDEAB04C57E62915EC5DC595DF9BCC276F995E8F9C5FB31888DF9061BB48DACFD95E002785DA77D3104E3727F6DB94D783A0F982A14FA3E410DECEC32593C0008C6097A633BF8CE1A3DCAD3C5A989D958D1BF4ACBD44328E82738E9389F23DCD4A9B4A56CD42D5347C17FED43029708CCEEF7F8F2ABDF03FC61C72B09F75239FEEAB9A462B85617407F26AFCADB9E796E361193A547386B3703EDE92690055627546040A5675F54FED152031ADEF4BB52F2168A3A6661E4BE609091253456971B255B48C1B35FF8B928772BA03CD0BBC7D66EBC56246D849205D79D854E5F522612E7DB7FC18E58C323C270D1EFEF1C9A244179A711E0F1D3C24419225D3E56C555F2725F56414DB70CD42A170A661950D991049E1C93C7701B02FE321E307E42E074A87EC62EFF98E243CE546F0A29094DD97D5BD5A71F01EA3EC8B7D19AE5EA1F56144DB628A30CA9EF0B272FB27C3C9F1145B6862C890E884D12E8C1D2018BCBA2D76F673D231C4D50B63FA38812685902CB83A10464C9A11D9D1C2F92BAED94D6A5022900EC936B11DF06B94B2E939C77411DF044812F4B08B75A8054C512FA8AECBCDDA6747D534D0DE89E83BFCC20F4E98C780AFEE5C5F9114121A0817ECBAA842759A70126A3BF77B0383CF3CDA623FAB232B4E3178FBC41045E18DB81451C27BCEFD5F244D9206EB1E15A2EEC4789BCBDE53F08784F1B0EBB778763E9486CF3F5DAC3306F4D6292F2CB221E403339A6AD8763CEC7E89F93776FBE99853C57BB279C0E485DCB3D93622C2022D59FB52BA96ACF4FB2D8F386673CA4AD1E9FDE705F59F46F4360765133A845D9F5AEDED31EB811DC2B584DEB9A4F37BEAFD21215A2E204902881B821499110719817D7D366F0BBFFFFF4A19FA702931AECD1B887A885D2883CECE987C8012B7B37809AB9299589A7F530293DB6C8D97FA60DE06A35D27BF905EE318614146F2BF9A335C19D601028D464E3D43D9AA2350B36BD2D6CF9FA0441E42324544BD26CAAB818D6D001ECA9AA3882C9778FA0B8E6F3969653944FBC6CDD484749B9C0D801F9A495E97A81CC667B6917100C0BCFE2FEEF433BB44D45CF5E1C6DBF0FB563FEC64B9C5F274FB6714B5E30D10FEC3E753176BD7399580392BDC5C6DCDA15FA2B80FE9050F1FB7E8EDF20F988CCEAEA535C2AE73C0385D1EA52EC8E1B9319E7D375D80F401F9466EB12B01CD89CD6DAE79048CDA40961A6959FC6CBF2B6018D398B5D5AB4A5870919F88AFC2A4D5F8155B5025190ED8581DDC2D91D2D611CBD501D3366042BC45F11BAC961F1B971F27E02651F85D24BD9AC285B66CD8BC985B5D5F8DAB4D51E1415B19BCDB3515F4C568ABF54EF5FFDB87D67F6B0601080A81D329E29A67BD65C4468B7E303F9DAD759B13C8720767A926C980A59E047CF1699FA6ECFF62454B26012E3422397163ADCD15A2890DEC6F662237F9FE06025DCE3616A53E88105AACDE6B2156A9471593943217F2552BC2A9FDE3392451F163D83A90A58A3966111495B39E9EDB84405B08C78E71BB3D46C6262FA9DADE749A63A96D8B8D9AA1976C592F4D0445F078E38A1B9C786AF7CA54942837B8B65B821E98A692F26808EBFB9B6FCDF1541BB74E415AE2F0B6C32627B560C094B5998F986DB9B02435E96B07EAA44D038DE57CDCA07AFDF90763846045C00BBBDAE33D548C935B7AA29C7E11F13375F47DE44BBA180672740C525B960EDAB1F11FE2DDB8249370D3CFFCA2975A6174D2A515F458F24470F1734A5FC9AF1B2A3B15718F88B68F58DE9CFE3512FE9A273FCAEC7383576D250994D3EFD0F8F747D06D683733234B905D4B0BABB6BD2C97245E81F272320ACF71C7437778BC37E6B83DD78B0F9799A5E2B4D87A1A5B0CE8C696F192EB559BC9CF745D46BB0F589B1D40A80C985ABD03875A305A4CA07EE07CC3B51F9997506C0652F23ADA1D62706F2744726A8321C6EF487B2E365136605857CCDD5AEA9A8792A8AC697449742A27D584C849B40876A16A2705F1B8CD82548E64FDDED6F42AE1BBE2CD374FABB060D5CC2C0D7D9FBE35EDBCDEECC38CC9DBE49DE05862CD99E9C2270EF057A550C1BC984171D4B333EFAD098DE8B4D929BF82F7C412987977B9EF354E8258958041DC1913379CB5C0384B873C5467E9B21DEC48AF2084B49E2D204F890B0D4E54A576902858F655E8FC7D938FFD96BEE07EC838CC4CE53FD6A72A686A6ED325F13539698B9E447D08455997059DBEC0E08E5016302DC2127CE24ED7F2D5456EEBA8EABDBCF63FFF492BBFDD475705D62F4F129B3316FCA531B92C3E4D219F9ECDFB121010D69B48897B608E42CF342D5E7C97ED3D39058B841766DCC2013BAC4AF3548D1F2765153A74CAB335D5D48A78ADB41498C0B8C04D50BC664F2B3B2D5CC4CDE036E7F2B32AAB25FC5EE93E1E84A58EBFC41CC51CF7F427096C6A3F54FE1889A59C88E58FB75CAF45BD8809A7C394E5499E233FA1380691E11A43EA50E37EA48D6BB678DC5BE07987C0424DB14D934141C8503DC05DBAA48B9BE2D6CD95B6E8231D708A0D6761E6F83F445EDB1F52D01168B745B6C08C6BBFE0039F9B76DAAFCD7DFFDFB032979EC774CC7C89C992610F30E08543D1BFDFF032C8C07527AF7EA463AB3C54C7A76A8907032204025AF24173CE8F71FF2C2D3B7E306CBD5D1C6BCFB182C49C64325D0F1DFCAE1AE8AC903C5AC735C0A39C2B809BE5386CDB98A74C714D3935ECBF6D148485FB356426382F5E978679FF6A7A742CC661C1E0AAB00CB71D096F835776D3E8EAFE499A7F3A2B0381A0D1C848C6C9C9BE588274AF8ED2B3107B42130EA4A0009682F0E1A11263EA03FA07952E62D4817E9F315D50B949509885235CA5D6E0E5EFED006F17C6DBE788E4CD9A2E536F798F5C5B7764313DFADAE9E080B5EE1AD82F7DAE3912C33D3AB458810A187D7CCD4AEB6A3A65B2BA83620AA6DA5F103B502F1ED9070BAA465C46152DB48448D0D48E0D23DAD145DB4460DCEC6B7A7204C2502BC6D792225FEE4E74118D7C2F3BFF274FB36BB80BF8C505EB38299065C609857E7A7396D712773499CCBF2AE6D40C02C50D7C0364A3F855AB90E8425CAAAF199ACDA1B1944BAB6FF8BBF3CA80F08A16A0BA23446766E33E00B72ECB66F5639C7D44ED11F9EECD1B3C2546CFDF208EC0DBD277083B9DBC01DC8F839B7F41600B998E9372AAD792CBCA5E88BF08FF991AEE7CAD0509C22F1EA51A708949EE5F9815359199263131885FF243FD2456776F43EB69FFBB64B0CB916FB244B2A3A706D57F93C453AF7C2B05AC3931D6A6D1A43427214D45B5FAE16F7DD57C23ACFBF8F92D4BEA7F5F16D82AA0D72438E63CF240ECEF964977AD3F480AE27F178F585E926828642D110B4B69F9D791910409ED8B3408E451ADFA8421E184111FEF558282CB2CC9237F6CB6C45060F54A4BE7A7E4523252B65C91CA41B6E42A6F4BC687A2662EDEE3FA615EA0FABC4CF6825946ED899DEE17753A22D3CD0DA7F9919C0F868F152DE1B8AFA2DEC36F0F05BB35E20C51A9024C86269A916DAD6642AACE2A94C0850845B2A75490B3ADC89E5C017BD2AB8BA738E0DCC966064BA358E1F9498F789CBA5AC590173FC35584FCB95E5A542AD2C54B217C139D677C7E27648729B3D7AE9B724ED7574076A43F5873AC56A34CFB43EACDAEF81E5C3D7E21D28884AFC4BE8B2C522C9D8AC3CA488E3750A780A6850C89F2C6B440306791FC7A596C101311EEA1EE8C2A679E75D69F188D993BC4A458850E88B7EA65BB83B12A0ED1509D9026C5BB10D1D4F11354FADE05DA7330E65A3B811CD2E99F3B2F6CC1715F31064EFDC208543644462FA3C4FE14FCCF4C9829F8768C908056150E293E0521CD003803362C98466B5C98002218A86E1BEB09D7AFA1962A1E75465F8D09F02C62FA11333B4E4216247847B9CBDD0829286A1A0757F01518CEEC4AC2163CD206C8A234E53C3F293B563CF55DA09392AE76FF5035D534F575AA5D8DA98C416102FF72B053EF86934350B8A6764912CD41724AAAB694365EEC55A9C90899A557C1E1C32720BE2358AB3E0B5FEBC58C557D8211D60A0A32CC9FC1C0943BC5BF08C198D722242A1CF81BB8D21E845B555A022E11AEC02F624A3760B3416EF988FDDAA2D01831CC23C7130C4DCAF267F49781F2133C11CFB54C8503FE566D674E8FF62CB5DE08D5AE686DAD11024AD2D0B8CCBCE4EDFC5477A5E0B12F997C71E93816302B4DA63FCEA3B8628D3D397C19C4B26A7471894E96AA59A55A4E16768919BCA85D60CA3119DF00E95C8273B2F6CF9877208736F92471F939F42454F559406887227FEC7489F893A3B6D157C73EF49C07390B131F2F028BCBBBE70BE2475894686BE1524132CCBD233EBC2D24625630404124DB333B7BDF832F4CD2A24604DB39BEA8FC438A03B49A49285863197549524CA06E69F73036A29C977BD765BAC06432E328BA98A6AAC233830BBDA367CC16F806A64B591D0D3B4E4C4D41805848021713C052495F10578BABE03FFCBAFCEB63697D6A570A820C34E2267400E53B879081A29ADEC66B98F1B1D25FBCF45C380C75028A23D58C9225F46036CE8148C1632AF6E64021F8AE0091CDD9794025F399F811BF4F8FFFEC9E1095FAB0A9E271A9A9E35A7C3E003BB696F52FC9CCE87905B16D16A16183FF2BAC1C801B134D7E656AC43C659BB166D8C897B2BE3F30CCDD1D7E66B9DBB26FF4A7FAB3DD6DF4139B3B34AE3670CDE88616CDAF33B42E581761A565BB399C015C7F8D8A0A5B0714A139F0267B612CCA8CA87AEB4F087B4D1C79B8AB2F18EDB347F46CF1E98AD079F6CC2E3930D57A9A86CFF37B51014BA8A6E3364A20F4475A5E0D32605C5517C27885DBAA008D588432CB2062F69D0FCD97ACA849BB41CDBB34951E659A29E1B49343ACF49CB6B3323F0F0D19B75931DD102CE466FFDF5A70DE568F38E120E3B80239E088D8AD69207AE03566EA69F0A24225A75C24D90052C3E485476B2CFD3A103DD123695E70570879B29E36FB2C61D15542EB41921E1BB3D9A4068E8CE0235C6B13D390D85DBA63C6156D1B5FAE83A0891E08321B6B0A4B1CFF51276BADAC4C06FFD3F71F3FEA5679FD2E927217C93200D395EA6B8FC5B2772F3A0D33FE6E5965B37DE87546CE9E38C9DD9493526F3D0B690AB992FE294E3E34C4B21E1890C7F4ECC4B0D7D59F5C57C98D13932647DAEBB04B0556460C9E342592CA60049517DE60F09ADBC7CB57DB890CC8CBC90BF2B1CB4F8723A30307B27FA2FA67BB4912DAE843F42FF07A84C46FE197C24F2EE7EF82D7EC6606E0BC7DE2B534E8D214BA4A76C7A614ABC533625FEFB2E2D8FFEF1C4A166B0B85CCAC963DCECDC2D45DCBE22CB942BFCD452F3881F2248FDEC4EB7468ECE80239D720731108E94B2FAD59310C7411509AC085EFE47D8C05C5F69D5B53858021F06289F22D68B422C4AF328634C5D87F9979617C11A883FD8DEB0C1D4C1D88EE03E2A67574FD4529F1F94BE072CB1E549F26ABAC8D02E9D17B47FFBF9D381996B3D630610F52772FFA471F9F204F33CA07935A2F9549F1FC4C08E9A1EE662B71E91FB23933E4DDC0F66C793BB1DD39A8F103AFFE2EC0AE6641A03DF236CE31F4000AEED208FB2805069F1D0728E35346372F946A99ABB0D6D2062B5690D769C8EAE006C9BE83D8B5FA57ED7DA21BC808CE210E995786B0E5B955C839BB728AE3D6AE6C10A2BD5C4D1E47E7B69F79066F768D63BD988AB7277AEFF9AA2ECE1DCE4C5588E4714A4CFA929A324D2ABEC3BFC58DCADB547E5FDA375678632CBE9B60DCBCF5518B46F3FA9EB8B25857B9961F02B2C8ECE27137E2956CC6C258FFACFABE88DBB0863DEB5BC746E5F0CA1366025BF0083DC5C6CAA1D17E86BEF0215D38C4469342E4CB8F2C002B579B94A2522A9773200BD97D6E2813325DF921D0CCABB7BD06995918DEAA937DC7C3341EF7C5B8E63D9B04A398D65C1C8F21654BA44F6552C1E9F33900C6C189E9F7B32776AF1AC795B48884D5999366231240EB12F81DCFB3B4741CBBE4081955AAAAA600EA68D22E5655184EC28932A31252C5F860635B041A2C6A87E110813D1DF1D3A36D779CEB62A13CE9759EC0EE11D17C78E1EDEA5E053BAFC623B4534BC656882CC87D87FEA55A3BED5DB62E624713EF4702DCC3EA6ADF07F69D0A6E22B76B84D8DDC961A17697152B19095EBBC9E7136DE4EC5E142B3CBD75762F5A72FD9E08F217FFD383546A889680B69D6459C3A5711FEE2173A226636BAD6454F454AF8528808FA2CB5A55B3FF552D9F9C2707313C821A452E48EB724BD4D880F38008B455D66B99347769FF5A55997EE9AE2137F7A2FB48933920D0CA416A16F980E6B7D714282ADDCDC6F13DD4618B8120569602C46A3CE7B25057FDC0754A214AF9CD7949C3775CFF3F9857E93D6B62D00BF10480D67076DE98C759CA659315B679B145B3330B9B2879947BEA0B9C1F4ABA9744DDEF677C3FA2F027008ED0671AE2AF607E65462B80A026D1C0EA5674AFAF8BA2EF1EE52E368C907D0027864D260964D411B957B6905E5BFC12D01DB6461459D99168B22C3370F16214A6AB4D044EBD55C5E2B6FF9B43761FA210FB992203053D2DA392CAC12D08C29FBD7238EC0551EFF37E3E39BF26C06950205FD5E04A52D707191530C1276028706ACB5939F1964B1991A15F428A9DE9DD19CD0A90BBBEA58FC3055D612384859A6DABBE4762466A05C1C7C386E9366BCAD00470D08A855178CF37827DAF63E34D8B26D377AD88F8CB0840361A94BB1A92D2DBB4001FDD64ACD5FD944D02C22BAD14A29AEABC7D7BC8A97F2F938E24969BE30A8DB1636ECADCA6AF45184467C4C5715E2505F0FDED735CF94BC4FBD23031855688B4F1CCD69FC486AC899E8D97608B410CDEC222B2D127C474611C4210DF59CB4993A89437A05B114DAF0EC232C76A5376C10F10917F490F6ECE01E6236561F1F7E77F1B3AACE76E52521F135B87DD84B49A7017CE23ADA63493D10DF64B434D0D72589427DCEDAA0D8CBF73B0E54C674ABF6772944C4BB4FCC261FA0CAFF055D8C5246614CE56BFAE69297D1B2FF223D5359B23B7A3384FCCF186FB6B2EBD99E4716F73BD8D6A1293F7C7A44D27E952F3EA5BE20D079AF821F9EEF3F60830CEA8C2CE82912212E481FC6D64D583E08176811C4469BB96EA127D13C9CDFDDBF94FA3AB99256FA52C9A82F624B9FC94FB41235322C98D6239B4254E5930A17A58036221E1F5E26191825C06274C21E311C0B4585CE26A1E6640535E6CD9E71FAA020AB504CA806FDFB2A72C72FC73B3D9767012965B222BE1F9015BD45CDE22D737BC9494CA5ACCA700EE41891BECA333024D3C8B5D8CB8BD79739FF4B6F10FD75BF95F5F265951D1736D84A4379C2A29B7BD802E2D2DB5F48E905C0BC75C5EF9D4EF4AA7FEE7B920AE953632A0718D775AD4481E1F77E8B7F61A2B5291E486CFB272EB79F49BF2389EFA4749703A0FBB95E398917A1C7EF5B89F96007B5B7091BD92559A0DE45FF1531348918EEC1E12EAD0F7E3D0BF0710B5FE7DD270E715965884C84CD97295C69C0888F553F527D8DB77A2D5EBB111282CF642342D5A02DB46BC9B0525BFF4E37BB641A658FBE39617FE1F408CFD97896C02B33DDF48A2ECECAE8F6DFE102DA10318F8FDBB9E9956834FEFFA0015F062A3F153D368F94772208859CC86D36F0D31B5018E4EFB2E953669632491833002F9C0969198D3ED992F6A47F7A54087D68E1BD9BD4D6634692136DEA7D00112A419DF4701055A1EBDEBB29C9BDF67111486A1CC9AFFEA8340D6D254147C449DCA66E4A6CDEBA3CFB199AE8ED051AB4225E53D7A64793AA1A621210F85D57E41A21D69A35AA51FC968CD45E632C8C413C60CA9A4E2CCA08BB2CF99401A727554DB72D103F14BE199709A014BD2E1B9FDA80FEDD2A2EC2D10AE0B3C384497EFD9443F13C952E10083544E513C4BF3D910FAC6BAEEE020DBE07C69FAB997917CE44D678352738B9EF8ADAC1A875CAD0FAF8ADD9328F2BDE0ACC8AC1D2D6C62D308BC9FB4662D9B72771658E92857456D3F4CBD971D94F5810A0CD5B0F495D2C18BD25974B9E0A26A46CADD37BBCE6F8141F3D28877154BD77FACAAC09125FD926BC3F0D1AF9716455E49BA0E94863140101F1468221CDE3D656958810DF10F9DA4BB79184D614A87CA9D7060289A603BCC664F2F974D5D19593BEBC5B53CBF03A14F7ACBBF0ABADF2B6DA977B9F002CA798F90932C0B76EACE0E97F3A75E7F6FECE2900DDCDD290CB8384BBEB00789C337EA19FCFAAFC5C5F27974A79E61C0D8311FB3825F1471A30AEA2AFED26C02DFEB288C759E5AE5DCBDE09F2BF4038EBBA4C4FF9F7C56361FAB62D9556E8B69D6AC6AF60FB28266A131E6324E1ABF8E9A179AE0612DFE0AF43E8EA34CC6963C00F1517ADA2891ECE0CA12A1E7AE1D4AC0B7A742A72D9C74F307335CD369926D9EB9D6DE881BA63B20FB72BB594D1B33305A7F13C90F4786131EF6657F6CE2AEA99312B443D5060A2F2433BD913428F38BDEA661731C32F30670ACD172157C6C3FF4D23FB924B7338B42BC47B8596CA9A0BCED7A5397531B78372D5746F5E8D48FDDED913DB9EC5491C09A123FB7E8DE770D4EE0AADEBE3F616EE04AE367F1698D3791DCBFE03D797D7D63891FA9F05A9F0B39101D747C1C9270AA9C8C5F17DF9B5AA0D05145DE455F1071620478E17F0ED3C804314B2FF17C8CC4AFD77CC166E74B58DA0F040A7DE9850EC242BFE9644B15FD071C582A8DF5CCF3006FAD382505CFE12CAC60154000E828F533B7A7E72EB1A73113B03E987D99FFA5E3A715E574C627DF53A00402FD8D14E4E2E2661F7AAF3AB17C5430282C1DE3AB46D8E526C36748E5029795AA1623B66B32FCB1EB3B0614E0895C222FAAF6E0D3B540A66DCEA5F1FB189BDBDBECAADE593084FE4BD523EAA3AD9FAFB0E7629CE0DF910C45BBA99EE3461A8C3CBE029058DD5E66BF8D41B53590AAAAA274693CA257C924590117EE92FCBD15FE0C1395EBE333947BFCEBC76DDB1EF7FD13B52016BC61CC35F588B541EACB98CD30ED1D65E06BB5A1A723358EC4E3906BE05051513AFA4CFB5E8F9B6846808901745D9EFA7538256DF3297DF1909E5A16C724194129B2DF4FDAA5831C9A3B27484617787088E448158ACDC4B6ABA4968E24B384BB7DE602AC13CF810AED3D49192B83AC37581933E2E7505CD61BBDEFC7D3DE31C23B85798F8D1409C06596EC74DABF89103472A4E5BE88D92303516DA89684DDBC813ABC9897292CF17889FAF331F4407DCB1CBF6ADA3387ACA19C837FF24CF3FEF3FCD1A0A965F7D0DD0C0991B578C84D7EA956774BA217C9B0C5F047DA699F5422D72EC23F59A70503195F5F24E7349A5F6BFA812F0503E9C7A1836C57B42AF8A26113FB377C295EF3F30F135BEEFAAE344B7A572BE2022467121A58B1C1AFF6B7DC7A51032810B578DC3147A5D8BF005C5F5C90D1992F83AC3B8DC10C818A7CA6DED6139F50BD9E9FB331C1D2EAD9B5FA35B1641572C4802DA56A9A3B98F8DF48EF44FECDFA0DB7A0DD2A0A944F81BD46F3168F8B9AF3F598CC4FF2CF9959BA9B79473CA4356276FB6A547A58CE396B68E622547FD16EB44D11065E57DD941F60D09163D84221190D568C5C998977D34B67B8C83B899B9E6E5890167F8D4748E667B42A4AFBCF495291B7909ED1B3F7DA1DB5F9C9A8C4854CFBDBC8E1782E585B0EF05661081A3C26E8A075AD21CD34E769253CE5765063A476203D0693F1CE74C6AFF791C6129C1DE221C42BA089820F1B5C7E03F9B492A41483DCCF1D6F71D45992241C0800732A66408F72A4D3A75A17AA7B5961EF0EEC7E36B1B21E759DC276C549D748DF22EC6D8641D447B82891077F9C163E7D668719FF3E02A098B7A11EB2C1E591264BBA7AA4D64E93988AFB8FDE0F976E11E21D4AC425655E5714078E208832A635EE1C28EA7A919A48A437DA36354B2E9D6434BEDC7E39636BDB3FE055A92503C9E6D5DBABC3EEAC67C584AD466BF7FB93A2493D19D0569C4A69F893DF481C54015C1E5A8E52FDD4FE3BD8C527856DA4E436AF1414B3625660BEE8E1392108FE295F9D4B2E789C1071FA7ED46ED79399CC4AF09A13E8CDFE0F7561F62B61CA8387A3CCE8570E491054B5516930E603E10E1B2645D2BF32E100801AA997E95DD285DFE07BADD392AB2045893A95D02BAC20444BE5D954F4CEC5C5C3379D02F523CE8329C37D0083583ED43E75FB38812C4C3D1E3D57DEC4132120517BE68C71DFD90E92AD0C2BC60CEDB3483651208E7B1B2A3CB8CA3EDFE28FB693FC06A3EFA56AF3B9FB27DE24E85AE8DA4CB1DECAB79EEC93F09CAB0EE8E922F1AA2074891BDD1677786F82EDFB888D96DA26E3243AA7219C65678965E36AB3CFA0A6D894652E91865F03A1F56CBC5319E5A8E5957B1ECDFC0D68004C8C761F2E0CD8ED874AA608FD992B42B320166461D08EA0BB9218A2FC4CA20BD81C6231DDCFCA72E0194611D121CAA760FA16BDA489ADECA8C681AD0ADA37840D0F6B99EC5B474B011DA8E7FB6485CFE7336531BC748D147755149B568AAF8E263D57EA1B0ECAD1BB4FB3EB7BB88B4342EDD34CAAC45125B948581C2DD7BD079E17320C698299CE09ACC6AA2C7FCBB0645F6806696E66D554F0DD4DD59D1BE2F0C6DCDF33AD311EA94C23D6C3EF5E468C12A5BD98BDCFC8EDAFDDBB373492532F1107E05AD091800A6948C84E904B54D39571DDD1F711F381B4C6221C443D2BEC15D8C40DA089BEFBD3D53E4C98E41FD13CA879513FC90B5BB454713A1426D094D537730A7562FC23AFEE8D304623B9A70352449AFFB12F2BD08DD607E7328E4683D59C049CC19A3E315BE4AF1C6E77171C89CE5E8434011EFD1E340BD4FB24EA49361D026D1D35AF589D9E5B6EBC225878B3D7C466B3C5B27EA2688B9E02B704BF44394966E08D18ED10FB6FC4AFDEDB265B5FB0B5F9778E475D7F8F07EF1469D0C66657B96B3173CA0010766D6E40DEC399E55E0BBB75F2DC56D4AF7A90EC5C6007451AB4EFB91777BC610832423552D3B7864CD077A5399452B9E0F3F45D0C32B536AFF0BD2468FECB7D1FA00072CF87E9541B0574F2F4682B06BE508FC53B9898437FCA44DD94B56E80555F33B0C171A334EA968F2373BD54AA1650A73B8175A6F9C0E3130660228C16E29A139675889FDAE0B217D9AE9E1AA71D9452839BAE261C6412F94A3B1B4BA72C25E33B9D452B7D53214DA424B588AAC642EC31386BA7C194BDBFF806E40548A63F55326842A92CE94D4A97652681644B61300AE56582F68081C5D2398DD2025A40709D960793DF203F5501A5F702FAB0AAD3BBA868FC8E3EC8B0620A05C886513F4F1FA5F36EB25F5555F928069746E2AA21032EE498AAD9A14CB2AEFCD18B5E675B516D5607F49107EDE10AF538D7088452E65B919868DA5EC054107BD65462F1A955C6706F717C821D031EA3CF86991219C5E6958EE3EA18872155EE6DCC5C8751A8E5D51D58E70F51A2F8D1F131AC0F6C2C84DF9C65EECB524D5C6B0A3AE164769F9E009FB410A6639769DD81F9D4239D909BBAC85B34E5907DB37915BB486400B1F7B50C4B68E074B45F0542DFADE9D9068F10A815705A5E2369938A762C5CA2B2B1DFCAF01AFFA4E3336A345E18BE0479C0D162502910F7A84182B6CA4AA63BFD6D134E1133DF83F986EA0A8EC693F071F6EAE6154E8BA3063D3317B4EFC06271FFC5A93A9E1275DCA926BC345908DA05D17021A76DBAB286DFB4BB91FF7773449288E648EE7130EAE110714DE15FCE80E85697E6D9E603B3AC3C0BABBF967DD4D8B76DCC579310721CF829FDAEC506A61476AC5042EF3A3F77AA172AFD9C2AC7AEAAFFD35C13C4D4CABAA3B9DDD23E8F354050A165ED57615B9B851F4B3E14EBCB9DC26C81D321BC6B27CBC51EEB2D80B365F9B85EEECA96DCB6EF1C1264460C4D2035C083110CBF32328D17C3CBCC2628250B8781BEFCF7D469864023314C5CC44CF64BE827F13C38D93C94601A36C126C55288D661B30C791E0BCDBEBB76EFFD76DAC459DA4F47524CF247D322F5F1FC5D00FE75503E1B14E896D8DB144B5FE9B43CF0FFE2A80E1269A54D760972C1C7D587FCD10FAAF439F4FBE7230D59138046622E2F67A38EBCC9E3ECECFDE8C14D3217B62AB41A5D1E4CFBC529B79C08C39849A67029820F325710880C5EF05D387489476A506747CCA90EABC7ECE7E5857DB5D27AFA826D4B12B5A4B67E0634BE356586BE9E03D3137171AB119A06FB8C32CC5F05F04954CDB6AD8F3A08FB557BE135DA21DC8EEB06262101CC06424839638AC5D494D6714E6DD15EC107B8A7C352F5E7CE49843D3AB799D25F35EED7B72566C04C4EE4BD842403785E937A65D9F336076BC80BBD616D3A3E45B5E3BFF9BD9E2B06A140DF852C79170C26F290A7DAD53BFBDEF990EE99AE83C1618E72854A0972A5586465870702F514362CD939A1830D32AF7CDB899BFA5DAC9BB2B2671674B05EF36A98F282A304D22047886026012596A982B2549B64EE617D28E361B7318083405884342E8350CA643089484BFCA519A22D7B2E98CDA5DE70D9BD3501F2C9956BE7758B9D80F1E8E6455F1383560355642D45CC5ECB0DE099048D3B1DD9D1C9564B1B12D899EB69B1857123D5CC45A5BA15AEDA463D774E0B509383C3C07191A8028C68B0ABD677FF68F69C17B13BA77900CF2D9C4C51BE6E96F57333614153D7199177286E971DFCE60F8D26FFC71290D475B5C629C8012DD1ECA55B9329144C5993D2EDB6ADAE9054CF040541B7C84EB27A06A51B83B74006727D5058C163E58B6F886DADDF21473CF1763E70A007512E327DCA02271986F1DDA2A419345A915F1934FEA91E05CD2D8548165384A330F6FCEF8BE7912299BFF7F773C99055FD951550F7A1D43DDDB36C55CD289047EEFB02874E91D5E15E14DD051C43406FF4DBCC038D37CBCA2B1D81A39364CE7C88D33D98B8983EE339DDE5240FA71411259055A7B2F5A9DCE743A13D55A26C62319454BB1390483712129BE75C977DE0CD8D23B5B9EEAB0D2A3A634BA958E6FDD6ADF05B8457F09400B15E9149BEB00C2A2ADAF57ECEC01DB54C6E00F3F4A975C1CA747707750319018BFE2F92CC424A71C918D1EF10DBA889F9B05105A3173ECCDDBF3348C8E67311335D2DE5205FA55A36A4CDD01E456E2CD3E210F6E47F75B067701C2FDC1494EB556196C3F72BF71B82D972F095AF9AE311C8FE5D09B580A67467408AE7E06993FC27FD90972FB8C1E2F39A201398A3CA0B04417F7AE425B616B118D4209DD4D39304639D2097959798F21C023A96A629470E60A3DE138E2AE52C4AC095F8DC1E031450FCA7E2B44A921FCD65F8FFAEED77D0DA218CE3CB729D11D6355535ED32295F73FFD35ACD56B64F5015F6FC1C75195B9B0CAFF002EE7701CD6158115E66F2251C0AFC56AF1C0FDF6F77D15C529B9FEAE3EB32A13814800F445497E486DA36FD585B511C557DE9080C967CD73FFC8DB94BEB5B7CF952F37D406758CDE79F52902CE11A004AE079CD062859B5B5135B93CF08649EC5DF33B0CB550CA484C9C20F8134D26C71EE5DAC3ED263107E278F973F5602C833A6A1010CBF1076327F421AB182462592F63E86651581749646EEF2DF7464F3EE8867CD00E997DA4785F721C6D37439EA13F3F3FE0CB09C14D85F07C2C57B57050B888A38B8E4BED0238A0A1AB1AE058EEE9DC845E7F70DCB3D9B5E5599C82981758B96F61CB2044D6BB382D9BEE530DA0D93FC1B46A882FBE5EE7CE0415149933390E14CC0E42ED5B54B694B2A7B272F6748930BF8AFAD4183EBBD9695F0F9BB259DC906CA085326E106725A2171904BF31153B37EE6F851C72F347C2DEB5A3CE043FDA09344B162BC01CDF98A2E7B6C1D8FAD06B4BC626ED1A85445A15E38B2DC633B647622551C1415F4D9FDD840D43219FB14804565C61EB72F57757FEC96F602E2A7EB65FB1A52FDF6575AB53C750AE0D8A84CA73D1B28C5353204E772E3535A8E454A65D1D3419AC6369905DB5DBA9D092329696026970C270827DE388FAF4A07174199B93B1764FD44500476CA5090D693E1CED742E0BF0732F0DA402DF0F32113716C855E10A5178C35B2EE6C0AA9CF040905BCC4CD822515EC4B575A335A7B1D178419A2E957D8E7A3D48D593B57B656D2CB530740D88E608ACFEC6A2C9EC7D539F6392F0A00D93F08530BC5EE357B76DB3E80F7DDF7546D5C94701AC299600614B58FFE484D48B3D6D2D08F2498B1CA5B42FD3E9A0C06AD9F1A14E39A704D9C5D33B982953CFA0E645DEEB1218A52D25A312FDC0957A6F7DEDF2172FC1E1CA5DDDF47149E65D170943AB705383A33B8E35F04E901019020DEC910926B98F35706F4B98E5192007FC5416076171D42787578F49A6E4E705D21B3580434D490CFC925EF9DE975E144E5DF8BB72CD8D735AE6E726DA30688A0DF3B6F96C5AB0D61703CB25758DEA132E3BF92AE3428284C0EE32D32808C9C2C537B3998E4F7719F0412A54FBA090435E7724B7D944BB26EC545957F0539A20826C38A61CDC6DFA253FF37748D633F0F36CEF058ED13D8BF4045CED815E227D96FC3875185A9412279CA340CCFCE77510702E4B87ACF05B5E5ED8C28A2679456183F9CBF9A2AC841DE9434E3D6F2AD44BE79BE013EAFFEFB9142B1748B412DCF4F802C5F5B8D001A55E20AAB24BCF807C142E930ADBE874C65129D615169F4FB669DD9ABE12846339718E1B57355833247D020CFA8314ED0BD8CB2CBB48633775F5AAE6EA813F0EB01227744C8F396E3BA10E0A8100391D5B9996B6BD003A301A77E6049ADB85F211F2446F4226C82A1CA4FC12BBC7BF252627A85227E7C2D86E542C7F9ED4BE504F67FD7DC10C04712B364E98334615F300335D057FB0E5076146A12DF285615340805300A799528D0C1A08E4803B08A2DE67ED40BAE18B53F0510D393FD374B84898D3BF2DE716E4C801EDBEC6A188C14A1B30C07B9E9F0AB2D90A65CDA455357A1FA489BB1F9F1300799FCFEECD32824282E712785B47FC2BCE539F66F3A5DDFD29B69A9E6D2192B149FCC7C356095D8FBBC78BC051B025211BA0CE4E262DE40D060E5EF7EC50726944CFE821E78BB93A955F982D6AE9B0D50AFEC40E7AB35E13B507B6CD0DCDEEE0A9405FF2FC59057D54929EF811CF49844D979D7642EFBD82F346B7240A8B6B1694D62DE877F12B82901A77867AC424FB6954338B10C0AD5C1CEB53DAE5659BE765965A39359CA3ECEB12E2575ABDA4342E5E1D9F7B55418DFE30B5DA396703970790FA8650E9E7B37266148636AB806C37DBF13A7A52789E26C68E9D7C5FADA232734D99187F27272F1F4FA6DCD56EC5D02B939BAE96777343512323F063F0AB88E7D7066AE00D9279D0ED64472883EAAD81AE60543290F0654A46B45BA9B1863305CABAE8292B6C564DFEA62F7310232A47F9CFC6AA8B91F2945F65954D2086D17D53FA9FC9ECC8C41F22D97891D215F0151DCEB17EFC07F4C63B9467A3483D4DD68CD5F2DF2E9D378CEDB48EF03B00B81020E2228CD4D45AA62C5F97661FA5B4E916F54A666DA44AA1576DE1D7C89A748824015BDF74A12734B244EA02935497839C52E5BBEF73789A07C7F2C8382BF603C81E7DECCB13E68236ECD57F5EF7E7BB0DAC2F0F599ECFFF849557E810BCC805BCD75AF90FBF9EE35D7FE3A9DF4DE8494ECD5A024B88F97BCFF9AADBD5BE6415F027CFA5A95B96D2591E5DE28E232CC684D6F84424FC7A2A98AC83257EF71CB8B66193180E42EC526D0DBEA3A09AC5347AFDF8D8CB4EA2CDF6534F3173638942523DDA5CF131F16D6C10AADA281A9433A8587630689B8C99EE2639F7500663318205B629B387F34A597F155DC5F4CE37D69CC15E16C5A52D5928ACC20DE10B4218AE58147837DCD502326DF7F54580A781A4E3DDA8421C52028B40B8CB6FC841BE9B658CD8D8AC78B5BA6E1038203105A604BBAD99EB99E87A6856B87A988BF999E62A1F621ECA48E1846F9FAD4DDD02C45D949215F2E5CB5C22C087C9BB76A1B3AF2829FA4DF12651AB40C537074AC559FC7726142D6B393C2C84E90FDAA4B237FDA37BA6E907477252C08AD2BE42DD662776F4AE6394EA84586F2FEAE8B408690A3011BEAC8F18EFF2123155D5901D16414AD9EFAC9D7C3055E1F788489BCCAFA7E4A50AF501006C57EA1507865DE034F1EA384DE714C7C605987B204DA4F4D6FA5B9A86C7787816C85F96101BB750E8B972C29BC56C66C6B114D95DCC75CB1BD96F4FDD810238528173C69D3CE02FC7DB1DD7EC783A909BA8454076BD664788900499BDAD2DC6DD82C5B78E0BFEFC5672C1B011EB576F952B7CB5B4D6D4CBCDC535EDFD3813B453037D1FA1CF1B2B13AE5425D33612B0DE26C06D8C92A1BBBBF58405340DCFA1749126B570C2B9A4B3674D9F3D93FB6698BE86E8DD15A65548DC08575E55CBE5C1186BB8A980FDB81DE7D3F5D0ABA4EF0BEF7635147EAF1F4F4D19ABA3F351AB3E9AC9A3AE83F82F97F3D503F14D97A0AA3A96A442E2B5AF96BE3EF0114A52BE6EE75D17E6D7742B6E34DD2A4FFFF0242E8CE2EFCE6C0E37BD003C75097F074D1D6288561A9D6CEAFEE82A3A46D7D708B5E482C6DCF6AE8EADC5FEF1ADCF536ECE54F633B3614ECB8B9FF7504802A96B59326AAC7C53C7F2116642EB2019340E19E6D05E79476667A52BB41DB42B2D931AA4B31F8BA83239F7F9DC659A6BC9D473D519213784367F8570E0B10958D789AE0BDB76A5FBB83A5B6DC76481A7CA594D1E1B358CD2E611B206A3EBD1B40D9C4FC75F48A5ACCA7ABCFAAEE8D6F38FB79FBA985E9149F9E08058603575B7B0A9E165A406B7A8A91822E897E823DCC742154CE8862322D53736D3CEED2F9131C0B221B5E57BB4F189BA89EAE2D017D9C4A5A2F164A2FF5727AB7477BF944164593765700F4D5343359B33D40732C3FE6101D6CFCE2BF449A10EDE35535DCE08147554FECBC5ECEEE440AC4CD5B6E0AEC1D4ADED71194147152E12981699C74A4A015E83BC0E046610331F0428703179150492B6E0938F35C25C14A01005D232E830D2412B5FA00AA94E15128F2EF837B0FDA1B97F5ABCA2C7F50BE1AFAA607C6FFE421C39C7CF579058E0F552EDF29B238E16AE16B79963A9CC79C0E220468FCABBF396486FD8D1665F708769E4EC29DAF2C0EED3A84B71BF5E9BC295240534CFFD57F68EC675589A746D7A690A26A3EE3D5F171D1E7E6014CD18FAD97D18E25C2A00F405704C7DB6D629E37D86AFE453DC7C331D6F75B56FBDD7CB12BD96BBFAB32B2B8223CF91E77382C997A79C087E0767432EDB331C49CE0437CF7E11354EE1CE887D7A3F7A5F5E039CC34094391A90535F996B8814092817CF4C4C57077626389CEE7BBC84179DEC56BEF93819E052BC31FC6B64E106A799F3FB9E313E1C334C1B49C8AF90FBADA0026C60AE137126A77EDB8EA1B45D7C3E7CF0CB1781B1D61EC15A8344E4CD077C3F12F04893836D8D7AF8206C99F55E9CF4DA24C0E19343CE6C25298A1B530906AC2DA839A35E59A2577CA2A40B100F3E8BCB9CC951D642ACD9A15111AB4F47D2B3632457708208153DC6BE4FCC4189D4B1C68EBDA663865744B66FD536453881B30B841E4DEE5DB9A0CA076640AD291E3204A9931233B3E8B68A9A3216B1F360A45F368E417D78FC5BA8F12E8AD18E2C93ED985D6311B372F6391906A0727C22CBCA5F82FB081F9573FFD41420BDD30F0E0C6CB0ABFA0BF7B44BC0EE55815DFE3BE4F94A761CB57E8D00D033AE4C7027A8DD7B01F690D772B76B1FD82A8DCE3FB7A19ED645AAF9DE26B16FDA08980684D26BB834914E1EE00944EEC991F9A6B31FEA33C0F1C1660A94625048E82F9EF513A79F551AD07C54B1FDF6D467D1535FBDB00E3CC5E62DE622FEEC2BAEEFF6F0B4B14146563CEB8303BAC0FC184E0711A9BD76F9B18B85EAAE13DDBFD1AC0B42983C16B157D6BDD06ED7E1CA63ED26C927C9C68B4E4208B50A35AA9AAABF3724DEDEBD0191BD2663FDC93F3190B59884DE71ED430E3B15DB9C23C91B52C1B6AF236E09898D7FDAFB97E0E3D0C3D108E1F4D04BFAFFCEE4D0824B6205C16F8194E6690328314DF3D0D362C9F33DE455D263ACD83DE83EF6270BF116BB5B3C5C219469BD3B37B3F69F829386F0FA35DF3246A2E5744BF83DEAEBFBE3CB941691F5EC9EF4843883296BA812D28D57D72C658642C3F66F40E1E37DFC87B995524253631E3281295B2AE89B9E15A47D3B764361686B885F478FBC24DD00B251DDB36E4114C0760E0C31494C8BEB7E31F40FF8B2C259D4395881F93F7523851D05892DAF86D3E7CE9FBB634E8AA6C466E9540FC55AECD082A979052FC3D985AC25B2D305E3609BA0C46192E8D8F42E84220A72C4F1DF7BA67D88613B954817489F555DEC826DB18A23808A2202B104B95ABC12245A674AB449DAE47A37FB79C988C4F4B1D6851F85F336FB617CBF48DD2500979F69A2DBB48476D157266C4386BDA4AAAF9CFBB2A8EF445FD8399D802F09B6A716B5A4535207C22E7BB2552096A9423C01FD04568B8E1017FECDD75FD259B0653684426374D37685FD5BB5D5816A5E3A6ABD9C649B1928E7A1EBB142129C4D810823B12A37EBDDC5A9F9C4401C98E1EE25B79E2A364E025D0F9F5402EACF58185037D19374F03D9FD8A7F108B37062D5C5449C956C1FEF12C7D25C89517E8C6D84772D2FFDAB3832E88C814A8BBA462FE3FAFDD4ACFD2FAE46BF995892A2782906066425BD508BA0E1DCEEE0A570235CC5C4131D7FCE025F2A3DE91387EA0B5C64813DFB37B03861090F48CE4B2013AD2CE79B48710A9201C6DBFDAB71957077CEB9A40FC445F42431CC8D7B98FF415DE916EF46BA88E1AB3D165CB5B9D6D163EEAF1BAF63C20C635FBD82A8ED252F3710FA0EBC59A68F81D4AFE3605176F47785CE8B156AF7FAD052A9645997BE5CE78313A33CC55E646A583AE9DFEB5D7B0BF90FFA49A79D8B1CC25D2808E4FB778EB9FF19E1AC700135BE60B6CD92DFD6845D9D6952C76CD651F9332984C98E98FDB0055F8AC2474D1EC2C73B04B38CFC1893D45AF50DC8FB5A164528F62664F82BE323D8257B28A84142410AD3C51846C99F48F516ADE0774B5ADE6F92FE487CB0A06E44863D1FCFF58C4072C80A816781287145B2C71E603856FF606D5B171375DCA2AD7F8C111563DACFA40F09697CB7AD9D3B22250F846F89D99BCD11B26A23061290E9EC9838C25606DF5917E2FE1C36B1FCAA08F9AC98723BECE4129BEE1B241C19667CFB1D73A3A33D67024F66821C3973DB80187CD56E71029AF76A0EEDB4320317844A2E83B6CC319CD6A85EDA3D44F7D2C78BED76618D393A42FB2ECB3961A78ED0F2ECCD35D7AE092DCC323DC52640A6B39A3D70EC8F1E8A49BC1C0AF8DD81F00AE5B0FAFA35ACA30EB724B1B51199CCB015ECED5F417DBF97C0B3CF8AFF1B79AA0CB23A051CA1FBFD8EBC67A039E6140B90233FEF308C1F9C4C00029DA1E36214582D75337A06FD49B33ED3DD66F7058EC421F00147E3E75A048F2249EA41FEFDC710AC23A5E76FFB7B5294B347AC2C13655EAC969F8A945C504E6943CC732B49B9B6660CFFF9B1AA45CA7B896C1B0B3B47EAE74EC17D2E34D82AFCA08899C0BD9C3FBF647092AF5AF64833DEC09459AD9988B7BD9EC0F38FE14B21AEAE767621B5A9F327371B4121FA2F2FB56E65B392D09AB8074AB7ACC1EFF3D3022746D8119233A14E9A6A107D16E6B86A636C96FEDFC3184D7153077A82E5E04F61A42B4A634E17DEB0A05B5007B60290DA72E791BFEF276617FB144FEA6C2CA666E36C83445077DE4EB788A0CD6064A0C6C7B79049273CCF04C44B373501064CFAF7C0FAAD850C81F064AD0938AD87074A68E16271B35F4B36C8189E9FFF8734B1F2C1A2BF1FCE4B33558A9A778FC709790CD710DDB6B8E5A0E0F5BC539FA0FF1E5C83FF19DBE6C49D6DFC3FE5B7121FC2EBC90419D29F2E29CD0C72F655CF2D12E1B9DC3F0751887CA0ADB956D6280495B88849AB8915E327CE56B46AD9DAFB678D34C193A2AB2736DEE6AF8AA975BDA6D5D6693952056B23512ADAE9BEEAC4BAA093386D930742D37785C79A5DE14ACD5F4A90C41C3F488FCDE1A4D3BA18EA023B27B2226C89792832F6D7C88129BEF1A7E8FA378E413085F57409FD22BEAB2653F298FFC01F7FC4F602B4F9D49D45B9471A7DDB7003898B8E3543588EB52304BCB509565CB2A48FC36DE8249CB1089F7115C892FBCB8FB6924425F1CF2C30B8B38509E99AE30C4213E11AC32B2CA5AC5AA1881B9F042714C043247F77C545E4B773C97805274B0CB5EB473953EEF2BD3D4CC21DF9AC49C89FE4B7D6A16DE358FA505DF9C0F31D8BEC1F50FA54404AB14AF30B2D25D890D14A4FD32F439E0CC174D58513AB263201525F13D1AF8DCFC39D9D6DC4903E54BE3491A95B174A6DDF493A4022C732FE06061A64731AAE936BCFF6B3AAD39068CCC7323FAD9695C181241EAC222B99F6FAC52A4183E5DBB99F189BDCA7266DFB6EB7E240B4CF6EF419218A0BD40B941F6F6F1B2028DE5CDB7A4DA2458A0A0AEA9CA3C0F2D95966F32F611890561A14E12F4509B0B38451CB5B16F9B4743FA750212FD1296103E050BB20E54ED0DF4FD081B21B3F6E4A27B059FFA448B47D724888430C7251244FB6AEA4F55E264491C3BDF85F5173B977219A4B4EE4F10539A242C36349AE2DEFCEE0E680E52937E1AA41463537A439BBA79C37019A3889DE8BF98939FEACD19BF80995730EC0C4E6266B71919F4A31B4FC93C339FDF9634CD40F04E4712C468CAB88F3D230AC2D2EDAF3E5A89A4CA8254BD7FEA6BD974A8A397C8B4F4B485AFCD745B7FB64A1615BA13229F90C4994D42556432BEC27185252BE2A06CB3142440EB751B478E1CC33AE907F24D8202AC638065A55A1B0EBA2706804AD349329A19B2A1AD7B7399887DA77377CCAA0D3E4FFDE54DF4BB54BA7A5C4C334DE181CBD788238C39C54D83ABD4680CD1A26B97168DD86F628E5BEB9328906D23D682A59A02B3A6D78A79835AB1320B0D8C80361CBDB0C4191FAE821CBC4D1B06235BEE2312B64E45B5332A26CCDF314FDC8FB3DA54B6561FB685188C9183A3B911924863F9C7DED5562BE104D271C55910D106A7E0BFC1BB0BBBC780B98BFC6D2F8307A66712EA8C460B812DCDDB3ABF6329BFC9817FC18502EA58ECA4B8F0742CE343E3BCFE11F856AB0DE73F0CAD00474D91A3818A14F08F2479A81B7CDE3A5C4926D7A95BBE40AD9839F2B2735464FBB78CF3811509D327E53D1CE14189FF6160C43C4108233B6AAD7D50ABDA2C1F8C76D35ACD5C8B830BBEE240C0828C0E0033B924DA6AC5E4AF4C6AB40B1CC6C790962C04778058F8284905B48FCF447983238F804267C547F9C74DD88418F3FECB6F3C310915DDF02FFC4ABABB52B0D7905296D5A459925E41B4A171ED43C115B2E1142374A2D7CA895726D8C9BCA9CE0C1C383C98EF9F27FF9DA34A514FDB356ADDDAA45B63EC282D2D9B285A7B437DBF01EDAF492FD61A69FE533D7D7EA68739FBBD3A15EE5E703FA32866793FBA4A574864AD99F2F51D67CA239B6562A36935AA5C3F250DDB3AD2A9B260B0C744D1BB318AC82A52F08C859D323FEF4C486789C7ECFC4E1F4512E4094083D0D373352A1AE8D76791CC68A04F83266DA849AE58FB84B44DCFF05EDFBAD397CE613991FA4AD7D232B77E0C5D1613EF49286FBB49EACB121ABD5E0DA5D0351F9FCAD64E695DB8B6C3F42B0D2A28A76D7593C3070756FE0783201C2F82B1EEDFF54A6E3C53BFCDEE402DD64BED18CB9C6AED0DB17955DF7E254E79FF686BBD89A1FBEC45036914705C93640A75C2CE2345A43D66AFF52FFABE340924AEC0E410C61EE452B2CA051078A836617D2F397CD2E905C0EC9C4EF7E333F1BCE5EAD063188805023947E70B5CDA4C7EFCA9CEDE464B597568DC2FFE45219834C90B77086038EF687BCFF43796F7994F429A703063CE5D147F0F01E2A98654F454E3DA99D715875D6686BCA0EDC907A14C41B6D57ACFA6A6E05C9E3BE7AC9BAF68189C7F486B26D9713298E3A433CF6BA01B8387EAB7B1E04F1091A9DB1BD8C3BE9033826A686C5DF11E8DC5A351989B636F4D1A3F0E4E2AEBD3FEDC2CAE7AFF884984189BDAED23B1C18066BFA2B49D6B4A22C849B47C1FCF49927B213613F9A4EF377AF7C8B6552A8026CD287CF1BCDB18A983E47DBE482A74E5F6767D6AD8FAB9CD06EA154268B862DC240A35233753690577B96AE535784D9692FB254501BDA9D3D9A6328F0B4B2ED872D51969FAD1AC8DA5B7105DF50C62A0829819996308E3348AD08A88170E255D50F44FE3761BC6E2B210EBD90DAF05495CDBE4BEC6460653BB98B9A49B9C872EBE15F97BD5E42DCF41B1160B406295678B7964564CE748DF5015754908CFAD318C3BBA3FD41DDA248D7B0DB1C071F705E9D80D5EFAEF33AB972ACE9891DE6CBCF6FE75825C0B99791C0DF6A1FB86C68EB6940B0D2A0DA6E710A39410458DEE30028FEBC78CCE4494C374BA8F2A118B1881F353EFC94B847AE45B23129DAFD03435311A3B93771D48A9ED15416BECE91E9A46515B076621A8A371439F428E5239CB744B4CFC001B1DAEBC378C7FAEDBCA28573FCB5C16FDCDD65C1D7B6B31DA4D4EA4DAADCBD553ED7755F2CBEC29745C6FC274103AF25F514BA8ADF2AFF5841F8D4F37BF53674D67EDA2BC467BAD5788E614BDAD04001544A5E3AEB1EFA1C182175A7300C92FBE948EA931548E9894A76FF5119552FC225AF2C771A5BB321C5BA50DCBFB40A505785EC7801B14BC560BC7AE02EFEA4B9967F3EE3DE4A9972970B22A7E8AEAF5F9A39BCE663F298FBDB3F17C444B04D87F9E1D0F553AF41A13E1EE4C465199E0C41DE48B23236D28F74F40EE065301869EEBEE5746E2DEFF05F940A2A1534FDCC5528C59E743EF02983A47F80A5BB28E521BC7C6AD99247177289B178690BAD4D25BF448EF3E4A27E98525DBB498FE0AD31E58E2B4110401FFC63239852092BD1E2303172877723D57C124B0F007980FC0BFE6F9391BEA5BC53DC76179A59E5963C213AE87075E9BCD501F36A02B31C97A35C3E9133A7CE08B5CA57D8BA980E8F7903F2BC04BA65FB4F2C00A35FAE5682A1A32F95773DBFD5789469BB8DC22D30B0ADDDF68B0682A2D9E9A8D228A96FA6A5A6EB4D23164E688994D09816136000DB79B68D46C38EB382BD9E9BBA2A81FB20A63CD4799DCBB0E6B85DDEC9096788BE73DDF50494E9CCD515CCD753ABA2CDB69972A276FC6A405F85A51CDD17F2E7D535DEC8C0C0B34147875EDEB94F0653D35BDFD7EE91108E983A943D4668C0FA3BC01E5581FBC9572D776752E65D8989FFDA22A9AA502981A4CB791EA2F40616CB4A4042581CE829074A53DDC5FA681DC793A78572E941CB2408554674FC775728DC5ACEEB858FAF01C57B303E9AE3F39C66034D21B33BB613AD53525B1D6ED3A46501E4D1B271A5A1D240DA2B94ABD1157B141C65BC4ED6B426FF6D260BD335D2F4ED3593250D449A21562609CDE59FC8C791F169920BA4E8FD62D0A12BBA174359D5B5C269439FB0589573298C234C45D82A87F390297407486543D1827A10DEC0CFD9851980F495B185DEAF648475EC171F57987AC2021B46E551D5E1FFF01DA50A493579FAF69FFBF5F2B3C62566BA395F85FF4436E64E22102692AE7FEAF05E532F49CCF598069CDD56863FCA3AA1851836BAC9C57BF1B251A940AA03DC6533540C0A381975B165B792BAA197D765ECDAC596D6102486FC1B305380E2FDA2D4EE23A0EA4F499EAE94E1A0831DDA80002674E571D7C335C47DD75E1E47616126A5F1D8A407B0325C43A531B9D8006344215C6191B785B706F630513DB1780364576C7915A892CFE9ABE9FE4BC397F04CBFA457B67DF8074AE3BF571C6298527DD0B564E6E3BDBC7969E2C2E35D5AA34145DAAD9B61F3402DF7FD96EBC8C7556E1E6C4B292E1516EE9B2E0AC3CC239F2B1AFC4FD9DCAD2E940C878CCB3276BF4D89C8925EE3D2C9FC737FEC2AADD349C23BE4BFDDF0C4C4BFA6A602D6C0C6C42F2351FD980410ADB5B3F94D88783C192F0E7F128D6665A7EC03485CF4EF91F4BAB231886E7DBC1B2E04135043A84BB364173909F395161E78594D0E7B73E7E3C014D917FEDDA0F21F68DD0F648BE83F118EE2A72D0087AA3ADC5FA39377EC559D1C15884036CDA256C0B79E735896F84EE8A99665B1A6757E07876C77FDB99D5C1679467BDE25C0368CF7EE54CC5FC250C1082B8074000B42FC5D59B3FB8345C72EFFB778C840CC3339561E6018E25B63E01EB72716D4C4034AB18A958D31D75864271C144B236CFEA88A2B8E01E5B8AF1BA856B1A07D2D236794315CD5811A8BD5D2F9605D0D6FDBB66332F84AA07CAF38F053D65A270C362D83754D2A8519274E46765D138E8FDF4BC5F709F54E89AC6191677C7A99480588FF4F049BC44AE020E07D60062AFDF9AFEB1C5A9B08B6AC31E7C896F89E2503BE2275D97A26BC98FB9DD9A5FFAFE23891ABDEB3F0A45DDA486B07F1500779FD5DD8DCB29B2C2A6D96430ACE44471489490B3E26662641D967DB8C324D1E2EE4874B9B523F17DE5C6C949A7B73AC7D440C2DE801A30043D060CA4C279EB66DE7E11659A83AF0A11109EEF252D07D188238835E594FF2E590D55BD7125B65BC74DAF681A4BC3EC09D2F9929C7BCB58E78CBDBB5000AB1B88A4E1B3B501BD07A828D0AA64236DBF1A9C6E8C22F891B568FC35874B8771A83879FCF411F3B030946048F4F1342C0F671DF20041E4C752B6875E248ED46F36FDE641234A04978CB47841130C6C565943F72C575F725D82C147909625AD656036F90BFCB184B865ACF52FF83BFB2AE91416973598D8871A691ABFDD7407142D71AC437497CC4EE0580A2C1615EA2C0C6F14A1EFEAF7459E39DB5B7EAAD55ECC65D941E21E890D9E60E9AD3C4F76F9FAB1959F9D4665EE6A2F06BA95E1C7FF4202C994F35533947F27E30A121C5A1FA4B4192BDE9CDA5AD8839C0E63CA41D7CA16AD66F34C5CFB93FF3410AFAC530CC48628DBD0AF440033EC6F10043BF46564187DD01E8173D5B8596A3B4EA074B5B8DC3F4A3609CBE99315D2499C63E61E8A3B36E7B38E7C665014AFCEF5DAC9316DCA695539572512D44050B577D38E4AA54FA31EA9241A1545C357B38333F73AAA75ED8F348D120232BAA4C808351B22D3D26C7BE243A1F7511160C1AD2C2D4FB2F0C09EEA20BD2C255BBED2C570BD8EAF77E3C3417678B676B5439434D00B7260E577C9E6C5A068AD82504CF4C91FB372100DDF0484BABA5329B93CA4764A025D2031B55B573B0153C765A86CE8F7D5BFA1BF40FE177729D48E7689DFF7AAF38F336FD542071FABEB7156EFF05C31F4AF9ADEA3C28123C10D89E1B9E5F2726E6D4733C7A04070E4A928CD40F499E5C8C7A8BBA1BB9C821640FEC16E4D4745257D2C27276EFB89AB8BEF8D0868ACA957937FB337BE808558408B8F9A84886CF96E2F369A251E634F9073F27DBC86FACB8F008A57200831F6B18050329C392A378B325B0EC6C584584DC3C8E2C6277EC86B0434B745BBC5826B62051953A8BC100D75441944F430EB6E0B4ECBFF2F3E452C615C452C0F777E8FFA308EB3FC3CAF4F910B029A8832AD59C38EE7FA77E32D2713C1490A88DE4EC81E8DFDCF014D6AEB26E6B5134FAAF43023827A118E81B6C9DF1FA3EB313A3A8582D9C62325979434DC953B343DCE224415DE23CE24886271724FCBC1A30C8EE2E4D690D3CEF53DAE228579A1DDF8A5561808C530853246C8A9F0E486FD9F2BA98064E4203DC6EBC12CCA9381774CFC51773D9C3B51FED3D89EACE310C481FE236D6098E8D6B270491A31E15C902AFF30D6F948047D4A5FA0A9A4DA35B00814A5DAB1390E828AC510B56C6B5A7F9A8CD11D32384E238DAF7C45F2A5624ED77076796D48ABCA477D33462A8B61B03C64168D7473C05D162520D0FC101CC9F0826A0A663F7C6EAA98DBF29A8559395FFB55334898BF1CFD25F75A5664B4115ABFF11CCE4CFC643D16254DDCBF34E8BD627B4F3F9EFBE048300A3C656CB9CA12639286642E6D5F2079C2D72EDC4DD1FE95CC15B6A0D53D02314E87E9126BA0BAB17C6D3B2F104492D705AC119830AAEAC5BC7859DA8472C3D29AE7FA1995EAF93F1C92FA172A44D4FB1792B620CF4A69AD9FB58570774CDF8A915FA7ECB8C3CD3A83FDE7EDEDB5FE6A9F404E44DDE4185E418D6104ED34EEF2F818F0B46D46AA34AC60AE6A96003E9082E9A9B15A93E0E2FD3B6C4A8E58C01F4423B7983E83722C213D3D776982970B528ADA7F97C80AD06174982B61F9A5791CAFC16ABBD66FA3CDA59043E4634F6CC8FCF15D91FB1D6FB3AA8BFD0088E5375EC55DFFDABD4E61A917B3CA939DD19680031C11F5DA35EA4EFE6A1608AFCA2C7059CE245789EFEB3A50E65D4DF3F0DA4174DA061333DFFD19FEDB5915B337739C46C81AFCFE69EE08BE70D2B3F01949C78E24373B941BC568F62CDD2397480A5E87C0E4E1C2F15B635429C21F414B3CBB2F4B126B3B0111310D63655C3035BC4B85B2DEB44C88EEF0658E19477C531FE0DAB759A4996DAA0520E7FB444B0CD6C1A647A9C3656F41E98A854EC2F737658BFFFE1532E6193F1E6730F08D56F34210DCAA8010DEA239AB21622C8DCEC42D35779FA7A3A499940C0DC3F8A04B3811EAA9E34735DE2B0AC3D7982F276AA2C1AE360AE46B762700EE9D057599CA8CFB91A7A1AD248B5B26BE59529E9DDC349793444A598810172F8860FFDBA9255FADC93C508E021E2BF38E32E43F26E5887E1811941680E2A04A4275AF58556E56CF091D2236F614AF3C2D5EB568FD6E4F9BADC4682CC1D98092E5F99CB7A89F95BC80BE961C341E5BC854943B70A1DC3FB9CDC9F79C676457FF24BC749D797A21ACE5C1622BF1730DD5E47897BD9D973D9F30C354446A37C2A23F7D55899850E131B8A44C8D08F6C6C62EF243E72013CD8A266636778CDDEC6D2213679B59B9781BC3DDF52CB845A1AF053A3817FA4ADB057DF765D76BABC001528C108BB251F397213200D291D59789081F0E06E6AE823F5140DCEB86D9DFCBC163FF330355164C737ADBED5B798FCE81D23CFCEC03015DDC74E6CFC0051C7AC8B3C4D610324AE81C6EE6FD9AF0B8079E1653FBE5CDFF7A76CAEF3EA08F3981A7A64A235DF2832E718E0076E7C3D133496BA454A2C0FF590FF3DF929FA7751345170FF4C1C4E1635876529977526FE7294CD44903996B7B7673EED5950D200C89A0073759F14558E56C14FBB93890FBDE01C41161B7B9683D8D9FCB8D5FB9FDEBB3E3B017F8FE6995C34D9ADD61C10CC91BAC94FF73CAD9D2DB22720367A27FF4E1C3DF9CDD4E5483019CA7259E86DC1E60048AF282BF9322BD5A5A5E4FA6F46E785039A92F48983E889C444BF92FE96C7FC71B15A2B50E81EA924702B4D0D7B77A78843715DCB127BF55FDD6703AD72A996D83B87D23D0508B23F3E600F0676DCC64603E970CC55D99E1657B60272C5944B5B39148B539BD41895B02265525520C19241DC77A1216426B5ECCE93779A8B8F799916CA4347374239E5781DB4FEC47D94B7DA42D3CE1BA516F8BCD0CE41A00446CA16491E544871BA32B7451C95EFD7657B00DA82565F5B03F3A825DD4479916FC6E7A378B37C77DA089B09D0333F719B9FB0C0697E8575D93D10302D2FCBE6183A5BFDB7FB38143BE74017A8398C0209BA5805F8C84062D49BA2495FA2A4409CCAD2E67973C0A8D086197D1A74C55CAE0B315F8A315BD26106FA85C0E56C20DDFC243DEFD20DD635114CAF239E3CAFB5F720D44120E32FC70B2DB14797A47640F3CB9B05095B8D5A8340B5F46E91C0210B55C3B87FB4E96740EFEDA6503365DF8EE902CB8B18747706B7422A5A292F909C1340EBD5BB5BAC5CACCE6C68BDB2E213CC04D7DC0A029DB881E2CF41CBA9354133DEF1DE7ACA7ADF9CB75CFC0F4957F791C45F0CEDF5D738609CB11385A3A2CCAB12D3BC371583BBB0D350062C6B8D027014E9D867A88034A58835638EF0D812FF353094F0EF4FC20CF4E65D03F62CF92F9A5C2129D67EC3783108C08EF566440B45E9ECC9CA3A6B21352462906B4274013A5E43E577A0CE746C3724C7D7819AFAAA384344020C9445DCB7651F9FF1EAB2DBD8C41EEC4E8425B51372C30518ECC02EB1599C6FF015BFDEB95B4E8168093DA38D3EB9B128B03EB7C5C1339E3128FF8399D72A52D908DFB49BD671AEC97D62A99DB6747E1B15D27AAA81CF76CAEABA7730C31E13821AD3A4BE0F052B2F858A86DE81DBBF04623AFB5DBFE6D1DAB8A62402DC2C496BC63063EC9F2483D7B9A64319247B1E77CDB49D24AC42571F6883AB921CCE0DDA2333D56F991042CC9A6AA8634D15EA20C730CDDA9F758E1E85EA2C9D8698DDF70DD791E184C400B2217089BB72371489D315B22D7B5CDF845E2B1933CC79E278B0A96750C1B1A32123EA7F39F1C86354A59FE0069DC84051464729C08BA719A3B2B18E734A377874CD95AA862870899C7B8358F06D7F924756624B5FA3526B1A6531AE039553760E93C9859343B2083EBD77E886953C37DE03884728344854DF96B996BAFC6FD14D24CDAD05E56CCE42C356D63C413C805A4CF394E9C72C263E1EAB19593351C6CD4F2F249BAE08AF6DB409C6ACA22BDB034E577578A56F69CFFCDE9CF8EF2649085E5BF6EF68D90733EF246BFF6D14E43E86A7A5AE7A09643E0D3CFDFBFEE37A404D86256835EA63C71B2B8FDE1C8B729C3970234D6E179AACD662A85D1A5F64C7936561B8BE993E57CAACDD558EE0C69452093FB12660DF2DC2491C1D1CA243D005010A4B6EC286601D3C999249F2C3DA825F693306856BF04549E600D26514D20CBB2B05219974F697D09112FA3810CD74C08551CB50D3D546C511C59D10B648702FD2458D7236FB9375EBBF5DA47B2020F85289C2EDEA392F573BBE4F29CDBD3612D5893BBE502D9B9D937A022667DE8D6FD57DA85D5D912A06CAD105E8A95DAAA8EC3CE228648AB0F80B5690ED09BE9E0F76E5CBC502F04F7DF981C1666220FA0646DE75B56CF67F43423586ED9F7A922F2FF062BE0470B2E9B2CFE4042EAECEF0948780CF344D80C91375D1186D224C94595EA8FDB885826D762CD0DE24EAACD3C3503980EED4E978DE85B45D9572DC6568CEEEF445FB627464528571A80A961EA39BCBF6DFC70DDF320192C730D1C48F19A203DA648170B60323DB088B7640EC0382A72287D8FB1241011197DB12564527BBD501AC000C1835DEAB9D1D84059F8898E9B8D1C5861F6EFB89B1B96D015B42B7208E9E39E1C4A90CC86A93160F65163A485C34B385CAA6423A212DB92083446759D1DBC1653DA3BD2E0CF730A601C7F7EA06027D117A71C533A38316F1507277DE38DF954EE88683BFC803439311F1BA414E596471D4B020E0BEA0A4F5F0DD3057CF2A7DFA213BF68D0E54FE7193334F70EA4B26F079FB01B442FD2EA4D57F7816CD099B15B1BF8328FFB9FDB6F7AF97153E37CBDF26B4621E21E5D9D3886C80872308CFA86FF130703A65B541AA3AB1DFCEB960885D54E81A7082C4641261FC69F343C763FEC4713D0AC11492090581CCDFE78B9C8CCB12E30DFA95BA6585073944F94D4966B1C0759955F84C10E654098422EABFA60114871DEA2ED82F3F9FD1E79EA04BC2FBD95CE3F882239100CB6F1B4C5E7B994D7303FDB4D283F8FF93D87A05218BB040914CC87C89A1A7F25FC62FBB1B6CD3DF2A9688C47059FAF07CE7881622F39CA42BB9ACE8CED335FA675B83F179C3EB109190983D9F1201EBD4F6FFBBC1308B8C44D8D7828444CC51E253BBF0A237ACE38F6E50D691CF506292646641AF4AAA373CA86A149AAEF58A514D9FA988E6FBA911BCC510898F1DB176988B3378BE633E90475A909FC690E8BFF3A1B9FD8744353512C8DC2DEB167AF1781F0880BBA8BDC2C15F67C932C1390A776B3188A0BBF3DC97AB0761CE70E563B97E64F392EA70168ED91708B0428C3B568786FD44AC054678598C9196CBAB664DCC855A0A485AAAF2120A64DA143D8179F74413D1A0B47DF9EA29FC90C7D03BABF8D6534EAE6754F45F144BCF5F41D229207CAC8E24A9C2B447CE218D3EE0F469E2E2C100291E1480C3BEB60414715051CEE1FE291F9297771B1F029ACF5AAF6DE36AE3877FA8483FA678152069C549F8E20704A5D81F9FC25DBFA493745FDBB675F8245E83D8B1ED04962A6AEB88013632F64853953DFAC8D47A7A2B1050EBA9E05E105F60364AD613030916D1572A1928186AFFB9A8ABBCE2B276A8CD06720A56601FDA15E495F45DF761BA71B4B703E2962D379EE6610C2FD74E2EB5A84BB2395200A61CD2C4B907031C34BE6C583123C531F8E1C672754633510FB4110638B8547776BBA62E2E1CC20FEB1DBD33542DCD483D43CD61FB6F6109D19C3896C16353004A2A0EBC4F25BF2CDB63E3AFE98CA0F8BCCA674A36E4BE20ECF14187A1C70557604E4157B10BE63FC0CAC88CF452F09C867A28D52504AD5F74EFB434D15DB4E27BB04973F89346BD2B534D4C30D94FACBA022A63AFFF30F785FB3F116AAA7B6E25C53C5CCA3C1D8B3969D33CDC8886D2B2EA77438829F6F2D67375E81310202C3FE06783349B621BCADB6D012F8A19637CB1B606597721CA530734907BF6E46A02F68202EE06FAA60AF51A89A3A2F281DB7F35649A975ABD2C2C52F5B5D3BA363BC3ED5AD6944E93F8409F9B372601EF7869390CF8B9EE11EE16200CBD81EC7EA9D289AF15AB7E299238EF887A266C3067DB99829F4BD1A0264D7EEA6A60710131A29DE8B5073FACD14C7EDF4F5A50F07AFBAB762151D53F5E4E2E1E1F6DED490F9E0FFD687BD042822064972D85960E0E8C19845819F3ADA143625B85630B4429379DE033F929852EE3B3F5441D663DF3B0A28F07E8583F8FAFB70D94BE5C51F0DB8C74BC181865509F6E20A71AE316155EA06978F821E8C0A8313C8BDEEDFE4ABA080CA49675BDA0A2C7FF7BC6C32923C5122A19BE99C9BD4DF2DB15659D98001918F6B175028247516502A931D1E818DBFA24E3447C64E538A3FCDFF392C802503F1F56168E4A505EF42C5FE0E99E8A2A74D97CF6FF8F590BCB39D1783489013BD4FB75D94D27FCECB987BC96AFF6870FBFD53563A536C4607C541747F7AC61F2E1557362FCDC235F80E212BCB7E5AE8ABB18E83A4BA0EEC48567208A043AB2860F1CA773AD0AC50C53395280B48EB3E5FE6B27B61370D0943A522583EF87139F2542AF3AC4CAF7AC5858108F399BDACDD9190B1263938F254E969C36D8470918663554C3EC46154587AE7D301DEFF6BC9F9FA16F50FFD1D258E05DD102CCE2C508AE8538C64851CA6169B11BA21DCEB06667162F42B16750125647DD73BA9241E181E8B7F2C4F9622847148A020B701119E4327DBDE9E4942C690C83EE2FB97FDF6BD5388FF2FAACDCFA3950D4E9D1FFD06204804EEBB0C2DF8D7A8135339A36D02DB0491C19ECBBA6516B6B8C0A2BC64C1ED62AFD49D989BC004752992B6E477A68F12CB0AF6B9C96FB955DF3D2E41CCE221BE48BE424A1B05EDE38CE1F374EBBA4B24769EEE2ADA2717FD48059D7E1A19E1C588B8DF7A7C756672DF0DE7905273E194AE1ACA6DA06627D059B695342BCE49C348111E28D62828C5BC1552A2DE0CE2592EE355FBE22B5478085D6B0E5A2A1C28844CC2A44F83E15FD5F6B65FB8684072A2EB251F2ABA5A9C249538384C6B6F7971B38E8302D960EFD9061481F1D24776C1EE1FB5733DDC961AAB48EAA3A68FC14318519DBE7D07A4A1D7AB041246E1104195602141AEA2FA44D0D74FB3CB3990D2707EBCCED86354E188694D3F9EBD1DD8D8E9A562ECE866C71CED3532F12B8097612987986731200C4E76138B12ABEA00F23FD23CF02016880CCF9EF23139CAE7F29BE0106CA0ABEC03A79BA26E1D779C062FE8ABFEC95ACE73DA3B65D4A8DA05E51D2DFBEFE09CFBA23E76B18305181A9F957C6689F1D6B27D967795EC97F1889E4C59417D40B4D095429EB781E8AA497A51119D22C80B84ACD5D42C2CB71C06755CBB5757A39AFD38F9B6EAF8FDA163CBA5ECA0A7DCEF1FE1181AF4BEDC22FC53B4057FDFCEAB299ACF4D04B48BABF6AAB1FB9269835ACDF3C826C368EA63D916064D829547084E3A1733AE053B339F7BE5EEECBD3C30BD852A946B2CEFD8AAC2D97B6F95635AE94C1D168F1D3D51977F391BE92670D15D2B60AF3084CE24BA64D5ED42CCD907E41C3AFF52E89392BEA7AB539E5D3781F6F41E87CCE0682132BF1E4AA72A18B9905E67C98FFB1921649B6AF171E301D321227B8814AE30C93311057E39D041A1DF3FFF5E9941D1741754514E34000E4477E0F32AB2500F175C7A9F6CB9F84501D4D1DD98C86AB3ADDD083FB5E79546509E85D23A9464A6526A4FED0D947584F321CD551E9B3E29D49235ABD554D3793F2C668A03462B9BBAB54FDBD6BABE039F47BAC01CD5258084E94D9DC8E72B5812B08BE80AE7EAACCCACB67996D0C3A0ED892B78BABFFC321121DABFF0F96C6F6F13C2C1E0FE6262962B13AE19288E2CB4323E349475BAFE329CA32C9FF37902445D8557B085A2697223366247397EAE9092991312E78C9C1AEAC9D6FCD2ECF9979C1DBB559CE0780CC038BA5B85D53ECCE42A32A19674EB1D1DE62CF33DEBAEBC8B31309061AFBB00E698ED5701A2F08568B122D6098A864C27F45E62F103AA22E874E8633CF75565456F9A6725599AF53690C9315198B314E05B76F7AB6E0506899D1F908D0370175719CE452290020EDF28AF27EB6BA1A7242D4987242886F82ED775616A832996BBCE72222EED462537BDF770E6DF27E5A9D67F138BFE0116D7EFBF848C154AA4F43596735C95A6CAC273971FC464FE647B32746891A54522873F02400AF7FF821B8A90E9B2FD7859DBF3DCBA95469237E88FB8A4AA5FBF9BD58F27D0B2A22E801E447D3C2979F062A3A2BA09081980CB596567DFB3578AD706D451D725A2DB4920C1693A8D89180726C2F7E402C6084FD083CC679B0847D0AD9CC8EF9E90096052A2FE127075375FF5B1087B883C3F3DCFBDF30795A97D289E6CCC6A8E7831D2A11881252B468515ACC5F7F736D798CCB6EABD20DBA2110550783F00A798B167AEC30AA9B1B840A51FE439A6FFC952C3E9353614BCFAE6926FF73FB442A2555F1C5F13BDC61E49F5EC945849F5B96D877ED090977BE3EE47F19CD31029EEFB3A6DD6EEA5EC733A30E20640DCAEF1466EB6E2A80A3583FD53DEF29E5842B24346D3A5C0A3F8A1CABB6D25CF94F79FD94F76F0CCC4810243A09812C57ADE6F304D36C261E03250B7CBE641CBE1371A947E01CC9AD9CFB22F7E281218514C363D352A4776BF9C842C98A55317A64776383FF50AAB566FC43581BAC0C78466AEE9F17931C925F19BB9A70347114249197E109B906AA3859AD3AFB9EBA65CD80380FCD0375BAEDD8C75F506BEEF5FB69214B85C581A3EA37CA049D2CF41FA9D197C8EA5A173A335D6F7CEC8034906446134DF9CD5DF48C569D401511FCE3AF18737CBBBFB58D2FFDDA2E6DDC126CCB6792BA0F3A973A57E1C467B0C56433CDEF37BB3BAC0CD8A19FAF112179F7425C00244F569BC3187A7051AE3C0C7264AE3D16B244AE3B6F7F5B52C5175D927D79D89C87890F070B6C4A951385856A2E5247CD968054F8FC600F414019669886F8D918D6E1108EE12F5C81D19C9D619E14EEFE03433C27B79E7566D4B348BFC90394BA386A408445520AD01FE6094BB9DC60B25A8C67E727B0D83FB2E94DC611E9F9E53955D96F5AEDD37FB0938ABA88178B268CCC45EF47187B31339D22FA0E8B7C17DDCF07E9DCAEA173C5965D67E69A5C4A6C50353C9618135E04F4F4E430EA559138C066267EE075B92399355F1DEAEC1767A20CD5F2631FFFBD27C93787A5C4CBCD55F75173FF5984F5DFAFEE1D5CB99243D6CBDB53A8186E590722B51FEF25361D92F98EA9663CE9E48CDFB0890FBEF43528368CAEA710655E5621C074AFD98FBDA62C6E51EF4E89DA400C7800B505B2D28E3C0EB6C601DA519D8667407583E05296FD4C5EC47322F632BD1A82EA8BF656A58553B1170D2A5408F1AED17CCC5505B19C6ABB359EC2FB5B6C117D8A2F777AF3BCA77F44B8152DA31D9C13626A9CCF51D6DA226762381B5DB53F48A8FEA0DE8BA163B27EAA56AF440E42891EB46DFE75DA771222990692B9600F2F0BB3079347594E4630410C44092735183BA9486650C7827579FD44C714B30C4061D9DA324A0EE66E4EFEC0EE06FCEBFA1D5363E14B43D4F1AA3BC6134DC408036B03A5CC26ED54A4BA992BF595E27950CE1503709734EFD6B44969F8EB5346C281A0C5CAD5DE5343580EBC713025DF6F827EBA23E7D08D958BF8D0B24D4562FEFC2A0B3A9DDC40C73EB3D0F7BA93A98296102E6AC4B67C331EF6C9D0622D18F72AFBBD9A84B3FC32DC350B17D5A85CECC441881129F85AEA72CB7EB88CC4F0B63E7D4DAFC9DC01E1A09BF6B095F9D04CB0ACB3A7D175C7E5DAA908D46D7BDEB41395B128D1ACADE5F07EDAAC17C25885EB05111678984FA18C34991D912F8E8ABE2753358DAFF4D4B24A63FB4713F61628E35E34FFBB9CDAF71C1BBCB559F2752C31A927E245F47F2AA852A9C7A376CE8F23C5F64B11320FD0A4F26758F7B39880FE54FB8E4F354578D7067713DE3A7048907EB635C7106E53A8F5CC07873E741F288C36B2588B76832875F9BB2083462BED1842909E4E08E0C05F32D69F556940797D2906BE65DEF3363399F27D8E8DE6500671545DD1DD7032A223B6A2CC13AE1D641B68A3911E5138775DC5CD5E2CB2FF53BD38D376E6CD707DC75A5A479DDA1F42D8428FA6B2DEA527289E61C5DEE1B54C73273C311143BD6788A5DE4E97F9A9DCF5CFA8CCA3E6B71B1916128DE6670043C4FEFF372EBC78B0F6557D517382F7BFE770396C54BD8B89A351327A7827165D5F77E339B9548E0C0D863F5F8162E9C32D4251CDEBEBF9EA5D3B1B9BE0208C04B12CBDCA2ED47DB86526A6A5CBCFA712F10A915207F1C575FFA247B6961147659BD02B7D004C0D1578036E7BA7AE5A038A59752853B0AEFAB7B0A31411407A1186A074429C2DACC5238E4CF4CF2ABAF3C7CCDEFFD640C1E4721ADB22FD890313C37EE75FA4BBEE7DAE359C428984886EB5518E6429F5FB5E4CD6AD55AAA2DE27CD23C29F621DC8EA03BFD69BD7EEECB38989F853565BE4374A40F5635DD0943FDC00741C5E0705B2C859E5401137F7ED30872F1BD0405448A5DD3D52787810F6F3B779827DA4BD724805721676933C4A4F8A4EC9AB2114DD3F7DECA1966575DBA784FF4FCE97EB8A43340B1F66911FD4AC5A4C7566F0DE74CB88E6C5D353C72BFE24403F2FC2761D8AFC31A5AB23399EA62C4A9E68CD923E27A6C9CD4A165BA2B872CEBA0945FCF7118C8005D261CA81C07DF59928445499C612BF4530F5E9DBEBE75751E72E38A93B1D7A2288BCC32785DD944DE596B080F57B05B8AB7A5CA6CF7C7FC10F82A238A944DD961A487F1B9B0BF7E03F44D5134B7A084873F018FA466F501DF10919F963886F9075D7CD80E6736E3FFDDB8A80750F7168553F45A4285641F21625A365A479882C0D11F92C5E8F28B5CD199E1F3F3325844C1FA3AE52861583C194C3416BCE62B8F126FD6E3BB81E8A531473F7CDCD6B792B7782BB382968A467B5BB232AEB49B25BD9C53627B34890C8F631C18BAE707044D807E45D2AAA833BE32737FEE16F36B7D218772EAA78D56040362DB201FFB93ADC492E2E339F7377FD2F4070D20FFA547FE127298E04E7A192E0C774098F7C03A813A15524F2A3EC3C446818671BE3F7664A0E428E6A311FC6E687072827FDAF3BB5211409897F50698072C3F616085ECB1015099EC20EA104CB44470C940FB2DFC12CAA34EB36D7200BF8A04FFB6E59C6BC9714127C2C011D4E36EB3637028A652C403D77ACED0EC870D8AED6C4A4B2229B300B319FF3FE2DD19C6DEA43862D65A31AA92634F0B7C53B13E62ED48A95CB5396F28BB74590F1239F9DAF3CCF6C516355FFF4F39C0B0F173A7ABE088E67BA247906EC73EFF205A254C38C995E498C129556240479544A7CF1AD59D6181399B018C5E456490C006B8EB387B183ADCD66C1E9B1EA06FF0D48619787D8DFEFFE36706FE5538006B79D68C5FE848C3410F74D0A1120B8B9C1006B91058A6871FDC8AE4D68A0A7A1026C3A1D64274733E1301AC740E530F81E91B95DB867AA090B39A8617343D344771BF28E1F52B42ACB7462BF9A9DFFB0CA000EA2498610552F3E028C7C4AE8B33126764D780BCC3FF0BBA14A1170934A2F2527DC3370AB0AFEDDEE8D20CEADFFA8266043AB73B50492BC9C0E73A1E50A00391D4B36BF7905300EEF903EE6E7ED1B174F7E7DEABB31B8A0C7A1C6564AA28ED3F6C152D13F24BE5BFC16967A9311D96BA5F99287659B92D6E23E2CB3EB2C010919A8C907A8DFCCF4F8F501290D92FAE8879835290F51304CF754FFCB9AB510AB18FD89CD1492870983033113F62ED37B1A0451440C66C29391B2289B908802F1E061F90440F732440DEC55992FA1A4B64E36579FCD4103B6FF3E14FF51B164AFFD7D9235E08B3640E6FA82495E72339513D30EE71AC9C596E0E65576AE850A07A9CFC6B64DC02C5E2DEBF961AEB2F677B1B789D738C07CC63F6F8310BFF257CD7DC8519CF9B0F0636780237913697ABA478DA2600B593F16BD946366114D0DB1CAFC50CB80FB3D9E4A74444E38ADD99FAE394EF4A2F49B2D8A44F0DA1C52666F639B73BAFFAA4E355722F6F4A0D12C0FEDC8AC487C202FE771C5D3A092159C1211D6D6F9A294FCF52994DC8DD7D7A7676CE9AD3B2EFB90359D527038F1ABF13ACD979D576A8C1964819C8217D84A6A632EDC4288129A191253476B0F674761A369F2096B90E7B4E007A42B8833AFE596B99153786B16707B826F80D20188281F3EC843038B6BAFBD6CE7A219BC59A091B8E77A45F2539650DA1790E7D1CC0EF4D6C4DAAA0EB371EA8BB390180FBB2721D1D33885C52F1DE3D05F746956AEDA7B6D6E66B29919F678EA54CB7DF393F962E5C3A0AEC426395EE1FA31CFE7D904A013A630835B13C49C8BFCA5CF09DF569456FB1F49B241D6159CD698BDE34F0C76AFFDA1C718821AD1325EEB444FBDD6958B4085A7606A0DEDE3BE38096F531F4E55A106C64427E20EF0CE6FDB81DD60B1DDA74AB713BD902FE4928BAA48FFD40A1CC12293F1118B14F2208FF7EFF74D3D55911AC2AFACF98C16F9EF9BABF1BE26C3EDEA250797DC046C6290AC8014DB499533BD2E58266C661D96AF12FC2681E40435C6B45AEBB1260F0CCBEA4AB94D927C75C101EEAF24CC8FDE92B7DD285D099E496DDE0229815DF89F887017C4B095966A00761BB2A81C5BB2B4ACF108AB6EDDEF0635E9E4730EC66B6873DE4D449D4A25DF3D027DC2423A11C3FDCF474F9792B0D1376D2B2C49075D6BFE2BC6343EB0E26BA35BCCF89CAED733D21B7384DD6E62FD505F4E6211A096DF05A9E8ABAD0C54A669DFDD687C502AACAC3CBE79648243273BA80438D9CD6686E4A3F5069FD46961BFFEA699EE0A4D65509E185FD5E5127BE0CA333863763F9A17831D92496F59DF5308B42325672149C398F0FD64E7AEA186302B1F91968A6A475E7211A65041F0116672E0FB7EC51548763B71D69CBCC2765A78A3A467FD6D777B5AE5808F8D6DDD65870930FF389304D7341D242827B43B1427AF010F149E6D28A0351202CB3B356A055DB327CF4680CA8214BD038A01B945DB98E463DEB27A4F36125C57F084BFF8A7570BC09603626B6399B2F61FF057FC5D8D522F156F0D4CC2A69102D886E1C906C9D9D72BB8C3FC00C3A91348762743B7342BAA03E32007A6F985D350012F9B894CFEF29A31ECCA4243E422D013AF1C26DC95E0243753EFE4178B4ADEF0132CE71A9D872C0193C761690D095DC81ACEF563AC81F22A4CEE4F71EE6917F3E880B9FF75157F135FC6751E7BBCA06CC5A4D8FDE70A2310B437A02F413F06936EB7376DDB29CF2B15110E80BC6F6EC940C97C12D3544AD308F3C5ED81276FF707BFADC7F672ABD6B20B82098CD720E253432126DB58B9FFAF90E13749ACBCF9E91A8D56DDE24EDF3B6AA79C5E248457F4AB76D037BA826531F01EBFA32D86E1242D3BAF5709B6C664CCC2F08AA2A6405E723470CA0BE9F9F22E14773D29D5C7605D8F7BF1AAE3F7CEDEB4E211054D2E36919DB2AB03AC15B455371A23FC732AB212032F8E1D87A6CA5CCDF3DB5CDE8B657B13AA1CDDEE9772C83EF1F8DDF8E88C254E7F5CA8921EE09A7FC2BCD638F5299D6D5A07F7AAAE1E250EC73EE7495C65A95F45FDA9C06BA0212E9CCC913BFC26C62823DD8FAAFCF1AA16A8F2347B036AB9BB05952F81916517DF199CCCCADD9144C7A045BC26FA26F824366F24A2611024A1E582DE9C6A381FD8D980576C8523E7D5AF52E13205A6D2E42977667775D6C1D5187654150178EE827E59E81061B90A423A00BE6351E9E6FE1BEB38C1F3DBA4304D3716EB43E2D993EAAB5415E1A0529F6B81D8DEB957F4015857F140A85A847EA9CEB537DC1E9449B285FD69702BDA26222480E6F8084803B5035983483B53A0F4B9A5BEDC4DE5292900CCA65A1A02B2502DC0D8544DF0B29D1532935F278432D2B9ED7FFAF6DD2E4AC9B42F77064C3E673D120AD5459F51E7C0110EFED58D736CD9A037FEAFB0C3006C970F693C90499E5E3895D60BC0921D6935C45301F7B97D08F77B9D4930E1FB2369ACF7238DC64220D1CE64AFBC3898D72372D069F210C14C0F22A445C6C9951B79C62643890EC5CF5987CF56061009B527CD380FAF6FBFD70D4EA708C6418B23F6A8CA1E4C7A22C6C73BEFCB96CE7A1E976C208F5DAE6651969619A3CCBB657BE5C23F71457D27C994E434FAF38E7D9814D8041954E4A878AEFBC23B7FBB6E989EE2293C660EB7DD7B4790C8612BF799876CC6E81C9459ED5E551E10B9B5A4C7D5A01152BFB1BD6C7785DD4951F96C2C73DD06AAA859B8C4F7B88000A615C68D31ED7BA85C418D88B0769E32716ECA901D8A36ABCD5A244FA9ADB87E78A353FEAD3CCCAFEA663503AAE023307F9075B1758515D4EB5C384EF1E34988D729E41A0FBB075800EC1699754EA17335506ADBFB7263DD3E38FD47D0A328059A075D9EA14B145341D6B83D85FDD74F6B5F781244AAA0C7F1F103171E89B440E0FDF2336997AFA6711F82B044261A757772E6CECFF0EFEBED6E83ECFD7E5A61B4AD91CCC506676FD423519B2078F0BA3836537D1C8290ECBC4EC416FB40E37AADC5B75D4D307945C64B6FD2FBFE39CD26C3C5D13FE7A7EAFCB25B51601CE823AF1130248663B5273A1B2EB06BC509092949D8D878F8DBF35FE805F421EFF78B1335971DB6B5FA43F7C94F5050E96CDB7488764E64CDA9EF5C7FCDC0722808F7329F7F855CBD3A8F26F8DC2F6153D6F5DA6DDE93192295BB0DB81B9636BA2B7C7A583BF056032133E1A4C1F1F3D502499BDF4CA6343AA2F06DFA39DB6226FED21F17C6D19976BCA10D96BA902625E03080CC8AEE1980DD1AB484D66F5FBD8BC6550B83457D75B3217E518BF5A2D607CAEF334545B4D69DA7791B95E2DE9AB163A5C825EEE411D4F5B5D2766CA4A3D509188546EF7696E9EEF751A6F6E598FA0E43D137A11E361AD4E2D13CBC92ADD5A15258D45DB11DD190826B932ADAFA4756AAA271F5757EF0811220DDF1BDC2E5C67AECD3B17CC1058CF0B4157F5B47EE34ADF8B021D75090CA5521436BDFA5194FC7D119C6F2108DF1F996AD45C4F856A1648E80439170183C5F43E6880CE2F7F7FB73E86FFCB441720C4115116A4515D1D4555861D6B8B698C208A45583414A1088FE41C6FA54FCB7945011D7B8659AE1BBB9080E1B84BE13E8E53704656AB576A0DA312360D2D2D87BF8B9E3F474CCD96BBB701F3045761C78DB0D795695935BBAD2D8DC62B4E8F5BECF809E091518192C76029FB7CE9E5B8DD2DDBD7C3856B3DE4402181D1139F083E377F728D654D710D914974F8706785AA399E780D9666595A6E9F23E79CA7C766540B28EA039FE788FC13F2865E911C62DC4D46BDECF0D9F04C863146141B3A6BE9BCEDC5C109F7F2C9BD4F2A095127132576C734CAD4C6FB9FDD8D734A5277065ABBE64B9DF07EB4BCA1ECED7C8D9D7B32348462DA0F9B55EDD57F69F82D8DBADD4DDAC8298AD653E30C3C33460BE09FD2574BBC3853F4049BA5C4CC6D4A290F444BCF7F9179F143E2B62CA39E8B0187BA25E143A3109B6EB3FBEE7267CD9CEE749DB03E38430B2AC6E29E9E55566CD4AEBE83089300EB8E36F40CD157CBA12154D95EB26108494B6F0A976F9BE224893D8C59C2DED39341B83DD6A78F709E7563C7D4C790D9F268AD85B8C3CDA6B978989DF9B9C6D7AE7A131F211DEE2B05C5A2272EAAF32DFB8D888E7B84C2AF6775B0229716C51A7E3092F19863EA2A50DB6B72DB08B556E97FBEE5614BFB823071D04A9CD8085200CA35D64D65E930EB811E3F8D01CF4E17CC7CC47EB15E095F662238B9F543BC5E979EDACAA1E1BA900DFC8287E62310E154F81715609ECBF8640A6D3B66DDB3730E83DE3193C54C700A7BF5D277348B3D3D9B3983C7791EAB98958EFA52AEEA6655B689044AFC5E5DE99EE6708398C54742037E48536B0E1637B1B2087379AD928F295B22F1D18A6FBDFD30ACC442F6FF65A068DBAB08297F2ACC04424E1376E9D0C278A43B7CF4FAD910CC2E48A26D6F69E8B09010D2DBDD45DAC88C2538811C594AFDF4C6EEF87AF334B959A82D1D7FE053E1AD9E5BF8925D637BE4B5DE6F187B49A76CC8E2E188F158DCC578F4F204767ECB7894E9F6E7E1D99C586B15F3DFA197E2F7ED84AC92FFDC7B507F779F8A433674F482FEBE088F5D23362E5AC08AF0A5DA9B721E722B33100806EF453E3B6FE1BC739EE626B47E2AD122A49D57DC24CBDABB3A739F39CAF6CDE6DC60305956B6E6077EC51AA3710BF729CCBB8788228409405AA7323E70D4158C0166FABC78B13F369B4D547D3D9F508530C78DC6ED880377A4D850E6B6ABA9C9F877457BC21267036748C4076B37FFDAF56C4DE05B887E296C2A1E3F11C59BBBFC0AE439FB2D1EED012D40FFE36BB40100BE422D9295DB1EB85C42FCA2A640A6E8FECBAB3DB65BE74DC181C6AD2F39F2E34747A659CFF62EB50DF9A119408E562D7E4B6AB39872DA357B2314D5E1D3E67A93E9846EFBA62E1A65440B216F7C44ABB93E8928012926AEE2A158E4DB2EDD2001E171512A353484A45729481A7FAF27C5EEC25F61D5292DBE8498280A82F70D7302A6DD9CF6F9625C6594EA3491D4C2267547E5CB5BE26ABAAF50E146D5BC50CE30A2291403E9B84D42A6296356A3025877683009F0DFD76F90EC7221247D3F95FF8F6ECBAEFCDED45493E74781F164E2FBA5897D5BFADACE92BC3E26320F2055415F45CFC5D22DAFA42BCD9E101EAE33A315E302CA3739EF4E2B0D9CACDEA941FC433D64D1704DFE93D089F2265F024315E9C7D59A4CAFB061299858F50F4ABA592DE8E99798534612E8DCC2782287C58BCF6C01240F4DB84E4EA5241CBC141E1F3705E7283587036FB6FAF407873A4EA0610553E23DD509E73D1C11C81B11F8B582E99428851FDF4868929969D499ABF1407515CBC2B929B342123D78239BE16B7FA88369A466A4DEDD3AF12B38E538D05B49C67FB0171AFBD120F1227D4EAF4EC122A8E2DE8DEE5CECFA3B6C1482AD5F0A9A1112D60143B4CA5A9EEFC8408070D39BAB801FBAB9E300ABC42FC1B97BF5BF6324915B813E730FC9C38684DE960E8E1C42306A5C7C0E3451729EF0BB67E378269E87EAC13E516C7B7934855BB4A5A6DE8E684E1EDFC5673874BB8CED951D2BEBE73E11C678648A9F328DBED757F3C4D58951045B7473BD4051963CFB7A46C9CD8FD0CB6DFCD13613D702928F3B4362A8AD43DB5C65AE03ABB297E611641CF8CBADF854B616E681ADB44D6F51268AA7B74D495A0FC2CE0020DF358B35C50FB280CB3589CCE9BE91CAD66BA631251869AA0291F059A1800599EDAF0C7F17E0EF4B9E5EBFCE91722AD8639B87710EF573A461E920D1FDBA97DAB8A0ED396D204500E00A031120E832D7B119F9F002B598E12DEA051E16BA6B879CE4122730CFCBA4CF804D818E043A6C8D6C7AEE116CA39F5CF832DC292F4997E15EE377C47B0FE38307B208C2C02184FCC435A38C92BACE16D2418FD55158C38FBD04041B9BCFD9D255DE32CB431A92A9A2481E1E21B4E943D63FC15F3B14C773471DDBA25C28CCDF930E5EED16AECC4AF70130741B10F6AC3CE4569D00190F59ADA1B35C4ABF28C7891219C2206702DC8E3D0E5EBBDB4272290AB08AA4E785A83CB939FA25942D8C9FD4A796E5EB4038F195C81DBE4E0DA9CDF58CE2A668B0A0156800C5716F7032A3E1B20C0CD1820EA6B6C2234577BF19DDDC52F7F15E6BF619F13B6E230D2FF6CB000E179402AF92F81E9859DED88B4D128F91B16B59B51DAB7458B312355FF89500C449A90CAE050568EDB5B4399647F6E1C231D11E2157ACB2D2D7A711EF4AA4DC58B72D88DB2D08546276F9C379125B0E3D12B0FA70CFC469AA2BE1147E0A895F07D65F08D6BF1C8CA29B9380FC136A084553B637133DADEB675B60ABFCACF1A721599C19BC5B54E19058BC5004CE7F2EC0F603EA327EBE8C6DC731C77F7FF123555E7690CDDEEEBA2EB3ABDFB0D4F17615840B4D4B3DCDF475D3B103AD3441FCF8DD199AACDE0773594844C997B76947154F839A2874BBBB6FC4C0B7BC2975F78A65F3CA071140D330F785E3551ED99FD9F62B2CAF047885DDC237ABB99DBBDF4DAC098067C85D4E41214B28252E6BD4FA331A77E116BBFE7B7B022335CE56AD6B908C37CDE407751FC7CA044872FBEEDD4DC792AA52D99D567835B8DC1F8664D88A87F83B4A81CD38043A87C006089E6B0FE5DB964C9F1ED24BB42A9FB9BDEB8AAB901953D1AB095537A850CDDBF0ACDECF30BEE2DDE290030848C16227F9A358DC710428865D4AD659239EDEE56740ABDFFB9FE02406DEB6D64152FF2BE1943C9C6217D9308FE768E20CC7FA0D3F818F2387449FEBB8F4788A4648B63BB65BD77780A542D53F0ED8B668963BADD90E7540CD351E9A34BA5C795292DE2C25E7653767E70596C7D99EEF7B223438512BE43EC91CA1F3EDF321BD499F30F6D4B79A66AF8634CCF72A58397C1C5A03DAFD9851A2D716474B06CEC9BA35FC4F8EC688CF6B0CDE85B6CE6ECD2089167669E9D8C2C13C7DBEB17834D0368E3C06BCDAA1F0F739891721F338DE3F087F491712A6872E3397A0E74C778868E3FA3A4614692B0C0E79F0087942BDC3B3BCAE38BAC82C5A640D14BEFF8F0954417ACEA114AEC7FC6C13C1C92548C2989C0FCA4482EB14F8DBA2E529CB425EAC59ECD4025CB633C824C167E1755966DC56D27752F259B0C5F38A2826B2C3A3B99DCDAB688735108B310D024DB2CAF7AE993983BDF9F32101F568224149F842CF60123D66A318DB281B2D5FCE955C0F69F51CF19C9F07E996E4D8899C7BAD12C712F88B955F5BCB6A8A8B2316416BDD4A235D8E2FA31065B15A8B238FCE1CC6F9F108DA9474E349892B1A80CB48BDD7459FE942680454A8F84FE99C2436CD1DAE505094EF1C9D054B7F8959C2E80C6794BEDE58F4FD6A807B2B79F56BE9E429E97E4720484CAC064D9619950DA77871FAB4E392EB58507C6943D6D85ADD3A03C6AF58EB1C401D65AFCC514D992B89910000FA2ECF8EE9AC5EE7D625547C6441126B3E568A612807A635C18E47C0634697233FE1B23ACC4C7151755AC308E72D4FDB1C9293CDB875DF3C98B4E3B102E5DED1D0DA3041C55CF190B538A63529AEE999A4E5D746F7210C3BAF061763CA52D78922C9B36DBC22C84EED49D0D5BE942779E7B34722E1625A7639355442FB1BB251B94672C9264E13668946C01AA95DF8D1E63CB6D9D7EBC18D94BA1284AB8C37D85334F359FA02C558777CC50769F04E979FF7DAA20E4AC64764AADB97DE8A599559E0E5D526817EF4909313E79FBCF05649F364934FD958BC4F7D11726C61F5B74F92CBA6F4AC7F2E32495EF017F2336C001EEABCD11ED24115968E73A7FFB3D6B69C11C16C0434C3EC5B425F5C3BB30BC689E6EFD95349AF6AE0980ADAE4DB46E2BE4CBF3CE42DD1A06AEB096B470240B4103230CDAF9E5F1CB0347355AE8A040B1111D9A27BD4647B894550EB727132E298B12554B314D679365BAA867CC8D048663B650CB0EA8AC4211B488A1EF45076399BFCC6AEBECF4549D1F2C86FCE6A97AE8C48969165DE3718D164C62CA4FF24501F1BBA1655B93BA47D325A6ECFF527AFA4C559FAAE9F9398749E3CA3067637FFA59AAC51B32B0A29ACBC0DB912136CE10581C52B6DECD98C6D1171976DD8379EC4BFAE1B6475AED2AAEAB8A8838141CB79509268FCA4AF2B7AE8EF235840FAB8F7A7C615A451264D399BD5B05E69B9967FB180C69DAFDF590A2C0115160B5E4946ACDF0FE1CA0F5B5F4F645A149149BEEE704197D32AF00CC6DE723FEF85890F6C857E9DDB5599E0CAAED63BAFDC7BF11C79FE5ECAAA2B56959BBE9768D58537C13E955C79CFED80A806ADFD3510E83809BEA2252EB36AE4260796D0F1CC4AE5DC5EA659883142A8621D59C689FE004A68D60FC45EFF48E61F21D03CC15F8A3C9A100950781D93A28E001E41486476A3726655993D3AE2986D83FE3BE17B97FA0F6981CF90774F1161450C06FB5FDE79B058E75123A215FD4D47293D1A701BF51E086B24CE0C44B1EEFDAEF48EF7F4FE4CC89B5804147115FD40969E618DD4C6FA0C5B139232018C5C50267F653D33C4D4302F8312C80397615A8461419E7DE98EDAEDE7EA3D94232B55BACB5092110FCFF80DB143EF9F02865917DC0D6EBDD72BC975913C86C5ADFA85FC9B358E3B249A645AD013BDBB2A83D71791F85371A214BEBA9D68934CCCD3105FBF7F4E2BD9CFB863583069D037D3B6C0FC2BF48D8AA10D47935AE665231723A90909C9F08F42225F2CE26998233431E7DBB1BBDA7BA20E282BDA160AA1D2ED48443A086E5F05328A60513597615D23223BDC74396CFECE66D04F5E4B3A1BBE8492F4753AB2A906AD2D5D05A82F63AB4493B406B5163AC0B1BAA19CE1E565132A485EEAA5332769FFB7483A53AD108355E0327BD7E5F3DD1DFF0E7953262E4CB91B51F3C25B0F058B128E211C1E2E932DE6414C3DA1902CF98B30049CF84B3C9FAF026257349D10EFDA208F0AF2F7D9494C0C55B30F868C282147B0C50DC04646C6F7EE35CBAC39FE87485469E13588967E66E564B01231002DCE0BAFE2DC52D0FBF27726591B3ABE8B99F59AE7498352A7028D5930CF2284BD11797B3E1091C36A690EA6AB2FB6D415C814EDDC86AF5CEB83E78E37ABDEDA6AE61443CAD69A9487C43613F5B3D1C725CEBE38F9A153EC0B8AC06903CC459AD69CD21E28088D0D8E5983361E7B15204DF21F903C4C16A4603887AD4EF2415970BA60BB1EF8BDD8C3A704EF1637AE34C9475BA62BADA4A50F73C80EFF8723DCE05F829FEECD6642621F46C052870EF6F9B41C761A744817EAB8833EDDB48AC05A111500AE9382346740BA95774C55D239C9BA02D16DC0081E30D9139F7562A6E5CC10FC0E0082A67881460E2A9CAFA9460E6E610029446B8174E08F976A6E258BFB1EBE3D3DA477A8CAD5EB86DF8D4453B2ACD115D005AC35F2AB3D27D84F0B5CDF1D134D9E34F347D72D3836A8062BDD08AC329DF1A041C93B55D432B7DF316F1C87D305A50FDBDBE64D9AB4BD931AF51B250458BC8D59C959F3FED85BB32C1A0F2F1433E32798AA48071862E56EF1B003B35BCD91A01D96D842E472EE9E589ADFB43D31B2C2AB5804426A4CE136C6E2A987563DBEF3093BC65BB19B085E8C9532DB46505CC415C5878863C596B202426FB6B3C8520C3C097ED96F84265809725E1C011A31F67F7EFF8D86A12C55272ABD63D7CAFB5DBEEEB19422C202C1C978A6D7EEB06E979EB32854BFD8582170404D4A7B96C8912E2F176F9DBD6245A5760A11EB7C1B41D604BE646E252067F287A74B84F040C465F9F90ED692C17AD2B3879DB1A41ABE04437A691736A4A3E5BEDEFC947BFE1743C1207760805F743E073F61C002DE075BA983A98C8478413D4674AF88C1A8A90CEA7C38D9605DEE15B4E48983605C26AF5E6F74001A4AD0787FEFA5DCA1DF0DF353312B5E0D8CA7F7B9FE1F42BA442335599F9DE1B943EB8CA3154DC3D2DDC966910F7BEADCB66BBE12B9ECDB7D2EA86B8E359DF6E13E90C6B81D2A39B4D858BFDE3A506518651D4A2E3CCEADF52E367655331408159F8DCAAAB592632BEF05F43F0AE57D9DA020064057F56740C2F48DDB3E3F93172242891E7BDC205F5820BF8D0F69706C94DD7D7F03C56589133E59AF1FB09B160ED2F6E9EF52182AE49000201E8F63E81B712EBF1B0D67FA491E4D5F8FC5922A01B0454DE8E8DD732BE4ADFE29D36BE55808AF20EE14C5C36A347B0C4532BCC29A115187FAAD71F8C87C441576AC07A57AFF0C81A800B3125EAF7B33C8FA75FCEB152B589FA76FF6873800D99A6A88636F8015A42D4032CBE321D5885962D9EC9E30D30785E18F9D40A6E593CB15F0E4122D0AE7F4CEFD5737BFBAF72459ED9DE4E9E04E78AD9E932E041BB22305D19E3820A2D5C96ADE8B00E998C8669609982447CF5F23C3B81CCEAE89876E3379A3A7A87D097DA27B654A80926756A73088DC52716BCFAF4FA33486268CAD524EF1BDC225BB95C3EE4BCF05C2DF22DAC479F0F7FEE5DADFD7D00F7C06A8A4DEA6AE49386DF2B28AE5ED9186352639E439BDAB4FC5B0DAD4706FCF925DC42AA8D98617FE6B1DE4BDB8BE21F9E7E2DF6695542BC709171CCD8699D68DE73A05D8CBE35A8D968A647D8B468B45D54116E66664296684B9DAA87D982E4FB5FCFA5A5547752B26A7788BFFB721CA12F219243C03925213A0EF42C6FFFF2067891103EE56B45B183EA5C830E167F0C35CFF518E7993268DFFDA078008F80C3E1E4A01A59DF88C87837CB6A9B5324BCA973C79B432F78A04B9955519AE42E0A129CA6709ACCCDE6BF2DA8B016C4BADD1E64BCE783D94FBDEA5C6634B0F9F7D6271708325E8438CC1B8F6ED597727B3EA59CBD2933C4340E601217BD1B7206C9E524E24D4EC6F4C9A97B8B5945C975BC09FD21F4C68E84532E4B13E2A0B4C018BE4A1A4A15F99DB5314D7EF1925CE85BC0BDC8F2768019DBD87A6C240C35F9195516F219CC609D479DE5243BA10127B03F00EE993567A954DB41CE3BA28FEAE2077F7BBEEFAFC0A836B630A7DF80CD900228C027565AE960C13C3FCF4C32B53CD0E760DF1A8AD5BAAB66DF76F32BBE0A2720C37697920ADB8A14320C17C9F10336CC8AFF68655BDD933F29DB96FCDD7CF4A6908A967C4FCF6C721F85E998C5F31A4AE581744725D15D6D6690F819F8EB1B90DE0953F20CE4FC497E64D879A79E883AA6CC7821990FA159209FD80FD61B6C892F0F4AEA0C6A0ED6BCC5F37183FA119EC45266ED68D923CDB5DD80132C4310AA3F4290FE0F5334C079DE7E3DCABBAE8BBD6150C339864C3425522344CD82AAF8B8B9260BBE3DE5A5675FBE36FC2FE8A8A7CBC8E3E1F1335E33D581BC58AFB3B3B4F84E8CBD3EED40BB4EDF10C509E065D723BC45C8A524AE66C06FBEFD4149BFD3F3C6D27F86EC3AFFB4A46CA080821EBC66CE9E428CB63154F30F8877CEF8FA995D6F3915C8498674E81026F64EDC299EDDCFCC0F95458860AB0DB4AEB790715AD22719C1563CAD6A8AC90A6564A4D7D4898A59A417E3D1B411CC602BA8AFFFF189167E31A2E8630E5D768660302358D80001A0D76FF3B2955ED5E6717DCC16F154F6BC9F9314D910FACF78FE583B57A22FB533C83235BBA73C777A9ADBCAAD3F42DAE84F083204956E34B676FD68FF8893EAF85BAE1A0A87ADB4CF07F3AA2AF48476A2D36E3DC8E034D5FA1462E7A773720EFEB8E34DB6C814AE4DE513BA3BEDA35DC2244705037180D618549312101EA51A54A152C594AD866EA74089F3B88CF1ADB1676070AE1820EA0C56070216F42B9CDD92228F69ADCB42FAAE3693EBBAAF67C4FCDBFB57E04CC16471C2FBEAC4BB355C588112C5C72A7FD016A61ABA124245C4EF0611E848497FEE0E5B0D37265943356F13AC4B98404B4D237D26CAE4C36A629CEB7ED088338D9D2B1315937E0B646D7E7576E8001ACBF005E2C48540CD159F0A2E9DA10D235956C7F6F386A4D17CADB41A818FD3787321CAD8AA86BFDEB9722028EEBF6129EA44BF5B088428B8FAD2302039090EFA919B411C85902682D18611A351290D6D44713A668AC99A71B40B09A9E411564F35260C222B91010E16B4F296B6EFE8966C2A26F901C1590DA8D9B1E405AC3C0E802AF555AE8A1C7A0AAEF38D1023A3EFBA3F56446DABFE1E9D7B9CF905CE1E41CA86617A1ED2E7FBD934FCC52C9FE4F765F84CA5BA0F8D39C56118859831DC6C6C6F822B17B0D8D227E390B69F49BBF4F3A1062A6CBE1D4B58AA12A0C4B93AE9DB2AA0BC5F39C13855C1318487B116C40F99CD51328F3370EF3A4305509233AE305D7D1D2A482B87A7866AE222C9A8152B037E754323A207AC08DC3FBB7EA4B83D1C6254465EBB16BCEEE44FEBBFE8D6EA736DA5ACB069694FAF01B517FEB6F4B9488D3730E07BF8362B9055535824BD3059FE9D49C1995FCA4CFE4D75EF81F9B813C988F764B6EB03DDE78109BC732EDB8AB98883B16EAD06FD84F50EFE251E6DA45F5123406BEE9BD52DBF73EC3F932D120A1FB7A76ABA609CB40918D2F94364926B071AD0742F598CD38443486FD0426BB5BC869BBEBD232EAFB4F07A2F83663652D44E426438EC63455583E5F50558B4D8082BAED59AF2A7903BF614630A39A106D50A708856E87579793A611F9D129FA41CC2BBD38DC7365F44AB276843DE2F1FF88A3C1969EF1154FB03170CF6DDC65C3E999828FE94FF738BD002D05963AEAAB2B3C66C306DD27E558D67F831BD0EB6670AD51CA57562688EF0F38D4FAC0FE4E7218BAB4FEA1BACBAD9912FFB48D7E962F1378B0FEB19E431A0E82A22DD1A396E97D78A94537EABDB7B35FB364AC8B0613C20ABB4BAF7ACA006337A4551A066EDBFA2600BEAB350107AB2EA00D49C6AD459DADD1DAD67F611E442BE8A9826B90CB50D6A14E45E49BFBFC05CB1B8CB5006992C9FCEE5E46E9F7ADFD446E3F673D1E335C3209A1BE0364D3F18E5D4C6EA95F103585EDC74BF9DA3BB87AB5BB7BE0A7E5B7074C633FD33F2907C6E762755AC36F413075C505912DBBD2FE4A2E63C2EB848A173A0D32CFC8690A0C84FF45247DD0098BD57027119975B6BA41F0AFB8DDD4975FE4A0EDCFB62876883B29A0DD978133E38E8A51B8D9605955CA3BA553F123A202EEAD809EC44FA5C0B415A1E073B6E2584B91E4EAE6AEB3B255DB0FADCBA87FF12866DA17E1EE37DE4323A13223408C8052F3823BFEA983F0200FEE8DDCE65B93E3F4424842ABEE42A9CF30558EBEB31859E907A215748C3C77BAC991A11D64BB993ECFCEDD82C5204CC014AF342275E99CF82B20A8CD1C40966CE8654396E924BF5ED5E9EE6A10287D959AE73A02749AC3655770D6C1CBF0A5FFA85E423D2FAA6FEA41B45695A696513731198F0F5B7451285366844302AEE30568E8CADA878319C961DA5FD926F609C679E8E76DA060A282F812A9DFDA4DE42AFAA65CCB6C5887C08D92EDFA5E5729D95987124D7713CBC02510FD388F853DA2ADDE3FF1BA5A9959FA21FD4F59D4FE4E6F98FA0A7C877BEA3FA2E3DFD71E18DB6037318A2700A7E636158234CB144BA6FD7168B24071FA5483A0D14E209244A59AB6B8C52B4FCF430AC339D7852C8E2DD91329ADBBC2CB44F450E54F65C0079079BD71FB6EB59E543B3AED230B314873620F8BAC909D41873CF99E2A8649D53CDCC1DE15FE108EFE94201153994A07F9F7A3B10BF62F9E02CCFB734A774E2C8FD46E335E2253D3F283611543C534F4BA0976C566D1CC738C568B2159FD7A74D7D87AC2706AEB5B097FD7FF11FD01E29761971C6282E85D8864140A9CC7B144B8A64F5C3FD33ACBB4B66F67D49C5F707DFE3CADC1368DA92B7EBDA435EAF877F03A094F0397CC54A73C9D5979F45F0CACEA3D0CEA24F8E86642F4271C8CD3FA993417080BAB03D913E4FF7C7210B44B02EA4EB8D262A567D714D3C0E50F4AE4D7A0D111B568E81EC476015F2850C1B5503D61D5602EFC4E8BCC71D8A122CC1A7C4033CE25A5D28BB887D571A247B98926A78854178FE513594D8F412F2C13FAE437EB21469AD20364D178BDE3325DF424AF135B59FED9C63C845F0FA146F0B490E4A52CAF65FA49D201A9F783FD93042E921A1B584F5D41E5DD99A443237AE0D245A0B107F8D1286AFAC87225A40DAA260AF067C13D921620F8F2429A72C8BA7D0D3C02BC7D03587FE9EA8A74E901164EA631190726549B4F46249F250E6C48E645773DD94ED1DCD12E3F0124B8E5950891893EF2A0099F62CFABB20D6A6D8F322C960F92933598A8E30AC0E79EA43711FC3A770DAC21ED32DB3ADAC6FE3F055B0EE20B8C08B779DA056738ED2C1D867E3D5A5A8331BD4C751574D75A7F1064ACCD950D4C74A68A271A0F8878B5507EB0C477AE9110AB61DF4D27324663041DF44B85801E52580A5F4AEEF55DCEA366A299B5DDE0B307A108885183F84C729B97C7BCD764B7D15EDAA736266894336F6460CD3CD33EE58266C0673554126E88A783B11977E5906BCE30F27F2BDE228035894D8210B6E1466A6673FE8F5C9553C923958373D84926E97107B43926412695581DBD2073DFFB143ED6A03786BDB8EEF29DD14C6B98EBFCB7BBD8B74FE6E85E7807567B3B11105BF84140370789946610ED24676B79D74A19372F94CF1B69C4C793CBAC5137C7230A41E2B183AA7954A747BDECC7DE0D453D8FD383B17C529D65ABF03B1B72A7F6F47F46AF94CD1A668AF361A0C3314C36037368749977F523A667B652ADFEA77D2DC89DCB6920277F5E6AD0DB107AF69ED7AD956419BA7D5366EC4405B658E3ACE0CBB74C8DC0CF7570A77B634EEC17F1FA1823740BF234BE46406463A1296886610152E73D3C48E7EA15E65D8E0FCA6598B8667B4D49877FF8C43EF69DF944F4B50406259F84F76361DBE4E2CF0C249FEBDD0B2C8E0321917448A3597FC5849ED6111BB877863491F6CBFF955D72ACE8E26B90204D5B7D49F509D09BA5380750ACB06E99C3C681801F641BA7F9FD185DE9166B15202A03AA98B5F25B1B55E3E043CDF620BF8E78C49DBB6D9CC87013F1BE73F660B89835E07454C2509074297018DD4A3593536EA602A428D443BB2FB2FDD20BB021940FFB12BF6E45AB8D6D6837B6945C98A9DD6BFFA1A33307FA8E7BEB59868E96D9F2FE3A2271D597165DB20C116F5D9D2839BE394E525D2458CECC8424CC68F8B14008E4547413A1A317F48411CE1D126C3B67F9AAF84A618E62F57E6A15E41B3084FF8E43E1E0EF608913CF7BED24D3CEDE5F2B680E2546BCB3CE516E8E9C777188C4A3A5B472BB58A769557095C1CCF7390DF55F4A5DC4429B3F7BD0A0DD9E2E63EDF761AB59A7CA566259FD3243AC7FCA788D958AF9C49ADF27DC855422B34750C706F4880DC21CFF9B849C4ECA72613E6FE6FF1E77BE7C1DFDDAF9B500BEDB53445A9AE5D95EC06F0540DF351A2DD6BCE3499C99193D1AAF51F6EE9AB77123755D466BC5882DF9D3CBE6E899C91F7ACAAAA55779250E6E223BE987A1572CE8EB472CADB0FFC0B2D26575D530D602B826DCF6F4AF45CD8FED56650B95551A19C22AD0508ECD2295BE6937B6D84DC7CD9B2186786CBF05CC9E3974235C9DED280CD308B970BCE2C7A3B184CB271A83F706348DD83680F576D99B410CEC5AEA807A175905972A5E02EBA1B9DD1A2769BDB671088D619693FD304247A2A8C80FDE5F00EF68437A6A0221343FC3A4CA900A81DA92008805887A7807D0A74248261BCDDBCF69D5EE325D62CB494AB7588F37DFE1C59CE29EC82BC4283A7992D9E7B26F8C2F81D72DE0AB8ABA93BE2D4A2BBFA80D7CC72AB9C1534282745F021292DEEA6072A81148DAF0283CBC9E540863B9552A42F5B4245C3D38C2968166448603BD0529F19D0E7C3258EFFA33651FC670DE044A654FB0A4DD37890E5E6600B6D430F7A9F35B856F2B4B1976CA5C4D43DBE41D0398EF905BA803D1CEE1822DB3EEFA22222BA00D554D2454FD87317FF04344DB00A30192780869B3FF19EBBF68EF34F301AC078354426E1B9A36CBEC8E73CA0847039ED6010C2BE5017FC9421CD401660B2A6C0005C233C319CEBFD6CAB5880BE7686A9ADDDA225A5590B1394C0545E41788B9979B6B96267EE20B2AEEE3DB2BC9AC00DB8CD0D864A07E4CC117B393606C46C4C209C5D45CA4B799FB267C890854BFEEAAFD2160B1C1EDB4727BAF35B75D84F0F232C7348B6561A6BEA7847748EFDA5036C1E6B3E24598AEC839474C15BFD2715F20E9283E38F8668D1B202445BAB4C8D54561429F5BFD21ECAA6A2B5AAF4951DED331A5E5DD9F7D7D743C06D4867A88D562B9CF90E906A4C218D92B4197A63478155C66CA05B2943B8D2D8894FA9194338E1B7BAE72A6284169FC1F5410B7E26BE1B8880FDC59C19CC4270EF475DCFE4F2A73794522DFDE70887D75B1F9864071042C98BB310BD3BA4225B469F93EA118210FF3D423D684413D247BF617BE0B0E92FFE2FBA38C9E2DF36859A109BBE85AC5AB863166487BB8E6C50B030E33D200A8BF20BF085581FEDB5B8543348C5E5AE52794F98723F1DE7635175285F0500206595920EDBA3850DA137E30B172439E301B30B977B9A07A4F189CB9D4C5D31EAF5669363144F83CD9C002D4F7E2F6793B451F58C0465FC9A3E7821CA7EB141729F6B36AFE77962DE9CE2EDD271041AE99543BB4B5FE116FB2DAAB60C3C5294098310BC947252788106F23B68EE2C25B0004682543D664B22D1E765C35D9E134667C0B7E287E8B8FEE0A2B29DF64601AE35A2BAF5D70A7037BC4868F18D9C1F3B4286CDE5E7176BF1A4AB7908AAD2FC8F5303206A015A36868DC0907092EA06B5617261761BE75C8453FED13CC49A924A902C84AB2AD76D4223F01D071AD0CFAA0A135D20900B4500302385407D6020843B6F3CD816D0333EA6C2622A332E69FBE3B62ADADDBCDFE84C9A0A6C9BDD419CEC4E4DC31523D09754CA73512569D08B2BB170386D7C63E6FA3D1E5499C80F4DC28BB74B686D17932874874ED2F71698637641D5843774508D7801DAD38F776B1F0F10EFC4F591A2BA27636426FFD73E97B66460130241E3D1482D240D2A95F54301A43BF2645DDB64649BD8BDC6C3C5BC51DC50DB60FAEF7A77992380288C138E448B109A8B52F027A5220434D0D20750C7CE00C382B1A96534751CA4EA5A48DCBF79BFB1E66A194EB1D8D5DF8AB485623B47CC0E385F1DD419FA5E46F4764A5BEDF7D448BA1CB5CC3E670A539A9F4F81974E9256A818945657A709A6D394E2C499F410B5B754E0B1841EFC282AE3348601757920C1D6479BF2755FD2AE071ED5D052601A907D902A6A188CB3226D78D7B84DC11671CD600E0A2643C86241B76B9F5CB5063A59DE9F02D36F33B482ED3696B2C85817943ADA11F1E2CE598A9B69F5A3C02B9297DA70720A293004866170917F9E7185C4A9A115901A52A93F6E6B7EB379EB1021A05E776AD5D7A9AEA8E6C1D961EEC1EF419A4EB50C96652CD26F8870A2FB690EF022B8EADFFB3D8280995EB64B235D2A398E868560FB11B5F8CC6299F017B1B9E1B022E1E3FC009503A3EF38293D752EFCFC5E274F31E3E04857D85BFB6416EF4A6EBE1016E483BB0A558D4AD23F8BC6794C47AE5DEE89D2163F87571589C35A9A45C6EC34E512C118F05CF7455C9F6AAB10E639A1D8155E49C74F1F5F78356337570F53333C41E26AD166B36CA84B9B9148B644ADE0EF0994D0D3B2C79E20E71D70C7B354245BAAB85F7D6036B5BFFFA35DF3672B0C76D05DBAA1A5E7DDEE94613F41302E63A8566FFA9EB798969F6852F0EFA95323B96C44A485ED0F895D00D20518A7427D53FC7C70088BBCAB3AC98D33455CAE6871296CF4D74F6CC47E7F945DA28F10A43AE1FD5235786871D77E0F96E26978CFE5D6DB5727407CB71FA9EC9732E2CE625E7F87B5D7C1DA41DD9EBB8B585924815CAAB90EC840B932E296053465AB8CA162C3C3864951770C26CB3C409FC3493C9A52F4CC33259CC70A3701CD7A3757BEA0F23AF3AFEC9A4BCC6BEE29A093F045341EBFD72E76DB780AB9D1F5491AA483A0473E5D967C86CAF2EA3618FE24514950EC11E1BD2DEA2C9E8100726E808BA35805F5926E54EB553973885E52349B3D3FFC0FA3C2134CF292515CD9157919D565B5C68B4A49D364119E082D31B4FBF15C80D28ACA22DFF29416D111929E726CFB3D2BD3646D94270F60D9495E00552BB997C17E58EBBFC1F5B57FA308D1B7859AD9261CB319A54828E29EA8557CB3A8A619ECCEA0B46C798F7C6E3B28338BB894E6ED7120BBD9DE48C4CCAB0D69C0F89018FF80B6B3C028C939D7255D8581C6FEEB04CA3526FFBFA09BAFD26B70A83C58DF674399205182D0484E266B9FCDD5C33672A7059CF32018E7EBB9000198D7E806E38161BBBFDC98F1C8BE64484B3FB9F14B398514ED6EFB491982FDF972518689587224C82C2BED210C88D3B6EE3C629BBA938EC083D1985798461ABD0982D0578CD03E8F2ECFC80448EA7BF28EE688979E972E6F2DFD36A89372FFA777933E48312949E1BAB8560EDCA99CBCF6493AD0782CE8BC16BF6E56D301822AC3B01777F1CF4673480AC6071FA18D0CB2F67E56D21D54E57AF22B7C45376EFF1E5F0E913D0B9E95186389AD79486C939B7D30EBEC9369834F26E56C712F01FC2930FFD288F4A1E7EC7E280A971BADB27BF77BAFCFC222C8BEC8AEA50A5E4E2FD0F094C106574821FEF0D0C58F020FAE90B3A493E35DE39EE919AA1DD8F0DAD920989408AAD667856862076E58A700FF5FF48A28FF9C1B26F50AAC13475A1795119743F3E9FC926B6BF19515D946DCAB103BA1481EE8B9D1ACF9A9235E5CEA07D471FD12D0C23F15A5673CECA49E2653F2E984AF0E24D988BB5D13BA6E0BF91A8DE7C22550391B8566F24D134D5D736A9567487FB674599EEB7027E02B1C8333A3A242F2FD5DC788013E5ABEF0BEB2304E8F67473CDECE6A90C1C41EB4A14FAF8EC7E242C9DB4E9E367B16DD531294671C94F52441F2CE7B396B653E783224D056EF4BF98012F964395E903CE2642E388EB20606D00530291EF1DA1FBDF96F72641C69056F85EEB600C38140D17046E993C822CB606ECE2B1F5961C844B23B6BA2FFC8E2964EF439A65F041EA1E78C26962ED8157F8AFFB7C66BCE1AA81EA2B0CC26E6EC7D0B6F08BA36ADDD9F4E8FA5CCC6233D6F1B6C1773FE98E630623A7DABD79ECEF5392FE892B97760378999C31E194DAC3C57D8786A51B57DE29432CA58C8752DF6351A7EB5853B5EDAB9F76D1E3EC118F79BA504EE8503C8399BE5D4613A615DFAA71C495C5006D9F3FD521183404E9D8D60D5680664A8A6309AA0927F8C82400F47413ACCF34DEACE2566CE8CA482C975BFA5900B437C4481AADBB36E3B933C63EF1A56E381C3751114867D9E6116BF22122522DFF2AACEFD0CCBAC99021D2D71B8E37350C0DFEB6405EACB31F9182591EE6E0E3DC2EC311EC7FB8EEA4B05CAE1CC6D20203D81C56C9DEF94780881BAB8E30FC9B6ED9E7D95E3A928331342E206FF31F14286F5FA3B5AC5D43306F939AAD1F15DAD8E090686770263408F9588EEAB771D4A2DD937B931D60E9E4BF3F4F1D0D37E3259102BC410D63A0A0DD65B25861F408DC8F9D4CC908B40A5108ECACA314B45B6F7EED069051B3F7D001D52927273B90054D042E783B10F7AA7028EF2985C881C2C06AEDD160FF55D11F392E05734BD9BB58343926BFA1435AF9E2889889A1891A134B1561F9A85750819BB9CA5E48EA9BD31716A60EFE21E3A6B46E3BED5D778227A114FC4331D3DCDDC43BDDD20DC572D785E59CF545747D21800DF34B6DC3051921CDAE7BEA0B7CE2FD6BC4CBF37DE1D8C7887D6CD35F5D54E7415AEB228BD0C2A6B0CE053CA37DDB2C60ACE739C3D44602C6A0AA463DDFE88890F15FB3D973EB3433E74AEA86632DC644F53E5D832BAB13D66C8BDBDAA4C2E88A667412CCF592A96C94107BFEA2555FBADCEE64D03B0248B51054E30F71794CAEBA18D052EDC8BCA2F9A5BF929ABE8CE00075A4235E591650F7E343B40EC72C7204AD8AF5E8FFAD9C379B2ABD48CCEC7D953339B49FF5269361A94794A0229E6A91D2B68865B90540897E8A129E42D030456A1107665092BEFC9CCE3A6DFC8C27FB9B6BAF5C8ECA377C602C4FE79C898BD7E7E57BB96F3A84DBAFB682B5B5D99CBDF13192D0849E444E9202E662A63EA8997A59A33C0B5A789870EE64C07CEB39FB3307572331F0F0C656D1D05478D937C41E9C8D5F5A2627450B5F5F68070D3D68A5C8FB033B1899C4360D13E8CB06844E85CA94A4C5A7280E07F010C8745248A53D32E110D19088BA3D6B05C0D35115307732C35D6718BC64E983E3CE2EB36FEA533867AD1B673598D2620186EB6ADC95958ABB2EC37CB056628E783FDB3696FE72E7382A4316D57E75DB3B9A864CA1351AFA32D2923D7876A4BA62194C03FDE52BEC69A6511A60093FDED403A23C00EF8004100B4A41C983FAE761C60A2E56CEBE8E822F0992CC5120F4D4105B9F80963CC58E3B53A308B66D0C6C32203EA4D1271D3DE78F6B33C6E0F0E1ED5CCE9681FA2DC048565B74AE707F69456B79DBEFF21FF4B0BE60AE72A34A44AD179691DBBC7E405A8736095D199CA263BB91253B33AA2832D7E3D1E3B3DBA54AE53BCB2996D13C0AC9BB1E8ECAAE4FC5D5302F53D42A299B7FDEC5EC34D8AFE4A6851E6A85AF4DBEAB0809553A5A31A7874A3C5E1CF3DC596D4C364303AC02819DD31E9DC7419D81EF677BAAB0A791FAD8550C738E929B17CB97A00A2BFE94F187C6D69E510AEE746624A984B286A263A11AB6B6A37D2B796EDEDFC6B8CC970966B66C64800A295FEAA5345D0E4EFE82CE50B80139FF5E51E9E6DDE7F2012A028EE5C02EB08D6F24EF6468BC59E1C26398B2F0FD18A84CC2B6CFDF9C6C36BA7893D3442E302314529A029ABD357B540B0CA33EAC19EA7BEFB8CC498EA4808695F1324A1C29E64F9EF87BCAA5DAFFCAAC6BAE496BBAACFE4204087D706956EE2E4FDF9C43B5A6DB1F6005C7AA39052C2CD0E143F0CE628C5FB2C180F1CA149F51AFDC63E4231DBC781FEC66E5575A6DD2CEADA06285AC6140642F3F5B666F490A6C8E799841B26DC4F479D712EC4E6B8A3C9786F196F03B5F5DC4D76B65CFF292EF1F634272FCF9BDA6C30A1865458EB79D2D4A8B48727B3D8358F338A0B04651F45E6174957AA595EFE1A1753E164395713EAE2499E52D1F706EA11785CD0429FB3BD6B438CB4263F87B601A548AEE0E5BF572C0FFA40F52F09013A0715D3EEC0F99FA72F5B6C99D0D70BCB0F43ABE3D42C97AC6C9040563E3C77ED1A5186B51DF1FC31EDA51242EC71F993CD3864062A2230AA05D91E726A8BA9AEB43E823C00982262F2769CEA9130E85F277D6218027C90E1627939FFD08305121CA3DB66507F5EF93802A3C1A501B0851CCADBEABE773201D31B8D15B70FE936179D750B232E4B762DE72051F1F5A58D593B0B314456342EE1E6CCB89E68EF240F2AC47F8AF27B85448443037181EA8FF92C18869FDDEBA3B778E1413AF5E68E3BF80DBDC51B0B0110430087B9E64A7EEFA79D0EDFB00FF97DC0C7A572E4E17988D3ED0D44F90B33AE5B5E0D4603AB05056CE9729DA007913A1022739CFB3BE3589CD4B29ABC54E97B8FF40D3E87E24F3017394BBC42E68B64747994F1D8B3A76EFFEC6AE1694DA52FC74260E52AAFF579A1000E3B591E7DEBB979F6E78F2A460653809106FA94EEDDB56CEBAE6B70BE72508191209D87E776496F92DA3339751FB45F7EFF59448181358FAF8588CAE536F1FD3FACB97504EEC77394D748C0182EDB298F4E590A75D20C12AA603D28880A5DEBB02BAF3E2EA8DE7658A2E28FB4DA4993A52E88839A3BD0C320B2965340FB23F8767971091CE8FC3ACB7992800749D5C4C7F43111EAA752C635B66AAAA170A6ECE415357E7C20F9AA6A650215106ED8B6743AABE9FC60BACD4FBC4FA6E9A67CE1BA9AB863CA325669EF46DDE3AF7F4D4D31740E087FA0425592ADEBFEEAD1EE4A72E69DF80AEF683BA7635E2DF6DE050437CFB2A8B27B9A1B252355D614CD0378B41E95D0323A8A4042C0B23FD2C7743BBC6FC53F3E72B6566CE615466C14B14A2B209F7363327C735E8306019B482859A240E4D8AB63660179D51B8C025A38FAC684A1143C13C294EAD49CAA93AD60F2CBE539C70F26060D726900D09BF42F0E6DB13303423DA1736E072E02F83C2D38F02F39ED3BF7EDBCAD70FABDE83FFC48D9B5799D6C0C800CB10A39BC0D27855B4042F1B68101C335F14924AA5A965CFE97B674DFC3BEBB39F70030F7FBA6B537ECAB136D07F77F703D1463669184D953A0423FC51A0B38659D0FAB558B8A1E3BEDC32EFE5852155E2FC227C12B458D38695E43978F6AFFF8FB93700B3C116FFCC4258AAAC86FA56ECA010B3B5DEB32797D4BC8589B3893F96175EFBCE7D78E5CCA8E13FB8034392AADE87603990095506612B4657341AE50F82C8FEA6AF2904DD72609785A1A44B01C17FA959D2DADED39D6B0F055123DB7F660E9D51C73F3A7B6B456A5F12B16AA084632CD40DE50D4D2AE82D1CED0607793BF4A962C623D97F1122D95C65418F0A9314D552690D59DF6B669AF27D0789C88D2DF3029E82E40DC840524CDE85424756A3D94F582E4B4A787C159A0C41DAA9278615F41ECE913E248CCBA4CCCCF43417DC09F8F86AB32D980E9683AE7729848E14DF5B90B39DA6E4BF6FA41D9F6A902440235D7381358ABA24740106F96874FABFBF6006897CC7C33E28C8792A267AF55E21ADBC4F111AF6E89162A683EC30AE1ADB08CA7B0A06D852673131882094454F7034BED4873383780DD78A0115CA1BD39F1A65514E5E7B1D29C1C2BD6BF50F0001372471D4D755350B57F0D9C48F0A679E78EAB9420F5C222911F16319621B8FA71E8CB082C4BCCAAF31AA3F032E32D8A1CE4B524CDBF920FEC54792AA35C9F1700F0D6DA6EBBC06F6657399AE80104A2338A9FB052385B322EC6F8A99043F27B3F20D00C53E3EE33BEE07705DAEE9A09A692874A52EC91A68B5C090233D4F7C5669ED00137A128D20B8417747174E73076A74B5DDB59B4B5B9700C48974DAD696D6ABD78DE94263A5BF913460DBBC138BCE6F7E28CDE4473CB26DB8D59C6E0F4E9274BABE0C7B5A5488A1C0916841DC26A5129712CA8D159AF567596CEF8704511465A831BA8F2BC75D007DB0A6DF3A675745373A2911325057B4F5F5B8D78365486CA65E40BEC8C72805C99AF4856648C0BB09E42C79557CD946948AE7AE6867F1ABD606A6A57610684F3EDF7109BAC8B0B7369F7BB8250F3F90E509F3275C9E1377105AE54BD0D1B14898C73FBB6C80C52A92B3FCCAF0E46AA684CB4997F873B03AA4047F92DEAD9EA3DE24621D32BFD59C4F7D443BDBBFC6CA4FF0B167DCFB8969B582978437053FB1D29BD45821109BEA28F54D880A8EAA78D93A4A4DF3FA7CDB0BCA64F79EBA01BFD8FE9B076D5168F1A79110D8D0B9C67EB4B87275C56F1ED32832863FA36AD717DBC5F32E266CDE2A310ABCA520A82113FC455625CB579F549123AA42F1201CDF027329452A3244845452CCF9D1094FDA515FA7401A25B1652A62BA33E031FE07E93AEF56015210F7C3ADE2F252BD6006D9FB92CD2BE729FC85468CFC5AFF22184B5BB1A8ED4837FAB00413BC752DDD31EAB3D5C24172CE2ECD024503F4A7A32441D5268681ED3E2DF7312A8A17A20929E9963F2CA5F47A1E26E59166349D40032E018F1AB491003CC0A777EFC5B8316CE71C261A14AA7CFF20E3AB9760591D982F2D725774D1A30B2A4E20FD0AC955A93400F4533B2B3C8434B00CEC7C3C7C71C21BAC6AC8346F49BCEB91295352E42B52CE86855D6D64CE2DC0EB010F206767ABE7639409D49F03622275317BC0F5EC84951E562C15977DB498381C1A940FD0A7A8AAA81F89C3CD2904668BED8782A8EBC284D050A9CD887B98C41F3D783C72A0EAA2283ADDEBC5ECD7A79BA3BFB37DAF1CAD1FC65CB25AF4ABFD4E710E28B540458807CB18E75B6F5AC6F3179CD7403CB15DFFAD4D23316C632C85A39C01C0254ED502C93D2FEF92B08F10D3CD5B5790859298925105290E627783EAE0848D60F66D3C9932F194189D1FE5425F39B3C8C93E7C14BC41367B89A920A7E3F04E6BA63C08A8D560EBDE04F78D2E2E0F89A286DE32E4F42C4A9FA4330097D4EF90DE138467729B746BED8FD5784ECC1363953D2D63AFB5A6EA7ABB68991ABE045E4B9F5036DEECE6D489D9034F10250688EAB49809605C50676F3F3FFE3BA165FED1B8395375F63713DB2E525126E19B7BB0144ACCA34A27A95005A349B468FD1B21560EE6704E09EA6579A3E62A1CD78A46163DA750D305AF119DDBC2C62FD69EF58301F74D5915F353BC143E00EA78EB8E81FC3826546331F79CDAEDAE4C996EE95EF17F88ABCF5FE562E50812B0CBE192AB39A40495279697DB630C282DB3505DC6E82B99263AB42257854C84942AFDFD46BD1A0ED10778519D634B743F09BBC4BDFC9DD7B55F61747044FC6B16380F036284C5292BA13D964AAE99BBF1ECDD6879F2C76C3E0540F8B8E64F6FB43D1058BA3E44B393A67339193C62B77B979C10EE0CF30EB248AD0FD93AA2F1011456718D6F3F5DA63575062B5A578D71BD2B542F25B8803D7230358E26570D3ACD00CE6DF3BDF4131AD104C3079389A83B3814BD6EDEC17B9598D10C40D445169F23E7307DF26B5A599B70475B8C1EE22BC0DE499DF0D191AE3A322AC639F48057C6A58A5A891633B4A265A1E926ACDF10CB48CFA3C424C7EFC9F7EA3FF02D5EB15FF023300B3B449E64024FF34270DF8371F984236A93EFFF913EACA3F6FECC96868A6611EA194C791E1A2A1A1EA1432710BA4223825623047F07D60BF756E0B6F4A02D9294240503D61A6392E76275E32DC1CD079F3E9D5D4B6D0BA84BF31242B73F4A4DF0E55B6D66BE4BD662034976998408C9AAD7EF5DB95D98B92ED58BB5A8FB49DD176B800E44211AB14C172345A9F81860B7A20651FB3DD3EAB8D0B2790649F13B92E67A3E30C0F1EE4DCAA9FFBB8FBE4F8056BD216CEA155978E82307DF8B5E3D46701812B8E73A8B88832FE219D9EC1C400A190CE21F4362C0356BC93B27881B406609AF580BB03DEC052E9CA3C097E042CF67335BF0BA80F7D327D6345C6235039EE917EFFF1CEB6820CD308C57EBEABC36F74ED675CE9B3EA2AD7141941CCD4B3321CBB4CEAAA9F45C874DC333BD840D0AE7338B0A4B1BD1D6741BA95CE2553E93F4DBB03BB93B216A506988CF85F3B365B46174788591715F268A787D2E1B3A158679FA1306C60B834EFC461B87EA968DE566BD37B4CBDF0927A32424A500D8F039ABD29127459448E3A303B94EBEB140991E47C27A89E3EE7B64F865605A2EAF2E4179054E1D07D73568162C298427CF785062D17148E5E458A1EF684A26153A14CD3E0F23781F539736EB3F19847CF581A641B1B96C4E6351C26A35AD730D7C62C29C5B5D978DCE427924C41C6812F1F1004E99F1325F67E0A4946415F84FA0BB7A4D7642649C812707315D3D02142DDD5E40B44095C68DDEEAA3E462C470E81007C5D905ECFDF7F702261C16B59841C98D55F572D2482571E77D44942F53429BC0333E5C07823C6C7E4B8C5D92241B8494266D0497D58F4932F55A97C3584A87E132F9313B5A68B039ED58A6B9619BE284132DE5F55F6C7FA9E5B317201EEC2C22EFF849CAEDFCBCD2029D67F92D79D8FEA9FAE7FF4ECD269F90B4FC0F6D35E3CA353CF2418D6514FD4C3F5AC86ABD0E03A00F803A00D7ED3CC8D9140254D902B7164B53E13BAAB2BC66E31827EF29D56D1BADEE2D557E0E9C14BDCC5E09BF60E845176E5E0FD2A1406D6B8BE6AEFAAF6F8B7B5DCEFC3617A05E8409A730A3BB0F79062F95F482C4718A80134308ACA9A9A2FAE2C50C16AE3EE82CDCD92491D807D2BA530525124BEA845D91798D663DBD94C651FF51A05BE6DE43649D7BAF8FE1EFD8A1C07AF304E73DA726BEAA43742E07AB87E07B97F739CB279EEAC916B9C0203926314EA853C7AB533E5C20694832C203DCC7DD7D3E2CBFB112CEFC9F3BE3DE44462BADC4A11833BA240D5E2910C4ED3F7270C3568F6DD6447F986BD1B0CBB49AFCE294D3E1B5DFD25944978EB4F39406BA464AAB0CC8BEC947CC1739F3DB6B3D36941AEB768466303AF7DE0B155F7C4888DB7DF5528CF8C60F72A35F393CA65BA2D6B9A3D55477AC8B227612394180237A0ABB96821492F5EFB69452600B942C1E2B2690765A644B3B5EDBC90403A5099CC0C707060AB61FA0AF539D13F112D69900932291BBE2D5E3AFF67C2F87E091B7A010CBB78EFCE0780E0AA9E389316B82B1228588F9CA48BED8F18A4CC5969DA7E7FF4487C9028BAD694AA758B91D44902358E5672CE3268F18776D64CF7FB47C825212CA61D484810B0C918245741722933B1B4B043624E23C57FE52421D91421F88CAD45EF3ADDA758DD4BEA19F3E653EDDF76498F411CA0603BA83028DE5170A4E4655E5182F6D71771069949928DD8119F88BFD3CAD792B8481DE511136B4E04B8815DE46C3CECE493C645AA5B9E05E711C3B77F2B45880A14A1E49FFCBC1D3C58A94D4EE1C9E23577F4DF619D5E5CB46885640579C9867B39AEAEAEB9B1BA4E9DE79B0EE8B1A71F2EEA78633ED2558BE7D620D3349B39CCD5C3306AD4C4D51BFF638FD48370D7A11865ADFFFAB91A6BA60C39A77DBC19623B4528B984429AB89C2DAC24DDDEDC576A8B36C9C58903FFEEB49251D92710736F01A78C6AFF08585956E6511CEF93E994A468EEB01D1C000913A1965C3F849C239617BBD05177C7633A7D0031DD491F1B3CA3895868FBCDAD9DA1514451520BDE1ABEB875ED29E4DBAB43E86F31B5FA9FF00266044C78E9896DA0A7026761CBE35578E5606D04E8A883B39845069223F8B90923D477D3F5077A10FB8AF0AF686D0565C080AD722DA74DB90B292DEC7AF43B70F1C645EB7626FAC5DDC94C0D4A44B5F653657E7EB4220145B3FE177C57D7C92F9E3D39EF6AC2AD46A75C463DD182F53B441BE3FE976654E1D3FFA82BE7BF5328F3675CF7C0D91C0CAA087E92CDCE15F328FCD3A4EAC7B4CC3F20E4D49BF41E698DF7EA2858B05A114EF570E4C76E29A849A00698687616BE1B20B185BBA0CEAA1AD4E68CF8D1BB3401FF2653601553FA3773ADB082508E15A2B27D4322B0F3A314CA52A8C72A8B869CF2778B25C91B4F9B1DAAA190EC123F6CDA5F5C12DA2746ACA472ADBBF53D209A5E5ADC174B2A64505467F43174A0EA7E16F9F282A4F1E86B8D8F2E3BE26A3DA0444D35632DB2C77E490A80221300C5F01404262BC5A0565AC529A4E70D4B6CE557DD520555199EDCEA5DC80A85C0C11503449E922789EA45515A35160841E118387926672ABA7027D40F766953E83114C13633ED5DA3AE5981B39EAEDA23A4A5C41E03122FDD53828E8F5A0CF464DAEBB0B5E676A628F0FC3FA3A272C24DA70260FADA81CEE7409048C1242AC9F041A2A7F119E2F784AA9BEAFBC86D5E62712F897865810C89C51292F32C7804CDBFA1096D0BBABCDA2BFBBCE0E0659B7751B95E7D35F04F94024B087C08EF9E2A0D8114495305110F7EC1F1D67111293347E3678E8A0DC7EBF220AA4EA4F1581131E97955EDE51A1385B6A23AF4E4CB984AF24B8625627110698F69571917D836FE2C7E549FAD507514CEA6300FB2B02B71FE45EEF1641558A5E9BD490947FB3A39FF7DB6A4757BF3B7FBA28D2FB4EB1797450F7835ED5ECEBEE8B7471CDFBDEF26F6049F448CBACA32F8C13A3483BCCA4CBCB2CC861FB83283D225A2982E92A10A1D8F98E9B3DE0CD6EF0EED07384969994F382B3464626397D2F218079CC1D83529F20BBB1DA97BF82146C82AEC37CE8E1BB6A39B1198B38E04D778023710487BC9312619C5E67CED9CF7E4A3BE702C46EF73AD861D8969A833F0F9A5F308E432945CFBC7732E04680AF20730D170F71FF880599035FEDD98DDDC999D6E71475927A293309BE96BF223966A3B955638CEAFAB38F25FD887F92D2466AEAB9510B954A68EA446115CBFC7ADF9B3363CD90A92D273CDFD93BAD1F7C52B6CFCD4F66838CA244288495883776ECA52316C8FEABB91AE275227C7151F5B2E14BC5358AF4EA3673EFA8AE3D9EAF6ABAEB35372F67BFF85795EC8FEBBE3E6D8A53EA883B0B2A05505EEE201E408C452B3E18EF2F1441BB73CDDE7A08050F0361B0FFA8FA4A3C37DBC7D8B316C6D8776C1A1A5DE4BAE46EA513A0D6819E2804BE107A6FE1689EDA185E09228B709825551CFD063D1E249B8CC99219AB7091AC18BD9A0E13E970B48F8813546EF6CCDCDA89F846F8482D3CB22485CC13E9FC570DC94A1B6277C4CC9935B0E9F11A91FBFDD257DF0C774D003B818679AF46347DD36F2BE3E5E9FE63870B88DC9BF0EA9D9DA2E5CF45C3A64C9C081A84E423CD3A42ADF452230B7BB4DB01CED5C18931FE14FC02CDBBED40F9DAFA0B1285C0809014FB51F96F37BA376EF99E1202F9AD96545D023FEC3B99F52BB121791EEA7A4AA2B4E51DDDCF323256BC5369D8AFB1B20044149BAF293A3B2486DCB89E259C89F2048E3A506C68E399B586D1738EAAE1290E3ECAB43F6BD37AFF0F426BB2A4803E21EF0310554399256ADFDBA84E0130C134F1604DC14FF29AD84F38F97400959576869256F049D9895DCD0075E99ECF379C173369423EE492463298A3E61723CCBFAE87519EA59DFF42C4B0AABABD6BEF6B3CE2F726EF337D18AB7B0B48631EE896263598AC1D8B05D3E90538A763BB01CD5159B7D7E78D8DFAD2A208FF66408B9737E407AE8BCD6A06D6C7BCB7685F72CF6F9DC0D91B5A4FB5EE0274609AAC0190C9B9CECB4CF68BEE083C900234DFC756EC1A89121B1DF5503A82BD6E622F65BC9E5AC775C02AC5042BC00F683C8B052E7B4C3C27D52F1239A52A596AFFA9DA252E12A1146BD51912E61BB17934460B37CBE7DECF4012E8034B741C556C7721376FEF8B55112FC75D14A4748B0F8A8E88B2BF8C0DB64E9EBBD77ED21870EEA84B28388F0D321D23B987AB0E1A5A631B7A383A1548C39B9061BFB5E8826EDA41943A63C68BD38142F9DCEB55091BA3966BBDD41B93E34D567E129CC3ECFF8673E93A14E1AAF905C30C50992C54E2AF379BF03A7C3D925F6458483511DE35EB109CDD008DC5BBAE84F10C066D685B89258FB92DA5F451069F84AA6BCFB4EBF6A7986B28A7B97754FEE18F20D21C1CC992982BB5708A80D70FDF336CBC020A8641C5CDCCCC6303CAFCA24B2495CB3A4EB11CE141911D5B3ECF82FCB2227B59D942A6CB08C1F022239917A1BEB43FD3E6A62ED7180725AB8DA75A1068E7BF5ED65D78D2C671ADAF02F0943C7A7F93AF34E618D10EC4849AE7424F03E902E511B1E2518C6C5A7216E3470CD481FCF56A6162D95BE4875692BF5C74F9DC743CD9BE123871AB725A8FEB238182F8A6FFCD898F13A354837D4828D8AE552D6539CD48819F962CC09CD816B645153A1D73BA6B3F624C4D67FA23919351FEC81644183042D7C11091EA7E9D27B180C7457FEB7BB2DF7615F61744F46730FD916B1D32A361347E81A8C83D1D3DFE321C2CA43B9F6C0E2180FFDE89D8AC255BDEA03720DD93C9327CA2D0C7B8FED05180B960FE27182C32F248931AC219CE74A4CF5798A4134396506A7296043601150302DB1A49B86F7E89C1023A50533814EFBBE542DF6BB9551376C6AEF08653EF8BA59C960157CE1C07C9D9F87E773C4EAF6C4620ADB5BE0BB09D8D84DBB49118DA872FFD93F4DF50832CC78FE496E7D71A8D946B6AFE06E5A9AEEDDB4E10C851C5986EE8D73A460BC531F530FC337F07730EF287F83647137ABEBC04157C3FD843E2267203CCBBA7D5CA401C4E225B6D91BF850ACB24A08C6EDD987F09CE8F3EA54D2FD64CC89DC1FD5248FA40C9507DA9AE39818B57DE299A11A3C09AB6F07A330DE3F6C1A05E39032A5EB1060742CD06F118D094385A41B4A5661C3DF14966F531B313925482C1834B54DFC0D09676B25B3E212CEFD25B97FFA62A20B4361D99F93CE94521F94D6A77B989D04A9FFD990429A88015727B475F901D37C7C094F08FC9727832B57A11BAF1E45A08EF175EF4A1816CDDDD909430FA740D1B1F713BFB7F4A3ECCAE6871D1A7C6E271CF3BE708475423653B29C1448E137226BC2691470101ECB55D2A6AF289549803D151B27F1008B596010EBE749E8DED6E0EA4196C3463956939401346657FF46DAE813AA28B6F232C360E7C0C3891B3121CCF1B90D66ABFD946BC67BADE6DD8A83D67D97522B878D1CA026264872D91D2A9810F472320C323F6AEEBB5739DEDE13C15A4921F1DB5ED1911653CB27B2CB7493B759A1F49D981C51CD08AC362DBE55E998B3B85751F616A5CABD7AF9D25828AA3259CE226562C755CB765558AE0CA3CDDFCAF2BD4B73A6C13AAAEA82335E8B0751C62CF61F54D5D78CF1BC3444935AF82A062EA96806DD81D4487FB0675B1FF77853BED33641BC0FB6AACD1BE0E210D609AF643D12B784D26A801FDAF0F26E3BBC4FD6F597611AE02D7EBFC7ED3CEEEFD125B2C9908B603A7AA5002E8EAA2E16972705CB222695A7FB0AC1BDCB8037604D117D7900F84B822BD2C7AD62D351781ACA3DEA80D5F19D54851BF716A900263971F6DEA65113BAE2BCFFC8ED9C583FA4F38A43BED7A619F5D1CDEA881D7625181C224053944A19E8168C717B461B131471A317C8D30120BEF342EFBE78DAFB3078EBD5A58D733D606CA35FA365A773FC1805A0727590B3D9B6862DFCAF59B7ED75DF49F592ABE91C7289AF60DCCE823C380EBDA7B5502A3A7CFC4C2E071970C03DC4F578F6014656011A6E6249A5205CCBF000642F8E64F20C1ABCC02FB36097AE24A630DA71EA9BA519031F522ED1B86BC787C5FBA145B1E739819A7B0F5E62F872140406222B1FC8DCA2006F38DEE7D6AFF80F5A15E83B72949DB19A0DDCCE3335370751E22B6583CD6AD0D31FA104DCEC4F60CC38B69FFE12F26CA67483BF08B3CE8DC0315AD0C15B498399A4EE96583803670E6BA3BC9DDAF8C85FD3127AD760ADE520FA34E34E510A9D73EDC4D0436489080B1C46DF32AF58553033794CB1ECCF62B9079DF10D33B8F070F3BD2EEAF4B23F6F440A4CAB1AC571FE420CE54B92931ACBA8BE0006C334202EF0397E412B70B00FEB587C4CB51321AB49E73F9BC5C2F916780672A3088F6A7B91E26A01BFC0CF4C9191780779B132B02E688FE009048C68BD2E1119C8DB51B1724C1503E8B8647B86C522004E2CA10E78C44FECD4D159A7D292E7E1305D0887F0C50649AF808A861FF07E3854103962189C9CDFADD09DA53B5BF7A154776DE4BD16B1939007D72B032D9D4215CF972D4DD628CBB9651440259D88A9F5385C8DACC1C2312BFC158373029FD317EB8B8305A63C3BC35DCAF8EC0DE794704EFCDCB500035B1930C15887C6C2B7FC184D608663B8C0A8AF963B926CED66A530AFBCE4D45D919A65E46ADD2662563E3A31A65A7C73D77FE2C1AD33EC85345684591F3BD035C8DE6ACA760BD3C9F3BC9B74E5A74113D1DA8B7662AAED841BD0E0443F684EEC8E857D4BC2B07D8B0767D82AE372206566ADFE4685DD560ACE117966BA17D8E09A9EC61A1106E7B9B84978E439DB71C62C9880F26F6666CC82FCE218B37F841F3B30E212CA75B02BAC652A02954EE9A9F89CED3F7DE0289D8AF0A0A04450BF082E5311DD32A02DFDDD160D5155B5D23AC5C207A03DD7B4E82BB4EF9560A7478C3627E4826D268FE64873E1981D68932AB8ECB1BBE7FD1521BB9A23DEC7AA93AF1B94674234034707DC44EDF6C2C2C9207DCD7C0855AAABDCEED58C3671384FC99994D319C1CEE48BA58470156CCDBAB8E94516F0E50E19A7A0D4A14A597572893A18F0CDD5CC2C5856D0ABD6D90F345644A280425FBF9436C330701AEB6867CFD771A4FFCEA844C9B8B1ED59358528234BA413B3EA68D568226FCCC3C25847E0599391C19121ED422DB3F7BBB06E36AA87450025BC1CFF41975E0DBAA6F1E6EAF99F457C03D47F19A5167EE9EF60D9D8412353B07E3B604C170C13D1EE648C34D054CCFE1522690F2CE0AAD0C936E1EB065AA0D9D6D24C782184DDB6A48FC7B9040CA4459A5C1751EBFBB922EEEB9E304252D2BBD660A02E084D338058451DD2D85EB643C39C89E0DAC4C3DEF188379E56F7B7FE6C5D47806FEF4E96A232813D539C5A3FC29E79C690B1946833C4E5ADFEAA0E6109686E99ECB2E9AE4D092115530B8B767BD157406FA785B84C605FD6404E9A1C3A7EB4E28FAD8F0C0C36F7F93F766455EEE8CF82C5B988BF0AE0B4FAB17BA6F3642FF17291B47304D7736944D896F6272E53E2CC0468853F5105584418B38DCD4AA0B8A7FD88FC9A4A17CA4C9284DA3F46CAEAA5F83FF058663AD064F1A4BD39E54A199A99271AC73EDE90EE6747DB2F4F7FEA8BF0C621A075C3755741188206CC8B2B602A71E65F3613CEB82892783F185488A2924BA27DB5BA00E630510C897CCF71E2BDF48C09D38FF730418F4D42B12AEABE2915E83BBABCA5EF69EF4FA6540BE2AF106DB53839F73EAFEFCE72C0508BDD1B0B9C050BAC9BE1FE104DAE4568DF26EA89983B42C0584CAA7C9136B580236799273689E5BDD0C2C74CE862B9F847E6FA32F629E806F6748523066B094CC76D9EADB17C6FA1ED29EDD466A82A95B702BD31DE325933861C9C3607341E6CBC6350CC0852769D235B413477F05EA9EEDD61CE205763334391393A83B809DC8C32797686FDEA23EF13D4D456FE31900EFE89BD835176E6B55E94586242655D150106E603C44D705A3FF26976208FAAA3EDE053EEDA35B17DEB86683327E67A77807AA90AC281A2A8514477B90050113C3A9526F0EC0CBCF4D8794BC19DC904FA1A954E0A5148FA58930C88F1D54AF162ED4543E6708182CFCEA9314464C99ADB780EE1C9A593A222C5C00962D54E0AC662D253F2053DA12AFD93C219D28361C4548416E432A6964509607115227089C1CE24480E3CA9C6226F508EB56B90F25D79C3DFBAE9D6581CE560C6BE91FB0788931C1367990BB2F3DC5F0C6E68A6F8E55CCB94D9DDB0295FCF6FCC14D45FE181E9B31DD8CBA7BE7B580D4D3D59866B4F2F597C76158B8C90AE52594DF4109359BD2D2FDBC9E71F25B360965074A4C91DC8D1DA72228AD98E9B00593C653DBD978043D90C4E38CFBEC931BF0AE30E8C8AAE7B36FCB58E8789003DB4215C9B1B8D424E00E74D6C4E5F29A0CAA01A0BC23C5F36BE75B35371311EA9EDADBA2B7383D68254504A8657B560511EC64395A9032E9A175F583685A54AE81A97A92DC0D5BAC0A5F73FD809E7AE820E12452C69982D962D39730F6F6A4D140FD74A6B362B4D8079E0A1FEA27C3D99B98291B41ACE435F4FE93E483C93E89EDFB66438F33F60CB119DDE6DFB2CAA33A5B3BF5920623ABFF3CB09C03853100394529EBC2ADB18CE342600DD61FE73FA2172CDA554867D1D37C253FA3E1612D93837AA9FD2362092C249D74C088629F4E62F0977FC469589ACD0CAB9946277D688AFD1E799BA8924093543300AA75F04757F75BCB719CD1DD150E96FF0279AF85F7E3A23FE50EF4E4143B75F1A989494C68F63B0AAA37528C96E35E8A24C2BA873CA6DB3D5489C11621379AADA3FC0EB2628DE65B918F7416AF6720738B80BCB552F7941DCDB83AA76F7686135CEC70C2712122FA734C1B7687E693D86616F1FE3C0D873F4B71BDFD35F0470756EDB5561F630DD5872460640152DB7F0504E7AA1F89076262E1CBBD56CCA1141CAA2ED412B153D88A72ED8F390AA215B4671629A4F312EF23275C68DC60D011E891D259736B78D20B6921CBDA4BDCC43837C2E0AD1FC73C27EDA74CEA79EA6EF418E112741819F41B16B535EDB4863FFA51E3EDE5B4A82B8CF58AE720EF170D00C2A7FAF71E3852AF65A7DA0F83419708EDF95236B064EE5A09F8D93F6F7048EA13C77F92E3726B45812D4D7031F6621465FB384EE255DFF193EB09CF2617AC3280BEB52BD4B2CE89BFCCDC90A4C7722350123D55A4C30A6AB4690993ADAA65D66E2AEB11243A4E175EE6183067BDD60A01BDCDC3B7F9C17D3B5EC6205129DFE10320CA797014B91E96E6C5D6B4BA4CC1792D940108576309826F53F7EB14AA43C2B9194949A8660B87239563C8A24D70339A058EAB8E7FED429D50481AE05BD153907CBC957C5161FC9C0E32AB83D7AC12FA3A846B74273929CF90FF9F1EB3F7458387D394C15A3B5FE9D7135B6F21B754F13112901D18888DDAE48C3346288E7A628D4A9C60C1A829280FCF1ED0BA78F25C886C994624C56201DA565D371083C653A649216E870F0724409FB0EAC384BBB0EE6851E01D95E937396B004AD409278E5F1F364803687E88CD8287F5C8F0107088BFB4C88972A63F31EC0A3DB641A2C4F8E43DF486A9AD65EE0DABF50144F5FDF7C2339DE99AC7C23E92D83B921CCFD66AABFE8EB2AD4AF20E63AAAB9330EB3A18F64D6A438108C64D9B2C77C896AB2AAD68481841345C1E57A7E5D20B70160BAB46B1A6A59E0A02DF755C9CBEEEFACB1DB37A4AEA19CDDD71ABBB6E7CF2CBD628C70B8C7404A689D51DADB16C8CA0C510DCC1436DD1A3D0097B484C9946ED102D3E75D76ED553D73B30D75751D0F28D78BD26B49130AF66A7E8A47CCDFDD847FF75AEF1FAA9B71C88E5469350E7907E562C9603AB39C392910776B9306458F70EF9DA3E2C0F964BEFB022749D9EB679F1FA187D9BD206615D08773A685271D97D01936FB1EA71B3B44BEC0499D9827A4895132A495E72672189A2A5F68833988203C689E039D71B60B70B4E6CD95F6F6898AEC4E13365F396CFA8DA83EAE7AA0BC0B0EBF1F92CDDC8B61760B117329E36BC8F64E441C345CE2D3F55AF0C360CB0ED7F8241301A9D82379F790E535D16A61A07DCBE9EFEBDDB037EFDF4DADDFCFBFF497990CC2DBD3AEFA1C4B221E48CC29350C443D4614215A5F121D948377B5E4C4F6B11010A1C2356925CA24E8D2C6A786CF2EBDE3E38188C39C4EAF4D779F7A2067F6794B8A0C5EC6598F2C25837DC24FF1ADA190A4F7A1ACA13036BD310377056E952E4E2DC342FD604219689550A1EA94134748537A959BAB043534C15BD320C3E76883F515D2868FFA6993222443D1723E1B85A95433A88DA5C3591F22CF90843FC39CF8CFA3DC373340328E6C0AE2BBB06FF023890A1F72E80E6C9822BA19AF6A479934D904A07DCDDFAF5B3C5794EB4FBBB3E4982FF918FD1010224D0C9A83E27382913C68A655C17EEAEFFBCB1B61EEFDE8C4C36D773F665BE5BAC763E1CE61620B7281E268C635C9CE57F7C357A3DA89A4E298B5DCAE510CE7812CA5F60E5B3891B2FDA507A9C7198D6AEB8BA49151BF66F9F2B027630F9A6505DB0DE52CCCE2F5F38C71127561B1B962F859CD5174D923F67C3DE2CB9D73CA2B90BED7046999D7343973B067561001522957C2FA2D80DA2671EC7434E873296A1A889F59386C42B24C24CD17B48F22B5620693102F6163A4F4734F1AC0C875037D8AEC12E45680F9CFD6DEF5C1A0F5B0D6620DE7F319CC16ADBFC84264605BA4BFBBE1C63886AD07532FEF718EBE7C2E060EE772481011EA148B868D396BC2042BA824BF890669AC005E2162C43E838FB4CD9B6071195D54832FD3BDC6509E9AED7EB59F94E2A2075335510AC89DD268DBD09A3F2493BECF270F599F542FD5B8C32643EE19D54D1E8C037AEA837FD1DAEB80622DBBD0816CC91763AF690C2E04922D531B5F7BB94A686DB02BAAEE040D513A680AE16AA96C845367A7E7BF48E29BA06AA8B0B9B7311269B42B3DA3E13E666A160AB82E083E6D5CE49749DC5E2F4DE145FA804BB797AA45B38F4C1DFAEBC5B1D71DE146364C196A3690A0A6AB47B65DE50BA28291DD2AB903FF1C84A18E5395CFAF45317EABFEC097321882342D38CA6AD274978E2B88A3CF13DB852915C58960E2DCEF62A82D38F240DFD28D8D92BE57C6FC8B58A113CBECDAB08E575DA49FD336C95B4F553A43CD9AE2B8E7FAE0496BA1565B6C09C50F46427AF41B8FC39D67593B3B0C85362084B68A3644B4A59B373DC81DA8A1B6548149B1D319A61745716E687EA030A70F1A286D1E387E7290C18F90D8F9FC57E595C26764ABDEAA3B2EF24CFED891EA343A9127B59D576998A63845AAAC5D36769ABF8AF9CC09DB8EBB238A4E0BDA2F9B309114D86814A83E50A9F4D1A42A956780DF5C6A3BF415B9CC38E9A20CC14F173A74078D9C2A34C810D345D8A68192E62FC693397C60CBB106486BCE3572726F8CB7477E54BBA5B97BA2A4750D76F430C3B16A4BB1C27D867DACC8DDB18FC099900698D2827C957A327F79FD0A157A91D76C79241C2DE0ADF422DA9A42DA256090A8B1A8836107F7FB0F576DF72EDF46F7EE5BF690E6AE69C1657AFB83D486E676AAF30B1F486E79600C7311A045364B5077D318D47A5A29FB8FC24A2DBAEDB4AEFD130E68BCA1D753432230852660D49BCA679993699479532914105EC0DE518F2B982D434C59FCF491BAE761920D7350CCA7D4139807BBF0E4FD1FAB33D62D6D8C264C9EA508D3A4E9221A4220A4F3DA2D4FE5DA9640B4A8FA7150B5595B225B04D375B6123B9301335B2FB01891216BAA37F8385A700F7F20A0EBAC9DD1AE3586C270E1CDE363E928689A98E7AE8F70289823881A1EC0D31ED9B9CBBF64FE8CB5DADDFCEFB7BD063F5B009B864DB8F4EDF08356864C79E3FEF8BC6B66EBDE5B0443A032F82708555BA25873D9F8808EF4E771944A7249BE605E3FC0282EC28E6A75B90EBE12DE53914A19DF57F1E6714F86AB16F542F8AFE85C341567FBE778C52E06C6F85789A7E594AAA034998C0FCD5F662DD9478074E29E26318D6E9ACC9D66A0CA12AC1ED73F01901249020A353133729D990BF255659AA6F6FBD84DB2D8F41E721F4B039F26A304963BA2EFE82A4D949ED8210CA89F25043F6C3E2FF76C587E81E832949F6FA2BD1508CDA1D1910078B934CA82592294765013214501DCC9D3C6FB215C0E03AD835A0D92360DA22FA0FC555011621BEC293BDCA7CC9CD0E979202854C198884BFFA80B762930264DA40F96DBCAC2BB08F8B7D3ADAA45EA04D685FF73C89093A36C942D09365E93FF599C6D4397299352D9BB8C17F6D32FAE5161C664214C6F84E7A3CB937D728C4B55B15A0EB52C702E0C4DA60D72E4E8DD63BE64C04658D1A638D6B49FCA741F4B1A5CA127AD011A9D7E2D96BA75A4B9A8E14AC107E01FAC73A915C79B65AB798AE25F651CACAF00F132313A8A402404915D965B0DDBD1954CB5C8E7A0B0AD470B1FFBB0C089EB94E05CAB2729C31F7BF98582FA79161D942F4373A09A576BFBBA962F81653651D1473EA9E04673E07B432393B3EE8E178BF67526DB19310BCF9F9A5BEB2B58DFA08D0CA899B3A58716117EFE249EB528466DB753D7444FD6124BE154DAD6F60F3FB50B0E7E104EA2666A9D8FCDEA3494C40B95BDEF872EB47FFF400B53F1D6239BFFDCF0330D7BEDDCEE6F0551D3E08142BF733F6F332B6CA7F5DFAD6AAB07BFC45C3977282047ECA7AF6B33C893BD60D25CC87393F25E709C193D7C088E7D94C2109CB494BDFBE617DD7E94903FAE2DBC51824E5ED88D4E5072AF9C5299CE575E003C6E0C678D9A853AF29F6DF8DCC14B6DFA44F328802BE7DE5B171FAE27635D71A398B8667FEFE3A508A8B79C537727AFC920769892E86406ADA942E8988C0A99689204550612FFE2FF0F74506131D1BFD9A8A24861F92F0D77453AD504169758D6328A9A7DE22189F0031016C9C09EF0248A6F1908C03540C94B2134AE08F098B51DB54685EF8421F844A379D6F870E9A039ABADBE55A062944E5142529FF54DE01DDF86BE605C840325FF24FB21C84A1652658836EA029EFB8C5B916D5A0DBB05602517270C2BA16A97E2D671CE88F1219DE986552D4DAEC4CCB7E8CE785F4CA4556B2B809856290C164BDA001C1DFEDE49DB61800843374EBEDB7511C71B6801A6BFBE29C9BF6A023D9D30696C7BF95BA04C462C55B7B0A2E765DC4505E9C953B8C9C249DD3D3A3F0D62404DDE45242FF9420E19C95771E6D34DA21633E349AA91A64E25595A03464B6781B406A89D7863C93E662BF95F290CF95FDF98CC92F083ABC89BA331384063A49BDDE8EE0962069DDBCB8236CBF3D580A11E5FF4B6A82ACF5D9E04E7D0BE039B168C8DA3F357270EFF6B5B5917080A0E27F88C08F707581E6C3D52D5163361F18D3CFA32B9253F94BAC2BCEC69DE4369E930477843C14FA9C752F56C721EB3C222358AA4FA6B57B09DE917146D739C1B5F22D82A6733F1D04380F1077D1D7EFEEB84BCE544859235EE12783CB058409B0169009FE89763BF1307843682633832CA7E52EB6FDD663460E671496D8A6F2F12F4C6F80DD0C3494432E9AEBF329D5E7E0D773856E9E249318BA469B301948D7ECEA6F49DB20641EAA4D57E49E2507903CDBA2E09714834650F8B7A6CBF90E0CCBE643748205E78E10E93C4646A6F5188E83AFC017DABA5495DBD6AF31969C3C2B0EEBBCADD81C0BA5D9CEC8F51950A24C0FB732A3AFEE047848AE2776B5AD4672C8223468DE0DB6C3E65A534E69505D0B9C934254661A5202B0DDC421A4150342F3635B9701A2F11E9D169800B72C8E47B1012CF547481ED7184D16D2EAD91225C02E839594B5D152310B259444CF68DE5502BF9A80A90E97226258B74A37AC5ABF527024738CF985E961A21CE5420A8059FAF80D914EB86C2A2428D5FC579EC0CC2F92949D7AF10921FE6373CFDDB73AF3778C287E57292353F7ACE0271D6DE9239B245D2F139C9CF9BC2D4C66F76A115757C1FA96B918E3719139E42758E344761428A8426E2592215020BE6F893B0142C7A43C77392B15E6B2EAC200EB69FDBCF5800E2C1ECB7697D48A2B6A03C1E79764E8C556FCEC024659C4B00D7594400FC0472DC3E3F2DC07370D76220BA24DF5EDFFE9830D18D9F1A820549F02012E367CA709B9EAA063A69360AC9D6C4608D64E9B2857AB6F23B08F099CE6750477F1BE5CAF79CCE6DBB827A6EFC8DB8E7A19D186916F0FCBFE186C0D56EAA9284A504EAD4FB15438E2341DB3E4C15EF995D345BC42272713D3B252F8A4224D8382C4A1BBDE5E4E61FCA66DF40677A8A26FC5B4765C4B1A950C1F246F60A4F5ADE62BAB44004B5092E2B76D9C74312747328F6A47A84296A2CA11CD066264521674CCEE322741BC6CAC643EF71B62A11FEC5A9247D3FB259552C2E7B5CECC03BAD3C1ECC25959E0A0A19C6D3975EAC31C39BFC9CAA1FEDD719B2D0FB27CF3F24CDB2AB372753972A9F13E04B1A21E10FB82240815210CE3D827911BFA3819E1FDA1ABD4C00D6D886DC708482BFC0A46EAB88896A0D4BF832B381CF25C89467218A63A601944E4FBDA68466913ABE3F41A503A3B36067BCD6212EB5FE508FE950DD26054C11488DB53D648B63ACC6BF17CAC5B3A4AFF86CB9F53591EF061844110D372D0AE97CD333413207F94BAD3E4CFB51AC2D2F86A2E08B28AB731B9D6F9C967E71E9040988016387EC0DF502D77AB45F47C561199B9C37912EF1021578D43C376DDFA985FD1E2851D7D691D908239BEEE604171E87E0ADA6B79E7C366B83EB690DC680688961D0082BA497B56F16A1E000B5A258F4DAA7360AEDD458385FEE93FF5C486A538DA000985BD541129430BAF71785AAF6B8703F366FE99CAC55A2E353AC61793EAFF220E52FF72E343E04B33535734E52B936D04D0B9E6CBB0C06FD47FC5910E1237F6151896C7B12E051FD0974B5D9D708E0D57A92CA6C69D43191E52107C3EEBCEF4A2516445C48C58F6ACAE98A69BADD491DCAA41B2D51101E89C572C2CD624E9E2A7A58231112E4EBB6F92C6E09F9BB45B24D804FFA1E06FF7162C3A002D730AAEF39D5E2ACB3076BFE4EAD212455ED97FE04CDF502A74B3673245B7B948EA49479403CD1DCAB26A2FCCAD2EA6F2C4EA09988D95E9B487F3CCFCC444FE9478CF70A6EFFC806D417FA04269D568C1C6C6211D606544D887E3D4E0426ACA3E972D484EF972696AA4FA3D520DF6E4DD868333CB946CE2558143E03225B6C21A6D30129268D1994ADAEBCDD702C1D31E5CCE9CE76A990F91290C3119452DE85CF7A683959A7CB29FFA9020D07AD06CA870BD0A82E8AC138BD5A0320D565678641ABA9015007432E70B7089743A8309AE6C1CAB93E869DFC2DA15025DACB786C594F25100516DB601342103E2478C65AD0AACC76CEBEE4C6BEFA200FBBCAFC00C2513C5B8502E74A21EBCBF34A0739665385C2A1574BE8DED96DC8E04D62315A0FCF676325AC76B926FDEE8B238900AC31070859C3095F9CBCF61BD0AE71B1A2B80D77B97D526DEBC342C578CCE43760F45B1327F81FD5C20DFE5C8AD9E4C248E0D3CAAFF8436FD1142569AE8095974876937358AC5C85390B4446322FB0D9E17E3358C66AF7798F05E893268A64B0B8ED3332B98AB50BA6D7772865A53A71719E8396CA2AD6392251ED5BB60EFC34FC8B5F9BDD797250FC687AC09000FAA66AE264DB3B9962127C2DE21C103446D9E447074DB737F0D0DB8E8297292B086215935934856B4E163ACBCF3AB01D9B082EB4B9A9ED6223C860217EFEFBED303AC9FC91496C6CDA4DC594324ADBB23FBDCF55E187BA52ADA465729295CE22DDE059AB3F20FFF215180C508E93FFEDD6522122EA2BC5EB63FA0C292D62088F973F43C8DBBE6CED12495CE3B195AEEE549A718DBBE29E92D33916EAB6E50CC2C6F123C8870D86A9309E281A89DED9A0AD62B3F83672617D6AD75B8EF72840F80EE16ECF9429E62FBC419E938BCFD6509B12A5C6C34B78E7AE2E985288FC28A9DC57E7CABA4AB7212BB49DF6879762753E76267118E8EA5C889A8A29AD83F73B9E6B5FDEB7F07FF215E2790CD998331BDEBB5E2F0F792AFE38FEE0894676ED6C65AD0954DA1636D1D3AF53C29744987A13CA6E08B5D5E010706BBAA0D4A27F34CA5CC7CA67EBF7281B4811447D0A819A48CC36C665EFE7BFF4785CA3453AA9687222923D8A7B1B7FF6AAAB7DA23F3CD9BDAE5C978DD0E8771D97D05704D866BC9290CF856CD7E50E1A38572A1BE7D420655041AC1EE6D96510761BCA61500809536F76DFC3013A5ED9FFB3E64A5E954829F7A5758023D2AB61D1E49183A6D6ED117BBA1882C7C7244E9152AA791AB61C400AC518A9446AB4BC09B20F394F8200ED8E3D438178B41800105FB88A01CBD9220C1A4189FC3221B55E5164ABBD09E0ECEC00FF57E8B00D1C6AB29A2679DEFD4BB7435672F5EC1E1DFF4178D53DA9DE5A4F18BF201BB726C6DE2E1C4620D9DE665FEFB8B04E6345EB1621C866581566635AE248336A9343511047B707166A904E32DCEDB6D8534167E8F261FE090D3D8AE63C693B3E60E7C72F523DC68EA700C9AD3ABF706CCD5B21037AC8C4BE1221D519593020065281C68FC93D8EC5962E365AF7CC8B65F9F428424FB017329F06458312C922B932A48D5CFAE96D7DDAED877779A267B36B13F7BB0CBEE942961755C723D7F03FE124C6254EE8794B9CEBAD2A3DE7B4D5B8E899E2B580B71D15E7925DD20D16763716ACE6C1B18669B6604305AC9C8DA502AD4F1BD3F78B88E5B95CBF919972FA3E5EA7E4CD4F178901BA6EE6F256E88AB8D3D2F90CBE855606198BD4DFD3CC5817C276734D2603B0D145936801D6497470D76B954686A5E2ABB5C0A078BEF1AE6014177EFEAA3FEC09FBED950C8848DC63C436C3855F2598DD993A48B01F5C8A190BCFA47141DEA6D56FF1724BFBEDE18035C7ABC602DDA76F9EFD0B44A9BE83EB8258FB749BA8418357E7C3D17CE9E4C3D5E74E055D0F2F8DF5C31D8321A9104A45D6BA88BE9ACAECD1E669A96B101C9D165BBCA23D2E226FB1BF4FDAE8EFDACDC3D542EF978E6AACB2F91DE19D0907C91A832C7A1604BCEEA2313B5BD87324C5BD48EF222ABB8E3501A12217737F152F5562D912307DBC3FDE0C8E0C75FE4B33D99C71B1F618E847FC913D7F1E077B384358AE877E7774067096CD6FF14B744014C2D888B094A2CE28F322B6D815FFAE8589B28A89695150A1F0EF1B9172535BB96B178F1F7B0914272A23ADCB31E0CA80A8952B239B075F8E623E660DD061A7C7C3A54F90E4F2AC29C6714A5CDEAFB54621014A148A70908B367B3FDEFD803628EC36B42367D873F0224407C053A00E8C1682BEA1B47494B7214EA41344B8DA65240E9C724F617403274AB1DAAABB72B66D998E47AFFA973D6821498A89ACD6A2A4C7D6662F25E873D3FEB1B63419C9AEFC1D13114E7388ADAF8F76FF601C02A3036B34CA26F1F9BAABC1B2A5AB79269B84BE65880683EBAB10E4C59C2DA77C70F16FAE93A0010B9379A1EDEBAF55D174B76A84A9C01C0F922FC781D7B60B830F6FCEF9545226578FFA4A3080DFE992317C0CDF989D8843F210EB0C7DE8A44928154E78883A4A570A045099EB773D102690F118F36A2B9B1887881D7950B771FAF915A63A51C95DBEBBE175508DB74D8DC6CCB09EBAA089FD2A790905472270B4CC397D860A5C0D146F3E64B1CC70AA4181D1B35CBFD9D016627BE9A1A4BC25A166AF206950E9FB399F4A07467421B4A44AB446752C78090557E613E0AC8ED9D11F751A65EF7BF37D8E0F8122F0ED6B81C14687CC1A53EE0D03289B20E4E3B50ABB4D85E7569CB353A0871FC8E7A4E9F83D88FC8FF03A467D0BAAE134010BFFD85D0461002B013822A293C6BF11C2E4CBC6316B92E5B96672B71D494AD5D5CD8A0418C7E8348F1642E92F12359B1C37C1740A732A0A974690CE2D6018EFDE02E07EB0C7FE3178C9D7BCF806F4C58C722C6EF9CBEAE73AB4FB6B60C8B5308A22B647B3A6C86F873443784C35B913AEFB1B328B0A230036A912FF0CA02E95C2C371F66DDED9B59804A9F3C4BBAE494BDD412A26E35A4B1145BE73461DAC029A18275544C9454ED6A074611DD4EAEE5F299738316D829937914CBEF3CB74E33D80EDC2B216D3E4C5E5C7055AD0E4FD4C1D0E25C51A9156E336FB10C715812EF1F741C3C31C8B6F8B6CF0EBFD7D7ABCF91CA374E76702A844A9AC0059FD656B50E43FEA48C4939A2D74A669C1A37E2B847F028ABC7D55BADEA591CF717420AF143A0758D5CC189024C4A64A4E029B5BDE09B7B1C3ACFA4E1EB026F190634EF3E5F4AE9A560090DDA87668EF19EC2EB34DD97EB744A3357308A30AE4FEC40C656E85828592A6A462FFBBB98E1F3B9798F378B27404F9D8BC10378B9560777CD22074A269FAF8218E6A9F4C95BBEDCA95C5CC2537C80FFC8767651555BB6664DA5F1448E3DC29FA5600B4505C7A02526D34C567076156FC848BDE68E58C31EC97223127F65E4BC854D65A1CC2A2CFABE51324D160057C890C0277E400099196FFB24CE8D68443841D609FCE284A2B2BCCB3CD7FAAA916F24A596E356E8CEB16EEA5C1A2E176771D28424CD62D1F16F4AF348C2C8EA40AB62B24118BB952109AB9EFA8F1320A46A42036167780FA828D270FE7176A850D3966894C78AEEA9597C36F819D38F33FB02CAA0B4AF7D73E2E205B59B8EB666A566195541D74B1A707DFA2683D87DCEE885F4E390A70846E1072424EFF38F643141B9C2DE1F8771E71F1EE36CBDA1C20675B25F86C4EABDD334900533457210ED54E9FA97DBEC4BE4F641D1C0FD8B4BFDE0D0902E4F16FA9DE8B54CC2442F2A94E7F07F0506271990B4E389473B7E44E02A48A30C3F81D640F957951338ED0723F90A49DAD09DF93A080EF28164C71EA95AC114A3CAEDE3C7160AF0395B015BB1B5FB5F66CF30ADB898F8EA1D89D3DEF05B2EA465DD2549ECD0BF08CC23C450A36F5359CD1269DFBD5F4F69CE2C67CD3C2581AF0EA3EA1B25FD1E73F7AF61552FCDDE19843C33C2069CE27D893C2EBC28DC00DDB2EACF3B2B5A683CF73AA938EB51316AA896AC1905905AD3CE9AB8F41770EAB4ECBE3670A8695EF88F838DD272A4F42300584FFE33AEE2103871E0AD930EFFD78D47A119BAABDF4B546A31AEADBFBD8FA89B3FEB21B9AF97E4D50257953C98391A70B0E1B11F0B5A828406B2B44FCBA3E5AF14CC666CD1FBE0D6AD33AA43ADA0050EE4F49203ADF8484FAE290A919D3BF69B2E37B7B643DA1EB96E94A28ADCAC7754EF86DF4491CFEE1C55D5DE6E2E5147C3EAB4B0314D715C4920C618B5C30BD07BC025DD2541ADD74F516A5D76CD8B2E763CA023C47833B1CE18BC70570E0839A55E1736ADCC1D1D8242CC40A01C3656B436B4D39715656E121ED57CD8FC4EBBFB8F03464ACF957D103F5CB041FF85022B8BA7EB43AD017A2ADC8C587519CD30630FF59EF8596FC6CAE80B421B2B513D70379FB619F19F0253A48BDFCE0F7E44DAFF914BDC8C0A99EDC60B566AB9ADC698AE79FB5AC14701F455B1EC881B7DB23A330644441FF0C746924E7B55B93F3735133350E5FD19CB0B344F5BA251247607627A49FADB1AA1BA177125F184CFD70FEE5120855F4600B54C2A33E1A0788E5136DA11AD9AFDEC6D111406C67D6FD0C79FA6AC023EACF01FAC5FF45C1A932A933547FAAE9058014914B9B8C46845F508E90F378D3EF6E50AA0697279FB677E46C613D0FE7B2C79F5879199911C685A3A55E24F416E823B48E6107C971397B54C5A0E4A581627DC4B3C6BC80C3AED7FCA66290B48BC7E42BB1ACF6DC7EC6194B12CE7FC887DE65DA926414F7F34CD478D914614BC368F88321365D70C1A4DB57A874AC1552A47231EE97C3C2B11EDAD2721170E744CC192303E7E210991A2289EE2A420399F9E0BB119840AFF2745F09D8746437CF968496D2A4EC6D6EEF338B8587EE238E756C4D2538D1C89C40807C6A1B1F8A96A4F3F2715877C75928274F28502F50154362FAA3A4D1F4289BFCE4AECF7DB3CB52B9D040EDF18B410CDB9A9AD0A2BE58E59A4FB9A15822FE3349FAC9C1B33E8ABEEAED32137833A6759D3BF794BEBA6BD92A756B65131C7F14D737AFCED330B63E45E55410E7813A517DB7F4A2165A54436C83451A03ABF77DE2FFF05A7766A60E17FA9561DA5B5B6E1BF372D47816A118BF3F9987F8BDE69D667B62C8C5AF82C122FB0C6F7C2EA81955DD900F6EB81945EE141828CB89A2764A53505C890AEE163D374A45042D7627F6CA7F184C5892D0D117BC48E9BE68E17F920E29779E95E538ED217B1E90E81AEA9C08A377D72CF7AFE96C5F7E08F0D63F8D74E0DEE2425D47C713EB6A6BCBADC0F307BA3F719926658E114665095CD30AA647D9DE8F4EA221341A56146976748A801D17390470D878F5B864C8B753FE6398FB369E91C7443F88670F7FBCC673C174B6559DB22C1CF5FB3FFB76B1CA41D53FCEB71BDC76CCB7B115B485808E0CADC475A30AD8FE0D29C884B6BCCA1272BDBD65D5B6175F3B09AEFE4CF65AF31298DBD107D9DE371DF2B061CB5F20951DD90E083DAF577660B8AEDC4B1B7464E12FC0C89D2865418B2FB595D39F2CBA1B575309028242DBB94521B22BA2196684B93F197BCC2FC6A241A362C80AB46AA21613D9FE15056EE0B37383FE71C153D74D3B3F0AA0712D743F3A7888AEEDAB43A1949ED1EFB527A766B847C5AB20ACE184FB52760CCC6BAD7599ACE9DC9E3E1265105075E72CF52B6F0813D13506CF54D27DE48693C9CAE21CF7A2FB89EDFB9F58162569DFB1D4BB89F43FF3739B896853C560F5390D3812C83DF974C3CC9A47C7964773B8A2B842776110F08FD9E28181433A77227051763EFC2480FE4F03671E8339384D9D0875C2765E4B50EC3293C0F742AA4B3481A7EA8CE97DDB9F3B8ADB9F9ADE788883BA9241BEA97496EA864F5B54ED0AC35EB7705B655B8B2A691F89F1355E5588D9762C1B82E21A4E4D447C6617656AFFA7B3E20CC8670DDD92FC79BB8AF216126699083C3B10204FD852F27BC0CFC57FDDE077429EBB7F7F618BC0689FDA23B71D3EA04587C0067B2BF6B810EFC5D95143DFF747D92EC168B7C9B89D43E607A2B6DCF82F5D6C981D3DC8B1F45591101F88557389593FE7E4B37A4056872505B58248CC666AB1DEEF84BCE4F61C8A7D831563639F041F8D2BDBD55D9F3AACA6BBE99DA68F7B127162FAED064E7E1F6D9EA39FD21BCB011FD6D34A5C1EFAAB57173249A61B6958C09DAB26037DE9329F978F22C632C96E3D163E77CE4FEEC275D2A49C4A62E7BDF5EC75D17140978E07CA9DB4BCFF4D525DD4F3468188A2C8D17D1A29D74698569AB4D842D23B0117873A9466C620886346D8754D40C3607E185A4E81A58DFA31B2EEB6FDAD5B63740319789D6A4CEE1812808F86F31640BDD0A5CF6CE8C9E20D162E6D5DF4E80E4BB2A21F4B07DE165541C11BCA14D308819C37BEC138BCB0F5CFD6158FD86D9DE2827F7756450B3081A3F3DF94D5FA476C197CD96E2ACEF1F9E57B5BDB2AE590026EDE01FB196A2FCFEB558110FFAABC9D4353F963E42A18653A53442FEE4534C76C8216C11281CD7505C01889395EA8C41017616E68666B1648545BF29EEEE1FB546BE570799862791A133A77AE546F93AE429D77F3B38CC5C8B5CAF9492060F0B9DF90CCB44028F711FE46EA8528B03F80670EE7C4DBFCA1D35A4F277CAE112C6A58FA0F98FC7C9B9CFB691809ABA7956B5E2BCC7F0471310AD13B68105AC43C549E248B12F3409FE371158C354BEDAF4698F388472879A4FC7E7ED5DB54E076D315E250EFF88DDCC74F0D55556765890E1F85970D4ED33580476E27F78C44C676AB7B79081F15CA92AABAA9B8116C6621B280AD1DE6FE4AE9D72439825617BBCF580E6D36F1BDE24A64BAC4E66B55F3B4896B030FE707F253E9054E593CB32D8638851B724E56033E615B5A976CCAB49152B9C884AC08BA1FDA07461D2D468BBC795E4B5F858D03424CBF760A2CC5C9EA5329D663A829A8E4260F6D657704209F3F29C092DD4228357177A3EDC120736B7B4B6C7C60488D586754A3ABB276C8981877F14FDA2FB5AAA08102FCAC33FE9ACDAD913971481190A4B634545A9682248BED170B49E5E4C8912C3DC4FBBF8C94C6C21A0D259925A42D08819E8B4E182C9B1A022ECDDA1BD535093CB57478D89C61EE542B8798B312A198E30A7ABE15043BD9C144605A5FF979B0DF8A403E58F1ADE788607792B93406401578045DE9CF3623EEB8AF24CCCCF63A21249D59B540A8B50276B6EF5D05D1F53CE277D765A8B1F4E4C4A913FBD9CDB932FE90DCE0DC64D94275341BFEE8D8F09571385AF4D34BAA2EA93E521E124CB948E730E57B416784CA7C9B59ED3B7302A5AD575CD117DF410DF73EC6C0B93DD7860C6D769D948E4E99C53A436CC384D60EC60B92D4754CFAC30CB9D3BC915BEE831A79994BFC23D0C033B65C2AAF916E0B348F22AD8FE7FBF96B28BED0D214EB53F2928CD0BE748A462E10B3437180C01F4782AC332D95FF3F3CAE08875C71C3A142A649B2F651CD4CBD3DF19E653AA36BD1FA7A31F5C56D8A0F24068861BAC24A570E27DD5177A5A90D3169D4B67EE69ADD4C7CBD67CAD415D57A10F6474EF0EE298903B6526E15B6E24366CD1DEB244E01A500236F1D0B0F4A2B8216D54F29DA8F5E66F01CF04C991B37BD4A4C1323446205110B6393F50D96F993DB5A33A72A8D783B27B912CDB40344AD8DAD9A5D10307AF822315FBE349E551B2E55F05824E391F261AD0C06F2AD3D42C7229526E7E42156619A1BE22007450BAFE5D470694D8EAA42B09B9FE7A49BFA9839BD770D736D375B7266022A5B55FE1412AF502B6EB64B5D99B929B7086C1EB3C76ED17F9A06299E40FFBE1E4C12E2BFDEB217DCEE7B186E727E447AFEF7FD523E9B05A409DB784677BDF68C77A9C9BFCDA5A2A310D66E3915EFD5C1CD0D0886D590B7D923246459707479D79B8535F0D6047315CE996BDCC759C807CE01298CABA2F38FBD3939BA2047A15395FE47CE9BFF59C3C245D858D928206B41E95853A46144F3B0358887E786EAF4A42A6FD1B4D4A3023C813DBDE7A72F2B8B41DB17AA8BA2F75D00D9B92577EC3090CA5890DA76BE5B4B9C16883A02B67C9926851791B9023B8A45E279AAA25BB1BD77ABE1D3A578AB6CEC2DA1929569753F9F07B48003C59E9B561F10DE322DAD922DB097764A53C382F59E382E0EFC7BB06AA3081F7998CC171E1A6905AB4272131EA0CC370161B248AD8848D62C6E0B1781CA571457E45F6F3469741F5F299C096B37B6A22C5D2A8CE7AB08E1964AD65FACDE9FC32CDADBD218A029C86AD47064775C19390FFC9D2D538A1DA74B87A62779B90C99FBBC430E4DFFEEE4704D061A753794DF77D34ACCABF46C2C3BA7410DF2CA4A2CE17D3B4CE5C7F8F829702B8A4C82521ADFA397D95CB82D390B8ADF0D76B4F15096F259890B67E7AB8D06C0D2CED0BD6AE2FA9B28CFE126D06CB7EBB4B645A42397934771EB09DA7FF3837FB8225860C0880F2A6F7CAA409F6DA9C5A4C95864CB7184DECBC2F5A1E9C3AC5956DCE6295FB438FDCB4E93337546F76AC9C64E57CF8AC0D40DB65B8818F917314A25DBC8E976934E8E0B1F1E7ED79D3153CEE2B853F777577CCD79CB59BE22F9AFEBB0B4B181F71A4239BD30B1B0E49DD8DB865A6C17D43D690D39343A4EED46337ACE8169342E6A8ACBD59A0D36DC823C787C509BF92933D93C2D0F2551433625B6BB03A07CA1EEB9C4F221F33A1C8733B3F2EA6EF2FC8AFB16FE6BCF8903F251EC55F260708ABB55A9CB708CE2E6CBB10CC1E0DCFF23E688D0802297B4FD8D977C57A58146667AA2F1FDE671052D5ADD348DC3F43279C843229CC021A5536C4D9C62BD83A36CE8397B2A76A0D6AB3AD5395B4C0D1D9D32BCB5C83EB078638172212DE456CC0D491207537551C1B4F541085AA76BA2D707AD97F65A447AD1BA141FAE3A937576FD3FE0C633744A8F9CB5D1339FD77906BB319B07316DDF29676961689BBD6933D8A33EC04117DF879420B68C2B9073DBFE35B03B3F91AE216E74048B8009EE6656B9D1F0CD835F0409A84C8859E5B460E8D84A520122B1F6252894F0B45E935BD5EA8B8DF44D6472C2BC8F0F9936680DA98C80504CAFBE764CF4EF253C0EDCB5BB8A9EA3FE9357CED92E1503927BC626A688157C879C012E710A11CB14B10BF8A34DDE710A99B1049631F370DE07F781ECDE34EA98CDD067CCC33E9E97AE404CF6A20F188A61382EB637D2B5D87D7B8AFE47DBDEBA9A17AFD7AD29AA93D7EE7A47D1A8BAE204B6BDCC1D7868BC6265280BDAB12439A3B66D677FFB8C15B9A958E16B3F82DD61B2B16FB9CD08CD2C1131BB384966FC75734F1E9F3C157A6074BAAD9770024880D228B8B01C1D14CF1B4F42A20ADDA86C792DCFE15D4155DF9D1CEEB23F2C165E16858217C770077E492DD5D067C3B56989D61EB645324261A24EB9ECC486BF45B2B793FCA8208A9946CFDDF2E31C23509DF9ECAD0A4B91F2CDFF4F2ED0B46FADA856987895FD6C126F02E24D292DA2EBB30CD6BA5F7C9FD9DF203D927219F5CF569CA7B1A896F598B0524002E613448DAC8A2EEB278B101F0AFF1FD458B8A116DE9D9922808855071859A2EA1F1579E6E4A61AC5285CD6B0F9B190FD97F83137DA22E36B393F04EA28FD26032523C44503217832D60746621E312F6A1C12E096706F36D124A9AF9DA2AE25D552E96DD4239C128DA5491E3D45ECF23E1BA82B3EF44155A7A867B032D1CB9BB17CC68096827EE50F200D9270CEA7A8FCE816410E0B8DB0D68587630354EAAE6215F79CF5AF6D29624ED4A8E2BCFF35D41E283E0AF42BA5C05465F5D3B779938B425BD4AA7AA6A636B1C947B5B1CB0C7732C50CD5FA876A6007388902E7640F4E00A9E75C9BCCD6D720075514C119B28042214A2107B7AFAFEAC70B1BF1D7BD0CF355CF47068F4F784622B73E65DC2EA217FBEAC0A8A99B08226D40C8F6A7082EB22A06A580A5D6B667482DC80786195A84C906FC37E0F7988F74E5DFDDA99A8F7323017AB6E87852A29F83984BD791781C576E8A1362DF1D38E51E117B379EA4BB970374CCDC1C17D9BE96CB7BA4A3FD9E0B920BC3D70F7DAA98CD2C316C6D3459EBF9C6479496DEE2A869D4AA6CFD759EBCFEBD3AD27B97A5954CAB9F188264B380F58DA08F249B88CC67767E5B0BBCA3B5591217DDFE810DDA3F0805691A1A4C5F1B8839C12D8532464DF9C1F1D0B19CCD590DEDF904D3A7AE4DF0EB32972A4BD1B78974B6EDE032E211DC16A83A99108DEFD31191E6CD0AE6F52C34D2079E9E4F7101592FE5249E4E9E74CF4E2B11A9CD9DC71F05DC20C5409B4805EC03753A11EF1E5E2EEC70603E5740A435407F09ECDCAEFB56C510E28C51B4D9B3A50079FB214066AE88F8EE202BAFE22C04E9339BFF864FCB3227D4D897647C6764F9DD81960ABC5B02828F810E76C4E6815F03813263A4DEC748830279770B881DED99890E912CFBF6251AE4799987F3F0731C2ADC12A9F4564E3ED4CD7BC057F4C36699E3FD2E905CA9AD7372EABA90B8F962FEB8A4659A3B897E788FB34E69AACDF80BE171479AA57C508B2D48A74D77488493D92B7B2A8B8A3D628455DD988378FD723299D7D8448712B3A5BD6E7A40605D28A70B525A4B2D6D30E7CD2BB62F1948F8841FDE96037D622CED77A37DB40F5E853CBC073F880CFFDC1F01E70AFCE990EBFBAAA7E4624CD89BE5EDCA004C064E7629A356880A6BE49214E296A92DD3BBF7F88EF3AFF01F5A6CB0573FEDC19E60637C13A527CB6D8BE7F440B551E6897DCB9F0F01B691F5D728B982C75161079B3EB499F4B826CDC150D6F7AE2F86808C57BB281AD0C82D5EBB8531E22492E5F71123465AC4842EE2B6DD12DE834AA0ADD8CF22BDEF4A0EAF56A7CFF1FB0716989CD07B45CBEC5BF3CA632E3FBCBA2C9C2C893672E4BBD2750CB5C5902A39CD7DBB6839B077B19D25E8A45A3718CEBD7B5A656667E3FEB4EAEE5362F83F4A08E6F5F51E72FF957532514595EBB8DB2787F08B0E3D3D1EEB0B4A6349FA709DAF814FF575C70D5BFB98A2166CF77B57F9ED200229DC7650539526AC2660B5B8959B5A2AA71199BA6E01F64CB6E61A323EAB355A02E5EAE110E3DD35F8F1AF1C34EF119F72EDC14A414CAD6DC27C0CE93244E5EA66A76D6A4D7853D0F49D3BBDA398608F6695BC1D55B622F0275B8302DEB163DE2273D4135E727B79031359A9B0E04940DC0BA04C9A903C9BFE00DC2375E9B56F799B47D03A30C95F7C056B8D22A7BC61D54EF1697A4555DFE9EDC5C7657B3D582C0A245BCF095587BF112A2D65B4235CCB4241BE093F42F83E21C57C2A3B851E7B091E01BE8469FC5AD47F0000F83FAED1946F1D1D64BF1ED57C27FE66A0994CFD2D7E9A628CB3ADAB67DB0046C5430186F93228D5AD08BFB465A75EA6AC027F48941474F68E0575E5DB4C8AE29637E65CA9CF41F4404B27EAC6C5D1761CD828B3D89273BBE5BB49455EC933969D0A34D622D66CE714E632809FD6A86BEAE7C9B7970D22C16FAE4E179D88890A04A9C75B499CEE693638D306FE4262C7672AEC4A657A96A8C6E8B689B8085AE24D89781381A645C3E24FEE9DE0BC5687A8A0DCAE48217537807D0AC338F343DBF7ADEF7799F8F88C587E6119B86E7660643F3D2FE38E18494CB2734D0E6CE392E6AFFB445A5E1EC562222EB97E61C5FB1F0821542B9C18F97C705030082D1691AF03ADB8AD01CF38845B579AD491F26350E7B6FB50DE97028D98281BEC611C16C0C3F372AF5E7C3CD682A2DE0415F396BE9178CC47A9967C68994B21C3B6F1236CFDD8EBACBF7FE01FBE5191E60B6D751DCCF45D22C07B6265CE422688D43D8B97D3D7C43BB5F1473D2B5DA273ED97DEC4C1E9278CB36897DAAB6E76399AD39B50D2727531C092E94A889525F5BF212BE00ABA878EAF190DD8314AC22E1DFFF087196FF2955594E4F0E4FB888FBF4FEB36F7EB43BAE1E7A988B204B8CC497306FCAF2AE7549261D4E881A6DE99F238094E44C95EB3BC3AD9FFFA7A301E5B808BB4B221291023410A997EC25D0C87E12C2F7EE57472299A83596E7A067837BE07F656336E5BCB7F59ABA4E80AE73B0F2C6B16B5D1684F08DC66D56B9C528CCD25D524721D2FF17D594E4891E64EB5B31A93F13C0004B3A290D96B2994919FFACE7856DFF707E259C026B746B0C33B0428520656430609A1BBCC04DA0631AD16F916E643F0B06AAFFBAFD77721125DE11E6D29D7ADBB043C344C74BC26B51C6DE61084E72B72ADDBC7B601E1FE0D2637A83F07AEE735E51769457814432D7DF695D8FAD72F29F4A879D26D0906351719D50412ACD968890E7EA1CDF899CC633A0444BFFF93C604258D79C84591539D83C5FF43340190AB64A77BF311B5BF5D73B51D1C0767CAEDEF9CE0EA410EA6116136247BF8D4535FD2CE151BEFB2DD7D6370F7A97CB2CCFEE3D9D85DDDFD3B6DB6DCC624CA88D1414AF7438B796377D0F3449C3A39501E024F0920AAC28EDCB597E096840C015BD78D1971F28C769422F2C6A74A6B8C1A0AB747386B93AD82AAEEC0AC64E7EC401AEBC1E12666BA628B2F48B17A790D49E9DA9C9DEACEF49A0BBA8094BF85B49D32204979D0BF7F5622F543A217523CEAA01E366F00256834FFC36D038AEBDB9E690AF4638F13D65FD55087B73B4BA018C45562AABCA7C29CF399D94F77CD24BF318461DB36368E6154959E28FDCB66872485A3FED5F6A0CD8C429D43A0937C045A8D645A49C7EB9175C9F8D4DA484F6281EAE2AC835A4F38B5F3EAA8C786FC10B1ECB8093512389A29E45D6E9E3D2747279334933DFA930194D3F732B318A17AC10D1C6404C7B991463795B1BD0BF2E556087EA80CAB1A04CC4D007BF6D3D7805821873FFE7FCB22FBCB0C4C166B04FFB8A4AF82F4E94DC7A987728390F52EE0D557DCABC28FD6747C145BCB66F15D5886788A386BEA17A2BFE05EF87EE7C508B14F5861015B09CCBC466326395488452C90FD6DC06B9376AAA07CFE3DE8719FF61A2AC33B03A3FCF5C42897CAA451081618BE80DA42FDA7A732E7F1753F0AF9677DA03073388D3D697E870DF681131CBBD4348555551CD9709466368B2553FC6D47FDEA7DD5B88F4DCEEDBFE43125BF95C6B2E81CEF6F5D6EA8D89051B32EF7F3C0399538C3B487A48FA0D59314B1BCDC0417689EDF3557A7C16AB00CD5CABD8D52189107F12D67D05E13EF4190C1F5465A1EE99745A461481B085EB237A24F4F1D30FF16F8C53832231903E1ABBF691185C94CF6D5B2AD5D93D31C2D57B3F1AE97452EFAA3A54482A8B90761301E4F6756B5598AD82C7C48B7411EA0CB47F61C14FF38C832796E035730735AEBAAE4E753175DD39F20AB6CD08CBF85ACF4D35C0EE442A1D893DA38D7A448F2CC6DC7B52E5AC251BD6C03CD5833ADB92157E33A07C70E504528A723FCF17CF8E63747A2C908FA91FA00B2C8DFAE00F0BCFCA3BB477A798EE921DA8DF41403A2F2BCCFC1B26D9FD746701F32D6B4839545F74FA4DAD2A18617B207E9EC6D1D76C593FAD784093F066E4094D73C20B524ECDF098747858DC103AE7661A25F8B3867BC9413FE4BF12615AA59FA5D6FAC2E323AE8E0F6F22ACE8F9CFD23E1FEE21EAB2AD54A3ADD15C9811AB5C2B5FB6E7E56584625D9FB762E45B7926CF8E72EC93CC211C8DC2F4A28CE2C6D38029B163235923EC62F3A322036C5C883364D02627B4AB4BEE86629A0678A641384D944B58229C1364161DCA1126C87D47EB93B29DB9C65E0F105211272D15DADB603C3CFF7F746A299EDCF1A63574DD5932614250CE7D85B5E112CE5895B2D1C1B534512F99EFE9E0E22BF3A9B813B55E2399C39527B992A7524DA3414697EE8807D854BC53DCC889DAE5D527673F5994E91036A4DF2EC1BC056C12DB966DAF5F1902A160F41CA5BE0B78E4E5D175A72A3F9BF10A6147CDC8CE5BD50BAC2BC2EB3048680E02231AFA3A348B64DB98F8596BA49CC88224D0726E7B4C6D07DA3F55CC8B6F36E5A3AE4E8BC142E5DD388EFAD17A0D2F6C9B67A82A5C78546787436751ABE9F7C924846BCBC6AAA62EEA06F0C28704944E3A3468BF04154CB9240D8719CDE1360048154B0A4A1ED19C0489B4B33ACE3D08E7534F85884E5BE0F6F4B57A9EFBEF2D5B4FA36166746993DD9AE2B5F68EF76FBC7A1EBE0A174C76D77382954211D9CCB7D24C2857256EBD4CDD2B91FFDEAC4668CD51187F24740C1004323CC932EAD329E05FAA940CE1ED6CF778B0FC7CAA88331FB8611254521CA29140FBD09D1D8EDBD4F5723BB237FB8EB56B1C9882D678644F1F7A46DBF14537C63BB0BC4E05CF97065EFEB5EC79D9FDE22D2D8BFC28F4732EF94FB415E771FCF85446301EB25D3F9B0A8C9747FB933D70D4CA16177A2D883BD6AFF3BCCDF2D5E40F7594AE2A9CE40F958175D450896733764E17FD2935C70DBFC3B097FDE66AB8B269ADC1F3A8A6DE49254EDC5BB90F20A4614D3413113FB75D028E9E0A793BDFB64829DBA3D03EF02735EF1FDE45B26F9B76E83AFEE61C4535123BF409E131A52EDE599B844A086B55962BDAA9C277B95C79E90B3999502C28B60796F98EEC4784D183DD57CC86C79F15BBD90B96131EE0547562500C419559DE31EDA7EDE89FDDF781B38E0F7095592B9B7B9671152202D2BDB73A4B9C1F32BD6A7F614D0F2A8EC5134FF6B409B786C517362FCF8F7B475AC73DE6508ADB4C4B3EF8970838C746956F1BF31CB96C109D31B113374ED1135F6AFCF4FE2324A247E55A502D635BC2B109C2C8E2815473C3CA389115FAE82F26976C7911C2673D833113D027EE9C13AAF87A010E5453B97E1399A65A56448E7D32E84F617960A3C733D88F8AFAF604F20F76A6A1A036FA3E1A92A35AFAAADDB2F98A92BB93BC8DF10523E9D25F372A8703368E45F6E110362E6D611E7D1A272E6DCAC4FF0238EA489920E2D5D190BAF394C371D2E65A6421F3E67E1449ED7D9ADF33EC73A2398185757FAA2565D39D489928EC10A531822EEDCADE8DCC5A2124F4BCC2485C97D0C71E83D21408FA957BAD9E184BDC687069AAFE52EA2F3989144707D80087A6C8C29B706E2EDF39A713022BEE281C1FB86F27B64C0BACD2361AAB05F640EB27DE192D8585138C079CF32D6848767C87694300B7BB20941CAEBD483C8E56A72FF628BE420E12A44123DDE85F6173E49B0B05A2FE0DA79D56B3B5ACDE24CED66BA191F099E689D9CEC2CBDCB741876CD7DEA5FF7E3BDF6B46E0435508306F0D14B557B3DA5F532B7043D6946A196086C25AEE2CEC575FFB656CD6743FA78F1743314D4D75B64C0EC7C2AEDB0EFB3B07BD86E59A2B2E6036680824012D232652FE6895D43239872AE174D244DDF6EAB089961985E59C207D1917482BF3F2E520769296EE3A133F898F0B753B3E5DC560A7C64494BB323C0583FE4FF2E3D607B69CBF13E90C694384A9DAC6548652F6B03926187DF7FA1926D3BC64C0C11103851FD318A1AD023C8BC04880179199C05EE9024F906DBE9FAFC5E31265364EF8820E5C28D0DC1556A90329176C098C2CBF7AB983EF1A1E84CC537B381A346506525CBFE992589FFFB293CB47BFCDBD685246BDB50CA3FA847839121130C3A67AEC32D13083C0A7F0FF46C26E776983376212DCE17A017CEF1BB09E7DC01ABED85268170C395125408AA988F48BDE4B800E6C944BC851E83CEF9DAC06FEA9FAFD1224C61E9CC5301BE19BB868866E2337368E8B7B8E72EFF3489200C7F943BB503D92404B1AE2467EB1B49D135BED031558B639BE31E163C99A577E756513EB06CF4A0016FF31A2A17F77C1BBF46F9FB590F5C9CBBA58994B35282A9EA8F6055179A94BB47A24C8266179D4E1A60390B50C2F9F499FD54432AAE6DE8FD6B8955A581C2BC3D06EB1EEDDC2AA0D50A652A42267B2ACF328E1CB8863607B1D8896CE8BB6C5D14404454797E6B2790F4C1EFEB12C5FBC281E52EC3037E63BC5E277BE9DF2F5D537D44CE1CD4BB45EBE4C60FA2F960D70F7A1A8683DB50B92ADD4A758BB7B574C8DFCCC761793F9948E6600D530ED75D001304718992E474DD9861BD4AD9C85AA89FAB4515C5532854ED8234608012CAE2652D64CB56C9D700476FBDEE693ACDE3369046BE5183720200D81F579F6C1BC8A6094AA5CEC1B2FD426EA7BF50B6540F43A1C0E656AFE71E830A1C728B52D4C5BC7ECB36F9EC70963809BCD2025D92D9F7828B6C62F015E1435B85B0416FB87FB18DB10470063DD6611A28148F1D9B5E3D66550E2BBE143FF436A0D48BC4D68369A72E56A8165643D2288C57A1FCBA856D250A41A7CCB8D35621ECC64D7A71FCA22F69EBF5E1EBA73BB0F19394AC5E5085E6CB0E25B7FCCE6B832470A80220AEE721BC4A119F1EBE9DB7DF4FFCCA1FBE8DB499E2B01C7B99629B4D783828D1006AF9D7DF683ABDB3D173B8367AE918AE84A408ACF84714C3A9CD4F4D78C9AECEDA4D22E262C40E588AAF9EE004440FD102C6B616A6D9D77688E49407A6278BA19F2294252F172A12A8351FDF79277487E2793BB3CBB6ADB476D72EC1C2D9B5F472EFA1DAC7C199C3D56362B526525B3537FB67E924E9375825B9F5AE5D866865C16F943DC50AC8A00D980D8025ACA2FCF488FA3587B17EB18E091892789BF72B1899AC0EF4079AA8D59DFC746166245DAB196801429B8B8281931B796FDAD0B75E5D113D817C35CB13D29BE4B8E75C369AD981E02B7CE8278DFBDDD8099FB4F6F7BFE95F6FA645BF841C4F40650EE4511CF1DA7F764F32FEE0A4FF11B42BAF406A61F6564F20C1A32915281BE3FD897D82837E084AECEBC94D676202BEB858A9D7824D7660590B2DE332110F2A83A2377AC35672154D60E1D78A0E511374AB0D91463EC3FCCC52B593A9B04F29EFC7EDBB29D79E052FA8AD33AF1EFB1D4E17EF94E8EBF654798A6B5366060231CDE9208B5656A80FCB3E703F2C5D5445C7FDCBAF36A4CCCAC24482DAC4F02AD64A5C01D16E222BC0D23C4CEC3C2AF2FB8CECF5970F55AEA96E6683A77F371FA73CC8627ED28843A201008AB8F50FCD79AF31064E6AD53A8784B8B3E6ACB3040642706FAFCE0FA3981852E521B860A62459F7DE932BC70817EE3DF5F863D32D3463225CFAD7BBECD19BC0284EF3BFCBB1C13A64D1B86CEFA3A66A9F0A4F6A799878036C41AFD7087BB06FD3ADC0BBB0C55FCAA25488F10DAAD7F0BA883028FCE814A0F3CC05AF531CCBA0948A4BC63800C5E1EA25A62F656C9354ED90CB88AB4CF83283154EF6751DCAF3F19632BF5E39A2751CA680305D442334DDA551B47CEC4EB312DF3F4E3C186B5B2C0B8A58F1F7C492D0FA7D2D3D17AFFEE5F88679D756E2924B165329D89690C7238747F07D320F9D3CF66D93D36244370414600C7B2AC08EFA749F8D7CBCB7A519A5B6AB6AF4FC749F0B4398A8703EA5CB865D997E28B3D0E9F59C99607791A0232C542E459F3109590D051C6DA9C17DF0CAA8EB755E5390FE9DCE569258F48C51A9E658398F1E1481410FF9CB3F521CB75B5BCDEFEE1E1B2A143726CE04C55D51229A6A3E52A1F4C133BD0B5F42BF856A8C6693F207DD148C48FA7453E6E1F708CDB7C3852248A9BA8332BD5778C20250895321BA78DA451A4B467AC3D0B01D2F8269E89C8EAC90A2C22D326B3DBE3139196BAEF87D781E31B218376FF41955CF2CDDA67A0FBBAE6B4B8FFEBE73D271B85787264C86578B7720F0D441ACE26843247E63C33B785CAFE1AEA05EEC774DED38A9629DE39DBC7C48A429B4BEED0255052597F29169DC01DC5DA133A81A344C779F4C8923317DC5980084FD768ECB9D6E71D88DA3CFFED2927146B82EE0D4577C99ADA44664CBFD4B84219FC5F8451559075761BDDD0A87BFE4C6B3A29D389BD493FCF8A60B64260008532920DE8FA0652189908FF80FD5F81C9CE4A8A767D67A38475F04F45286CDAE020164E46D5A1BBBBD09FB6C9F6E6CB222D237534551B04B3AEAEE097ED77F62078102489BD6E9276AE562615FC85EEA16323A6EBC4B14404F2C8B6DE20991FFC0CA0860B955349F09D48C13345BDCF6755DDDBB1DE156BA6ED3FE6A3E89259DDED0005B283F0C04BA4ACF3C7BFC3AB407F87074025CD17074D064E2DA0FE958863ADFB7F9F65B54240C3A69B2EBEA1682F200BB5DD25230AF64C60858207767ECEE3110E841407CA80E06BFC9D27F5B664E1CEA9DEB8F22DCD180069926A772366B083D896BED131BCDAF3C15962F5EBCED6E29E0F0E40BA2C1B0340AE1926F9CDF7F9DDB4C0B8AA6CEBD280ADEE287112E212FC8B7683443D06EBCE7174B217EF775237641413305E301405BB7E057C16320A05A1909760177F431CFB0B0C6843AE58E2F9F0108068E15C279BAFD2EDFC0AD8AD9D62F813387B47E50C798D952D926B3FC99007CB30E0BB39046091EF7ED3010ED43CCC8DCEC1556957936ACA48E54196797002B81E6CE2D9C48934ADC690420745BA82F6C3E5352DFF979916381C6A336DEC44B7EAFCE532EFE83D8A2967CF9501B08BC10C37A75C3942037368A58E0F3FC0772146D97E104C6D732A9E66F3F13FA4290B6DB1985FA77C45C95A8CB1BE391CAFDC50FAB6DE18A59A6010AD93AD593A0E0C41736DD1D52F8A7FFD457B40EE518C128894958394C7A91F26D191208454BC6462D47926786ED79C0D770FF4BF8A9341FF63D22DF458FFD7FEF73EB13CA33AD323C392125E3AAF20C9F27F15E11D284C2197005C053FE8E2C7EF9D7C5537ED732B03EA0CC482FB0370A137617CA522B8A8B3831C97AD5A5E641E358B9B824D7405E557BC48BB62BB54F5B906C16D2FCA0A435B87EEFC9CB550321D7900C2B4C43E8331BA03F25ADA3E9D4F4173D8492F6B0D533D7F298CAF5D2D510584725E5236C572B758B3F4AB28F5D6754F26C04A58B74A4B33E94FA3F583ED5BD97BB5B2472ED8E7674467841FF2C0BCE52AD67CB5236AB2EB8207A5B1BBD9645ACC54170C36FA4D72118DD300FF2FDD715F986F47D38723FF2485A88B377BFF67895C1EFE5A603EE6BA2B43257A8C568779B72F9E6F9083EBED9E4D4EC3E8956981929F53F63024289FFC0EF28E87E68932F1A95CB8221577E2D718A50871DB574B9B1C2004FDC04A265DD74D0F8F3E2971FA7976241ADFE7A1B20B55E47E7072301F33002A47358DC6069252433AED683820F2AC7BDBC3B08EDDFBF17D49334F34406C95C7C0F4EDBF0714B54177CC559E5D81DDFC58C8F037175EF2328D04B5CB36128679852C2673F6A111A1746925D3BC398A7A029260DB36274AF9A17307A4EA360E7DBF5A26EDDA512E48E2254EEBB584C29A7AFE2CCB8FA85ED83D29B11D7A8E5D2A850CA51384C3C499967B169FDDE2351BDB1DB271469ED8CD782A1D4B7AFB114A662A8898D43B1B522E91145660A008DCF59490BB582FCEA1B4CD19B6D0B317F4AE647E253C4F2EF1F78A7AE2DDE663622FFA08817795CB3FAE8828EE39F9E65493924E92B441E06FC351C9ED537C79AA89A1B20BF8E7798DEF7B553157288D4F0A25B869655E16A56C30BA570BE30BF551F2BF1E90971BAB66B148609680A211C20305056F9E853FAE7A603C27ECE20D2B2FF5ACB189D30A87F2FB33B6151517137ECB07D0E443A04FB6E47B58C86120C9B6E61FB00975A32F20F2D6E0235A14DD936F967CCD2A9E3A7BA0FE7937F862AC8BABDDBC9D5EA049366E2405976BEC0F115784E322F8F59E20B6F0546B578276166A31552E910053D935D487F3253DB2DF57D7645DF1F0268AD735FBFB9AF4A643976E9F67DA3F2F5D359C136DC1285CDA4D02EF8DF5DBFB8F06C2785201935773F9E1A0AEFA89844185EED6C96FC152BF8209375A471BBE9FE001CD4078602C5E40B70A7658EC25864CC6A24E2AD3121B27D7352FC482CA306D787A95046EA0E1EF6471CBCD0860917D1D823A4534674DADC5CDC63160091ED1182AE16B03018DE0B920F9855354E215F3AA10A69A4A2BF2B9BA54834EB410DF6833176661535E4868900D5804D34D8DEB7976E43D08EFDDD505D3E6AF1F0D6B4C6C5EDF1FAB5C31476379F8970453308D974089FF1F6C6089CEAB99ED4AC05C292AC39C612BE560578ACCB92CD10B54D14A1A8665C567DB0430EB1ED46597FF45B8412988BEEF9EA514A6E27B68E2CD29468DCDB16C29A54879F568C493A7576041254770031B0A3732BFC581604C3663312DF4B77F0C45D2F09F9628ED162DC8C466E03377427E1323432FA0595AC87D4B202C702DADE8BF20FA88B0996B03CB87FAAB39327C51D84E845D40B74BB1FEFE8AA2708C2BB8E8CF47C17028466D833734C19B23B64C51B2A1B6C440510F773F2D3C25711F73A9DA486F384D244180D84761A47B79C92C04BFD7D8AAC3688D29400902FD22C1E607F05905F898E5349D5308E684885D1833BAB0D0569DD7BFDBAB1595877B3388ADD05753CE27EE6F44742A6127863756FCBF301F9D6E1CF6E7E346AE7592BF16FBFC7CE44253A9ED2FA7D8C1E480CCC3DC786E377418ABA2105D83FEF314B6A4160A8F34CFE80F789F24487C59D8C9EDB21652DA04BA800D735D7AA2681616606F48A01C9CCB6C499D5B7490A5D80622D708E0BAD6A20233235A15F871E9551F4CAB7E45F0A9DBA312F2C1FFA8A698184196F22A920BBD478A773E463AFB82F2F323B3727A78AB9277EFD530C65EEDD707CD491A9AA7D0B8D362AE004591B112D0E0AD48B0B6435680560EA34B38800517193B6EBB5CD0EED4CF9D70721E4E2B29D139EE7A9FB931250CD6993F5FB4A87A2C5AC88FF4F5114C5694B1D792716B79DBC9A7C9DE0B9B7493EB273230FEB000F069C02C7D9972793F34D02B22274B5552F7DD5167D187DDD2A865C21D560BCCDEFD764D253769BD132AEC97CE80C8B0A935F0F2D4CFB5CC8222CCAA2A7243D1C4509804DA8FC3D98D957C7602E4388D63C8E10EB065CDB51D98278E6358155AD936F7972048204643A14A4882E9241A28E35CBECA9772EEB5FA9A7CA6F0DB3A04B51B920617869CC565AD992FFFC53022DE93CD6E844BAA5E7883E73123B371309F3F066C61C916F9A3DA033AF3C16B28614131F5827286259A1307FD55AE15FA47C5B8C9C4165DEADA98DEDE7869234E4D7A22482F06B2290632A136CA677A7BB4E53AA3E1170D66BEAB2916D6BB2C6A731089A3ADB01346F45A940BB9A392B14E5BF47B749F74A19396116DA6FCE5CA331D990CBB9532D64B9D79743F866479515F5043BEEBCB59C45BE3C4C833CCC07070E5BFD410B21087F3E9D67716CC5BD10EBCBA6D9A4D88CA32347B8D0111EDF3CDCBEA7D27EF0325E904FDC315BF7EBFA91FE999739D5C8C12C3D569BCECA56EC72051356799CE350B29168142C18B2387B3B344E086077B1D4AA0D1EAE77972A1C55F2E91A6DDF8D5B0BF9E3DDE3F9ECA9A4EFF5141F8433D94F0C455C3750BAC7514B46EC799E167283FDF8AD7BF7DF67ED10DB1478D69507515D6446D511DD6EB7A27AA2B6A7EEC99BA3A44A8AE75E0D6CAF10AB8B7DE5E330ADB9FE2A940BF367201A2BD906294AAA1B83B0BD181CEDA5EFCB146A890993F4EBD128CD3C6C9E56C28C4605D9B67F553BEAFD69AEAF7649C5CD8602E1653E3CF535308ECB2C31E5E573493158C994DAE7FB26D28A2B1EBAA3EA4AACBC1D7528E282CE2EEEE61D7B2EFBCE6754C111E44C911A4C9D004C6DEE252802FD8A6CAD98EE9DB53F750141A16BDA564E4A38722907ECC22DE53A8A92AD167FADF2253B684915A048A80E71FD56CBC73BE118FBCEC86C0772A86C05D6ACA1AA01F685692CD8892F20536F9E546E4E01BDA7EFA722DD441EB1089B9FF916BE0EEB3D177DC80598B9C67C810E361F94180B0EE81052CAE9812E95B00175CD9DE8E70AE6D614CA26A9F7D4F08CB8C8AB1939ACF0DF5B0DE6E58DB4A09470AD343A1EC0022620EE9A6D3515A9AFB875C41229AA32CD3994D00A92A769E6CBA0BC3A4E9933968BCE3DDA58EB3016875A6FA469863734C52569C62168E157FF1228EB62CB189D63A6FD9713351FB191FCF71EB2B65C2D8C645DE6C95A1BFCBA9D90F6E3CC4A0C120FCA728FC3E1696A12D106BFB5D9AB3AFAC77698C08913C72DCB3944CF83909AD02910992D3EDEF6D6249785FF3600FF89C1EDE6EC0BC4B93A8788B4176A198097D50D95D3C3B73E9510B0E5F656A8CFBC067FB701FE2C8E39BF3530BA40849D584EA51B684E210F0C188134D7801DB92310147B20898528A510E84FE5B65FDBF0B711A8474D29C8C1FB0125F6FE939526AE1C89D4E0E388BD33531142FB6700C3ECA865F974719E57A7A744271FE16C54AAB3D4865D449781CCFFDC1EBEF1B6A8DE215A0C99E87444069C4B5170A7CB3CAB6F0D7150BB67FDC1C90930FB697989DDA3609230CD06DFADAC75D6E09D83EAC8C7C16E4DBCF9BD5556D9625B5EC927C275B68A973E6E131B49F8B16913949C290E5DB45DFCDCA7CC9094C6F0E0EF228BFA569AFA102AA02D44F3CDB673079E00EAAE195CBEED936D7624A140DEAE0EED002F3B751378989CDC6EE892CEDB15F823830E88D56FFE9B92884E3A5B871B09080392DF46047008A49DFFF10D9D64DA0608977B6A409DC365AF23294D7D95343F9E77834EB663FA6BF6F1F6ECB40E1F535412B02B841DF42FD65642FC23DFFCD427763085471F7E185306CE5A0D0E5FAFF30F0CCCA9E8DC1788D895989DCBB45F4AF8ED2EE9AD1E8E230CB9D77A36BB1D25BF54194E9AFE951D935D38D783BA66F75953E29A65A44AD8D158567D8F791D6FFC16BFC5F760458BE1444601F2793CAACCA925674B30166B801954003A0706B7EA05FCCD797F68CFAFF1B7E6FD85230B6F33FF2DEB39D27AD3B4787FA378A3118E2765442278EBA561040CD4EC39107D639C98DE5B4796A504EDDD6ACC6885F02347AE5FCF28B92A1EA1D3AF83DF9E7511201F5FBEE65746E3298A4502B9448E243960B5AA7D19D3A529DF9DC550E226161E2DF5E071434D51EF2F45DDF3C1004F180A152ED6243D3543717B4B8B72029C19C7AC9BECC4F273AC8AE3E57E3874A1D7449A27ED5EEFEFDED522C00233A95C58D75FC9E083D3FFF77E3DF86F6C8EC4E4B77990A8F6E8D3AB85876EE8311569AF28EEDA2F2733CF90AC2788CEB8979B4AB57B3D69DC01E08519AF3F8DBA56CD7166955A7A805D32F510BE61A84658CCBF8FC81906A2592F6470D80AC834954013D8C37C3CC9BA46CA24F8266FD2667034AB9FAA44271E37AEAF917AF2FCEF520F2A676B557B0DB8A9880069FA0FC6A786C9E2B699CA6E02B803D26CF4DD6BE519C4AB642FC00C0356CA0EEC076311361125B685BF019D9C2C1A0C21A5C5664FD851731F7D49100ECAA9C0207C99FA27E0DC56ED22D81C11972C54A89FF19F1C1A7C4181C0524782741A2B308C89A4853340082F0722C887BA3780623E4004BB3ECB74F7C3F8F92D20DD95F6F2A562B5B106C4AE8BD085BCE15D9CD78A093CAFE8B79F9385DECD050505466FF634C1CDE74A03F6B0B903FC33FDAEECC6C2FAE9D7B26D33EB41CB382328A083F954664508D5F9D176EECF5C93BC635A14726C379E5D887DF0832BD5459E13299E559AB030D14AD0DA5AD7FF42DE9AE68A4730CB5C7AFAE19D7549BE31E70DCC2811EE62B1734CF877D3307BFF3D5B3D70D0203DE2B7F7F4C4853BF9935F4F96B96E3E93667E0EEE4DCE6B9F0111F09D82322DAAD7493DFED4660A7E989148079A47C80E0ECE3DC7F5465B550AD0C7FE850ACD88B1EB0DB40F87FD9E18D9624994930B5BE9361953DBE68B58AB2A40919F3C9D709CB60160E1FA866EC884F86E0C143DCE2F103AA43DD3442E0F46850501E20EEEC253C113B7CB784B1233600C53DEB4854335102DAF9AC45F5C07D038B814B83AF1F2249FA59D11EAD2AD84A772C5E1779ECAA0B68C1900E4BE313A569D94CF45A52D3083215A060D3CB01F66F2F753E1FAA8521C3C51599AD0AA2D2EA061281AD512703C8FE17645B7047C1D828CEC8816B6248AF2BFA43235A43FEC1FAA7B91B55742BCFA4948BD75347882BA2E13B5682135F31382C1AD460F4FEC5ED19961B7CF402168A14FA0BFE09DFA01172BA11FF730912E033F98E3C34EF64499E1FCE8E5160153807962320BF3ED08DAEE43F4C5D5A38BF86CB7899B83CC2DDBA641EAFB0C2F37A484886A6A078D7051AD3BCF91D8D0FC4D565BEC2E484E3B31229007475936F49D9C4229709CA606192C1ECE9B966664AD520E04F880207E60F378A039B51A2126FAEB82AF316907BF9AA106D61C71E4E027F7C5C3CEE6C422FD107CD49199AFF0C325D5AAEFFFC3CD03018304C95136812A007BA46CF9C7091B058A5E6C28EE314E6E08A461239BB4F9E89C139861F7FFE603138A4179ADFD58714D899D19A037866D3C9CE45F9EB51E2A77CCA4C73097A9DD277BCCA5C89D136BD860CDC8BBBAAFD8276F4639E7B11E1E229F1AA7BEF83E725CC3210926FA1C5E9FA952B06C266D40689FF9EFB9CE246FBCC342C91CA57F4CEB9D7CB11B2818838C89D7BD5A23536382305BB0E478788B81E25EBBC0E07422B495A2E089C73306BD0EBF10A32D5EBB06CF5255C8A844D0572EF40C38059655150D77E29BB3A205E4EAB2B7E0B8CA40FBE093956BF549411858A93A3048ECF8EBA1377E6E3A88A90989FDEB8C62025255FEA0F46B15D41788BA590E9FEC5C9F28068432A16601744696173F7382E9B63CF06DC8C3093644A1C63F8D8678CF3940D0E57F1D91B73F1728DF738092EF497E5914008D00E532F3B4E1C817745854F007E397B5F5CB88E420D35705428A2B3035ECE3324BBC4777A27C91026CD27B47917D34AB8B48D4E76BB2F2F97CE0C59B923049E1935EC4373A0999F607A7AFAA678A38460F6F36D019D3303E593DF444D190548FCA0DC0F9B60586DEA93745B1E4B7C51AD270D49DCA9750E1F5793387399A006BC57EBD3C6D972FDC95B5E4AB6AD68E1A9E5839C28BE097395F6507FBE2BA06D7CE9DAF256EEA82D2FB94C4BB0079347724CA24C1D3173A211E0C3C0BCBE2CB5A3E5E0B5827B29251C7E0F542C8BA11C0AFE604D9E817040C5E1402527F42204DB1C06861A3697D60992A457AB88E167F80A418273A91CA97FD0BFF9901DDC7E0CB36C9473ECDEE185F96CA59CFA710999ABC92740D8E87D29373B081118B7CCA4090C97B4D7D7DC208A54F24B1EBBDE3790473363AD94737CBF4C1A64E96138441EAEB43206761DA8CB1FD1131CD0B73D92E0A65E812D57723DC56EA49CCE66B07232BA539CDABD5EEFC1275EFF071A59FD3AD71288686D95D1744E9344003CBB7E8A772B4C12BD5657C4BF7DDDA6C4E4C235C27CFB5AFD8842E1DD72F4D98489A5EF7FEEEDCABDE0DEA07A7113C0683D147E7E6AC626556DC0E066E9BA5CFF9AED35ABCEA968EBF643D118DE57E4E486551E7A5BE3D47908F923F58BB9F1E68AEDFC6156FFE90075C56D563D197A1D39BD167E431D103270735A1B0F70A84E8AE51DD86693A0A22736F98D01D4A0D6B0709B30FDBA4BA45A88EC2749739F947632C4B3B0888D35E963C26F37C16E6A2EC8A76B4D7B9AB75F386D6F968964D84536CFD706A2E1374A1BCF773D0D276EBD1617291049D46742B1111588C7C8063E859E7C295A5C047CD669CE56E7FE9AD8CBC0361C9DC68ADC727F3264D453133CD54BDD415F439DABB2746EC265BA8581C0E30D73D0409AE52766B3E0604DEC7814C9F5348A719B8716213C62CFCD3A5B6F11406ED1DBFBBAFBF2AC77751ED00BD18B8B7B811AF671CC7ADBFE3D141787BAD1FD6E03BAAACCB3A9A9E72A5B04C702E74413CB29C7BC71F87531955E9C31A81D2C65EF68740BF66D0453AFEDF2DD81FBCA8F55CE7C9D1967964D752D3E9E17F1256C5DF94A16019F4772980458FAD076E94CE4056BA11641752D7AA87B68089FD28D53ED774EA108028291889E544A8636C41E69CB20987843B3E94530E3310DCC7AF99D533CDE0F4573A14DC077E0F26BDD1C6DB3D7770170289A2E2B4857BFAC37EBB8B82528933244E64BCD3A9F530B3177D69611E3C4B222F302B1846CD810A09CACD0E5AFC0E5DD539BE9B003A0365E47C13558776D050441392152BA7F4E45438E6D8E9B52E013528C99C09F885258E5EC830CA9AB2EEF54D3497915445E993982012E1A56FC4D52C67463E836FFE5A0AFCC137F72FF87A11624AE4B81081FBA5A47E7F7C1F6D4038F50663DF63F08A7D3F812850F7E634FF7930A15BB4788E4C99844DACCC113F7940C661F04D2BE396E5DBBD9416F8B7ED1FCA798DAD4E0808AB09B152ECD5528E72631497B48C36751BC36332E2814B7E132B241D3CDD6512CBD041E5F571D65773E1A1D9B08308118CB6692409F5D4A7A2D04E42E404EE0CBB1DEC95083FAEEF9FBB3E358680B25106AFE63A2146F5708D99022DEB6955B99F815052B87D7CC7D4F7C3C2D49916247CA5244222A566D578C53A2D775175AD114E8001E1FC9659A4E2F3FCED4A0ACFE8A217BF73AA0D16E11B8E07DF3D7EFDC679D58A8154E0E1F9F41411E01C8E0C910455AB6AAD4E3A44BCF94233A7E0AEC8B195056FE71FCA523E7FD84D915D5ECA8EAC3A0910E514F1EA4F30EF9D6BA772C863B01DAED0CFEC604894F0F9E1A9B7544B60A2E6C661C7D9C571F7848CE4E1BBEBD8C708119EBD66E806C52677840A012ACAE5D13F08ABE2EE61D4EACFB2312C0E5F8671828D7BB6A9AA4C1B5342F38D5437772AAA9FBA6EA7E1D108A971F6C27D86E8F4B006EB5C1A396F1CA351FFA34896CE137365EA4919034F445631E2A86BA0FE9EFF4A6750F81963062DEACA51D665FE85B50D82B0C2EF9199A7F6A3D6324346EF7494F434D6782C7A7F0385347D52B1EEC4F59150BC909161B28D674B25DEEBB8F47910657D25D3BDC52FABEA2D0AA3EA34F3E1225F472156B39332E60A5D6F4AA096A1EE3D06B0905C344895243A01C6F1233A939A54374B077F71B4A901EF2E3446627113C22D9B9F56DBEA4BDFB8665676B952625E9964128A4A5897EC9E37D37644B2324CF322038F322F7B276D07D4638F1FF662290F8CF12BA28F41E64FB2B4E70B8D5981248D826D85E57FE0510D7EA33A99584DD8EE33F00E52F708C379CDD6C2C1D39420DD1937CDA43AC174DC5F1C7FAF5D40C44C7741A98897A258C5F54B8818E740D9026E5130849A5F47EFD517BED1D346F7860FFCD839D42EF59E9EB3BCFE24B378D5CEC0C479D41A121A13D4202A52BA686008CBA9E13FB36357F0BFBA2761D594A36A3BCD200C47B2CD43A0120D1EBAED813C6729BB9296A83F38E4406FA9E0248DA6C475F4A497DDCECED02655755C53FAF87A39E31899C17FDE0A7C31655178FE7D90B5C07911D7F6BDC37265880E3261DD2EC43B04EB558D54E187DDE06F46CACC37BC1476379EDC09E339F3336EAE058FA794575D134AFC37D76C27844B0F530EF06DA96A067CA8B7DABF94BC331C4783741F9DEE5AD6CC3C8F6E2BA34130252CB6698A3F9E0D9209235CEE13D61DD78174BAC3B93C7BB5807A4D6AC4718EAF8BACA7938302201E67C7A77C8DCD6ABE2E57170B44D83DF67551A6AC4236951F1EA51426C9B152B9529C7AD007B97FC3A9902FBE8388C23D06D12ED8FC2CAD8CFF5A1CB5AE29229E7AB060F86A03CDFFB48E5AA98AC748BDB3C7276BF3C9673C90CD95AFAD114D585665B7579E26D6FD106879CE06A2D0215FBF222283D5944F146EA1A63BD1124327F4C9E95C4EB370EB047D60D8913C8A8AAC945815AB159A362C176EAF945A96089D26D1245B490E0B1549033A122CFA3201CC575EC2890BC9EC864BAF9FFB78DF1EF7DE543BAE8D81CE32F78CB419DFB7DD395429375A9D02F3FE6024863D3B1C957F40BE30A9915271931FCB6B17E585317F1D3CC020113D72CD8595F47194CB9C7F9CE96294295F04DCB70B377314CC4773A1CCBDAEAFD58CEB127FE758EE3B5DCBD718BA5C4023AC5AA99960387473CB11131BB488BBDB9876920D88C1F31EF115AA60221BD74E7615EDDCCD44EF44B46BD7D35DFEB5B618EE51E2E9201AF4A313060EB3F4A2DEAD29E1D05F30D91C418BB1AABFDBBDAE414468B44F3DAE500E14450A0090FCC8394373BDF12E1019B107C261661197CE726EF933F6D5C81560766CAAD84B195E5652BC4D597561A9FF6B42B85357B710714EA034A0A8373876AB6479433C3478679E16A3F9B8774A8BEA5C3352CED87CC8D7CA50D858442FE9747B469E9721C76DE6CF55E5EEFE2E318BF4BC51988E7313EEF1A70A754E7AEBAE2FDC2B978C307CF3A40EDC1C2FF92FD3E3A8544FC6BC9FF8F5A60E27AE63E74EDF72A7EEF3D665CC96C792E2867D155E46FE3AA51709850023B4C76592B765CADA3F20B82B6F0DDB9F90960B67E0A968FB9F16141B8F6B322BF24B4637188B985BE15588E25A59583CC43A14A1BFB4FD97909B6A76419AB30B8ADE8A3D25C940D28A4C88F7648D32C6A9D9198F71C8E76895A3411C7531F1FA6F01C6794CBB3D4E7DF4035C33E83D377140F55DE65E26DA0BD14A55C7568A98F3C79F643CC09D609C212D155368DA2D45CB3A268E0EBE902D60C88D9CC7D5BFE3A5C678A33A426F7799DFFC07A2D39D76BD26FAA1FD70C2DC2AE901D3D0D2CEBB132A8C90FBB802DD0D062F6B623FB0A259998E3A959D298A057C6C944BF46AB61038F147AC2A9E1C05B2A6F1D2B790771D5CF439B69481E568724DC31D7240D48A1F155BB7FAC3B1348E959F91B44098415DCFD06049CA282D899A644F61492220BF1FD9852F02624131251DB0FECF030C394C49B9C5D4EDB77793FE444206D378A4DBAEDE173691ED42D1C3689EE251E26A1B91945E66273A9103E741554E509FFFC486F124B68FB1546BEFCBA3A9CD226AD48CA595C8C9F1F929C6E26D9348F51B8C43AB8B10924F17A1B2DDB38842440ED2CF259B25D70D41114DB5CE2DB48F0661B45AFDE24F6F9DD043E5C5F148D09304F687F54D024403BDC2323303549CC5C2DB5F5003DB029D278AC1B05AB2D2DC58D0A45733856711DAE8FEE9BFCF2E19DF4E5CFD84BB0DDE1B5ECD330CB2CE253851F6E3C0631ABE9AF5263E84A6145FB28D0651907E5360254D6B53F8D6E402129D57EAC6D23E2201CF3B157D5B18B0AACF2F433BCCAEE2829C2729A99470EE7B0757480B0D91780541987EB08C27EA0CDC9BD0145AC606327EEB316472D1C70BE708285E87BB6CD8F41DFF539A95F159E7DF44C7CD190DC60F872CD2CC45C945FC8A127E0A31E1C464FEA1A35C0F99209FAA544247A6016C2451EE1097A7BC1C61B2030F9060506F951718B20C600BDFA9B60E53D506B6A43BB8A66EDECC6AF44E936DD2142C5EC2611D81D06EBA82165A3517D5B795DA83E02B262E36CFF1846890F4051085EB9CBCF34E2A2A8FB564EB1A7A49DCDA3C2DC2A7FD65B9C07E534FB8F72183D6F97259BFC861D74D34A03E704002D7EFB6CC7D5BE64FC45628FFE56D8AF3D081F2E825324A34C15FEF22F49B96CF38DD6CFBC39F8B34C590D89E7EE0E07F3E53D9C5FFC9285DE96860365908B15C1E6C6ACF925E7F4E2A4D3FD7EAFC680A1E88D4C5A3F2DF573EB071E03F18D2D3BCBF6C11FEBD3EAC4DF834A539BF2AD5B4DA2B33B56072C51A9432121188FC0744FE6DC148213E7444C9535C12C89FFBC7A3E0A2FD759DACDBD9B16B720ED7DA0B726A242B857C3E9769249F2C35BDAB4E25A09191DF588100D4357E20EC3C897E7E62E6E7662E1D1D0543AEE5AC14E5EFF2C7A272FC251BCDF56C443D21CA2447AB09780C25318136E100168A5D8889A69EBF5B996AABB71A0433F20404EEEC4D9C57E7DC8CF55C44546EA4EA39FB393F1C3156AF63F4D200D9A9954F724E50F8756EFC8B839C25BAA9E10B858FCC266B1216273A4015E77F53B1418C29F1D0406920FC791BCC7F8FABB891130B74226D8B4B00F52B7BC4587F838BB5DAFA137646A53F89755BC3D921AD4F46139F676EE29D5BF871560DE67EEDDFE81354288E73AF6895BD2D8E66B7945E2C44A25DEE23303662D61B908613104C18D2C83081A4AAE13BED21F7395E792477BEAD999F0CDC37A095F5B51BB0DBCA5EA2653477EF35DB7A3198B2BDEFEAD01DE91CE0C27086882C1078D24F4F33AF37FCA2E921DE72EFE9BFC940129819C5DE58F32AE2F5050690A2EB4624FDB5B46016691AA15688CC280E0575038CD25E055C469B3EA41DEADA4BE24A8ACA85BCB0B827D1F234D4E07C1FA64FBC9957841B381C9B1D1675F18748674888F8B30755B6A7968F93A24837C988AC049F51D070D20CC930E26749984B1F5884C5A6F818A3E95CBFB5B7DC9C0F033030C01CABEE970126EC73B19468E7BAF87D9086614018D309F72DAA3A7CC085F29366B5D5FE1174CFA0F6792C718F994B15B9C24F90F74ABB60B59015D049067B66BCDF7E933AE2C7A10DE45496EB55182208D0F4B63B46916F82E85F635E99969AD48E0B4B38B583CFEFFEE26332319E5AF37B97E5E6AEB791D0F8782727F23C0C40610EBD443F9EB5B3EF05C6F4275E72E60A183D432611DAEA4BF9B43CDDEFBAE3DBB4F7CE64E1AE718213C3346ACCC4FCFC183EFD20E6A6D71066B8A1A266A179FB2BC5D40F569400F067B1AD65322499E5C4867A1FB1A8F7F0B46C3AD6B7C27055B7168F7CA5D3FECCDA165540B59159FDD84D71A9543617F3771F8F48BB4FB4DCA2F4CC149B7BF9C552499F8A65530C493C3DA0F0DA3A9B58A6221E546A8FC64F4719430E3F6F32B810FD981C68057F796798E940A75FC7A9513E79B32D09FBB37B1F99287246395D18D3546E09E754BEC0DB625CFF589194D350F29225439145E520CF7799A971681F36773332DEFC4ED869AF523A9606C6D55AAA41253D233F1E79FEA803317039184B2A6E899A1E246D0F357D7C709DC2F37993478D189C79220C3D296A3E84ED5A58876CA0E8F4FCE022F7E606C4DC6388EEA2AD97A52A753479DE262105647D0896F584E2895A54DDF1B99C0D64B5C6EDC36005BC37C663A2D5CBF25CF7AFE8C022A83C173A95AAFEEFD9437C178555FDAFD1BAE801BD6C9D244F529F0D5B47B18DCBA1D29E668EA384B152739995CEA0379C4E12B68E03A29B1F4951164A656B9E68FD3B5F7CFE86701DE6DD04BE7E0249891D9FF557E730C69BFAB3F53649667D6EE34391CBC5BF94BF16EFE07EA100C7E44143E603F4035FA8E995B8088724EA3F022DC23B33EAC9A786BFE1175B2F1D0F1AB24C3FB731342E699787AA1B72F98DA6335894AD731AB7FD014EF620D36A486CED1016CF3940666EC82226609FC386006350DE71E40A5A598BA20070A70B9BB444E1FF63A96B919594B6A3A15B050BC40E3F5D78C7FB063C7B63A375908D6B6FEEA0D610F85020C6394EB152F1FCA2F8DF6EF69EC558A3CAE308476FA9F04A5B944D82EB8961F91838D01D46716653ABF436FBC4142F8537E47D10C50E56088FB8E1AA351A71B4A37DB230C8114A0B4EEA4FAB0B948170EF82A3CD33492E23D33E5051D81AE24C2E64521371550D70B466F91565EDD7EEA4BBE5EB840F2CABEC35BC77B1747BEDD95BCCDA0D8E746475AFB8AA21B851AE18E2574C85CB1A399852ED6CA6D4149DC98D38D691191264D06A5B61541EB6953F385DF9EA0821FC1CD4939C561FAD822F3064DF63F726B1AD8DB2EF0CAE44110464932F1BF83598AE43E1CD6B22127069EFFC525BE8B96F4E36EA645DFBDFE516959694463CF33504D222C8B41DA830D56505D30E4BADC98B1638F64B2C5E4E4349659DE4484AE8AF0EC68D44374CD6FC19AB1B478F291CDC552A787E87EDB90CE72C2B528E98F18BE9F60CBA492A4B0545A7F5AC9C3852EAF80BDEDFBA27DCB495E3D06D47D3F749460B14E46EF59496162BBFF400161DBBE2048D568517FEDB4A29172E16C65F338949AF92A3A444C16AEBC5A617759669F190853D3D494C4C5B7F4CC76E78C22AA15B653FBC09703F4EC2235C64297BADD0B0FCF21AD5CEB90BD5194DA1E8786BB1692D224F701157D28467EDEB70D8A5B6DEDCB9B48C8160B51F0CEE42F64416B58E5FA7B5D650B00029D4FF78436C63677BFB2432E245C7F9872A470ECDE9D8D201BEB95BA77F4CD3A6A2A7155A314684DC31DEC36F69C687E18202465BED6645C8D4CF2014B852A2A012B9C3B5E930693FA14A67EB54F6200AF156F0E09320FCABE98FC588523D18FD47439E437F108FC9C90CE1701A2D9FF2D06A5D4A126CEFBBD076F56254312FF5A85DFD4399F220494051B84CB601C710BA80098ECA704C7F4AA2BEA81557F3D7E8593010535A0EC2A9593B6BB6BA0EC76E64F6228C85CD21CE3B3E6139E4E8FD39FA8B8A2D01963265527C8D70E83CD59E74C4A4B1209927013E2A6989A94507B20A837792EEE5B8CC6D577AFFE67EAFEAB78729E96A388F9097BB58040C734C376E4B10D05E50D5DFB4BFC5F9E0D796726709EF3939DAF3EFEBD5A9CF455748C88C4EB6E4EAC33D732F72630BCA56BFB30CDC94E8F0456833BD1B505711E3BBA836E84E8FCC51E401F8B6A846F44193EC32857FA485CE2C5D9356EB47C53A176230E7A6CCA13BCAB8BC0B0EC1ABCDDAAAB61E5748A93140E2C553E99646252FB1C67A4587F84DFCA6114DBA080E5C6B71B8F7A1954AAF85A8028063DA237300A161F0FDA6372AAB3017B3942A7DA3D5EA1248392927D1524635CA06797F3F05AC72972EB2DE7E7E3504A9670767F2C6661E956ABC8C0AB0338C3DD105E88DB51193AC159A83D09DFA7DBDAD0B7777A96BBA8E2C060003E56CC5137E1533883E29715D41EF6A86236A3DD3A5C0420C19476543969D2C8522AA4F55CF61C3C17B68DD12201E449977662A00D21D7CF682D48706939A58DCC670E6BB94400979175C97A7D021EB25CF22E75F4DC5D9D12F578EEA76E08C3433779A58B24A7818485F68E100A2ADCD40950650A1812B3552B4940482B5A2F8C090DA78A75FFAAE38BF379E601A38F7FCEC5BF63C3F1EA5270577703C6C1BFEC2D4B7DC22FFA3F5C99529113808C71F695B0C387BE7857D1A2A59249990CE182ACAAC220FA983CA489344523A6D8CFCDBAA5D4F5DD920170759DAC677193E4FFA56D0A6FFAFEB9EB53C85FB40CF37AF30E940CCEB6F54FB308AFDCFB92C4AD7EDF78D14C19B2417AFA51D8A6BEC7A9A44E31FC1C76E24B505D409FBAD2483DCA751B6BCA24175EC63FC766D307E1CF38A862E8C2CE7FC4E5862A73C709C1B37D8DC006C4E3181D06149B7ADF8FB5E5CF9ECD6F566B1AEC005C37ADE0302ED9F0A810A256A4A5CFD0750F162918F9F486315FDD3058A68502B71B77BA8B2006B5CBFB1EA1FBDE3EDCC896A970581237E35213F23A901269908C325A5E740E0967E5561C14C1CEC2F99E368E0996822BCFFB58A0F99A95D24D13BFECE82FAD84B50ADED6899C9DA436554CD40D4F32278D48736A9B4A3E2C6317D533FB026EF7E0AD8345CCE92E2F5B2E330619ED3E8594BA0445A7BBE055EC539CF15A669FDD6D1F9370176EB6274B1B58D89E3BFD5F82A8671D558FDCBDCA2A7E28912EC2373A8E612FDF9B5D0DAB9843F3E0F2072107DEB44AC3EF7F02F58D5335B233B95909BCE78131F0CBD4D4406518D9E95A9DA3CC29EB095CF32CD958BD8A76634D6D66A2B5B64212843B914BFC83F1DB81BEB4CF56A464B20B572C871DBA22C9F4BE66631F8A49F57B5DB191DAE116E49977FF6ADAFA8E5025E3617AC80ABD453FDFFBEAE1AAE31396B21929B7B9033697341923DF51F67E5B74081FE0A046C18F2B48511302288659E86484036C1978A5DA21CDB2E64F68F65339BCFD08F0796FC18BF3350FF16D7E3FFD53695F44358A7DDF63B56D1D724CF414797A38F45AF2B434E1F553808D20EF914F8693D8AB4EC5D0BCBBD3824DD58121B19D3849273632A6E195ABAC09D66422161F1DB9D6A504552775EA1D929B0DC8F064950DF592822A143CDEEC97D726A3A65D0C5E1008D7CAE0528261B28738EE2202FC9F74FE00408EB779F9BA84E13CEBC4EFA00994E2688C76440ED9A47F4A64F297D69879A85F55689FA71A4380FE3776F7C2C11F68F7724F8697E88B67724CC238E5BB1AE8A010CE7E4743EBEE1CB7EAE53E018A822D88C4592272B3476CB3B974CDAC8388E472B29C5DD6BDEC9C8FEA7A9D6B8AF9D3371BCCC43C00F3E4F71DF1122BDC4530E73894944AA8C4FC7C0EB31FB0D24BF011F7F50F1552D50E2BA035A6B121F61EBB62D4AE3686C98A842E854BF4B8055BD9F18434A6A9CF13BE8623C0F066FC40D4CFF304258B34BC7AC97BBEEA0766910929643CFEE1A850C6A057DE7AD3F5C10B943BB59210046B3D7613397948D86E5FDD3F37C926450F836E1ECA5BDE6AFFFC71C8A4A4173ED826378356C8D83832CA0099959402EB863AD6D7FB847D04767E1A436257DF1D396143EB0EE3C7F5B02443C3DD85B94A035D1E613E1EAE53BE0113DB33EFDA75554937B31E193AD697C54D927C67CF9340FE25950433D090A9EE1BA1F8C5F7F20563A168DD24998D404EB6E8C8E178BB1EA202E313A43AB5345EF288347FD4A19C3123483D68B5D6D9267D2D50B9230CDE2EFEA28724B78A609A47B02CD015B42144CEB7688C3267530F5810CDCF4744B65D6A369B0658BA8DD96645DD4C390844CBDECA8C5DB1EDFC40BF401BE422C93C7F552D321A3DBBCFA1DA8DE98B2A3EB85B04A258E47F55621904D302C3C3DB1270A0ADF363C525C1C87B7A43D8FC3AEF52501ED70F30A841F205104D4C2A2B1544AF1B475D7B1049B5556C6A3CF8B0A1C82A09A163CEA7C45602B149BBB1DA8936326E6ACF23163DC77129F38C226132A1710702A29F3E371497748D29F4E640B5DB5BB5715B29A943E8ABC1AA9BDB916807C7719201D18BE3CFFDDBD303170CACE471EA5C278A64B8BC47BEA5A8275A51F299643A42E046986CCE55185EAB04146A39D1F6020A681E5191081E97A204B1BB449468523FCB5B825760C7617127434BC818E5A8EACBC69F50D0CC46ADC1B418771BAEB6F6C46697F1DAD56F9812BCFBBC2288C75F17E412D7A68D308E5BF445397A9CC768E397E8973E010319427F0F6BA8B53843B0024CCE620E96E3906B45A17BF09702232C140A000368A76B147823D0F7F19D1372CBED4CB2312264BE12B0021C167FEC6ECE3E4057919725DFE7CA2436476C3B959C4AC5CB6FDC3311576CFEC09F48FB8A79DCC3BFFD192647885C9E80ED45E5A7CF77E1DF54932EC1D3343B60639ADE2EE11266B7FB20EF1BBEDBC518CC6F168851D40D113C6CDDA104566E448257325DB0387D5F34AF13C85D5E617A18412E4269E456F5F2E2D5FE0BAA34CBBFCD5F6F6AFD43B750E7B8F5DAAC40564725E669CFCBB1390111BB3FDF62558EAC9D27AADFF5E2C915B5626E5137E382F8B238EDDE049AD03D20F2CA4F0FFC66D32977B6B0DA96660CEA09EBDE4CA81D4B1E9A8073C312B90E29EBF0FF40832021081FE774BDAFA5B060B8B211C7BF8D6148819475AF5FC91721F8E056973DADAFB00A293554CC9C867E1C77591F8205BCD86B7EF419E746824F837B8DB0986718B4584195C2F850BF0916B646C638D87327B27FBEA1F35B45BE190F86294CE49C65C17389DD9C5DC39ED149536E759CB4CFAC12DF5DEE310A84C93874EA31E0A9FF8FB61FBF109C4647B38CE347C6FB94DE05618D76ED4F9C58C88783A5D9191DC6B174149BB05F045DCD76D11EFAA6FE525EF98CE5C5BA00A93179995AF120ACC1891A95F187B718E7B61E97A5163643EE64949525BC0E4C3661F44A8105477898AD9E312FE3AFA8197912802899AC2CE632FAD83B4758848E92FAA081DF0BA3B95189B6EA2C02CBEE9D3FBC42DA92D54B26B854D4C4FD79F8D1CB553DC9920CF7F36A86A48B703FF0D1C2EC51F8BEE9E063F264DF70203CF8DFFF5BD2E30BC3BE5711C2BBDEA1CD4314FE7018BC38E74CAB2B9E9E4D0C99F4B3DE4D65A71BB89E68D95BD865E37AF94CE52A3E68641BE1B145000B2B026D8C670483DD0DBAB4EAD3795FEDFF78601BA4474D6B5ED0956FACB3014651EFA16862812E3480F05E6FEF4B5A7BCDF1EC243E7117DA70651C0306831C718B5020A160021AF5F8BD7C4F85F6ADF12CC7F429076EB94B3FD1A2025A6B557781E53FE6DD12148638EBDA86FE3760EA4F76A90F0C2A23F2BF4A14CE2C84CDAC5640C5097017B33E080E047956CEE10F1AA15D99A92A0168DC5427F5B066A456F81E6631DEF57E9555AF7878AAB50EE0BAF0AADF6F05F4267DB20CC7DD4ADBAAAAED3BA374F591BBF962515AD935CCB43B27EBF766DBCCB36206F1CBD395F81B53004A83E6E9F257209EAE0B615D3B66217411A86E0EB8FE55A5EC8003BE95B3AD8139C5B63304DAFCC512CE186B5AE192253F48F5728E3C0E722E62A68E0D6699BE46A50CC6AA063DC9CE08590D72816C5811A4FC88477B227AFB594A3E9D4E4AB9D1AF9949A2FF6DE9ABD7A7E488B1B1D02C0CE04FFB14FCF2BDA3D531898BE9B9017DE2AE3A10C034A9EA932418B9ADD2B59207EE7DB666DE7428BDC843EC46BDF4DD4892B07792A4B48ED032D77666BEA648784458023DF11226FFCD022C753109AB48F83B3B30ED3422F59AD14AC53581AD22F87FDF1A9BF7720CB60D16FAA37C2C382E77AB9460784504CF9CECF45B658092BE8DC1A3B6B8E5B1B95B757A725F7D46A5CF443D9A9563334E581E8E9715FC9593A65889CFC966E1252664DEE0DC14D2A4EF766329CCFDAD3E4FA8CA892CFDB34A8E914E2720D838713A188C0C1948150E97FC8EA392C4F546F26ADF1DADB3FB33D8926145F073C9EA3943BCD5748611F5937D2D766EDBBEB00E7CCA466B99C8DD181490CBDB1AB561417DF41AB2A00A6DE7579E9D6373191014DAE8EE726737C138221CC3592245165A069F44A1F5F1E88993EB44E9D0D979C543A27E7FE3930FF592872558CE5248D2D1EF7E445332CCBF88878AE4D23E745B8A4E6B5495A0B5D359AE86A1A670EA5C0A279E33FFDD03960AB3E341FCFBDB7E7CFD24C6C8EE504593FDE284026ADFD087E01FCFA06CBA30D1726EC528AC041B658815A8EA5C852F07BE4978B797839DEDD65736CEBC0E52C44846B7139D20C9600F8E23FB2D6DAE8453F6942A577E418E41BCAC912EBE7F122F97FE6F5427B74C8FE7A046BCD5621D2E7898ABF31691DBBCC8F9F43C3404CB4C2DC0E968B2077A622058C822CF73C753A4E9F7F86B84F3F8F6CEAE83B1DBDE51792A5DFC9F23532709E2FEF5A1C3BD01DD8F5B4FC2B24CE0F828B4CE66A4325770144EF55C5FBB8EE2D682AEEFC9E85DF34CF7305FCB4F105D2196AA7226D700D42EBAF1831FBB314968E91CCF19661C4BFE3E2D8F8F15B127AAB9AAA388C766ACFE72200E2D5557478364C2D774BE546042D90AE378DA7397A438F72EA98B8F769046C9010DCD4CF53ACBFEF4B65F084B19A97C8931AA5A99E3E9BF4EEE3A198CDF69E884948AF3BA8DA2DF03AAC038B4246B4948EEB40E45CEE3F3540C87C8D06C2FC0933B194FF7C49466E0E6979DFBFCC195C914292CD63EAE96D069DD7438B9D419836EB99D8DD3D7D45761A017AFDFB4516C0DBEE76FDA0C0BD8509B81C54532EDA94921879E4AB15455F7A5DF9C4D3F5D4F87D3CF68EFA8A475E119BE132CF10372A9595142DEA4680837D11A7DC0F48D68D56A5B1563A314968BFC13789D0435FD758476BCA18A83AF885B18F4CA17FAA0ED95E7E39FDD8155F158608A1C9AFDD66015255F5F8DB2C0C14D9D96F81797F395474E965ED8C0F8A638B025758982E73B9C6E795B68B41FBD9770336F43436C141423CE78BDADC66931FBD1F938B1C095022FDAE6C6A56195A1FBC8C51C4449ABBCA2537AB2278C37D6FF7FD024D54105B8B19042A818A0760853F947383AA2D0D1042398C42ADE6C277F8B9270417A81FF7DB5506ACFD0059271B334B224DB60A2D690EFD6C8E3EB9FF4F9EB7FA80ED98CE2E018726D159990D2D47A164416CE3D4C790DBA26F6C42045B06701DE9A650FB72E9D9DC5F6903B236E04FC47F628C3BA7A71ACE58B268D639AB6244A225C6BBA0310D6F64B7B48B3412A29CC5C98925580824D32DB796A0E1126DD9CDD4E3140852A06A8570FAEEE5E1FD1B97A7007BEC24E4E393A0AE0621F6FEC60E3181416F5F729D2309F7A205D5FD7DC281FF2C792D0F78C1D8CDB947F793A28EE35B7F03B6879F57B11FA8AD5E8F6FCDA05CE68BD33251069B8F6C396BE4E3DF64FBCA34407A8176B078CFF3EC8221202EAB836D86117B8FC9F0845AD7B5A7204D01720EB13DA52C01B3DA723D3DA3D91A36C9F6CB72C21487CE4C61525A98714E9F3E2C1EF4D0125A95983E2B7F3C2F761F5D09B10A2F789C047423D9DD835A15636A8FDE7309927971F9E796EA46755FA6A14C161B3567CA2A183A89211BF133452F635AAF5D2D3290820572A94E4C0626B0E24CCF76243B937111FEBAA2A9F662E93AF966E9FDDDE5FC4498B1350B5C6509F1A228446AAA713EECAF56E98F7295FCBC92616456C479225925E82F55D090C4F992AF18AC11F9864D1E6D95059943F1718B850BC05C106F4080B970AD9F34CDB30D1C7DC281360CC853F5DB011A5A068FFC2D2E6CACA9F79AB4D58462EDDFB27127900CD9377A7E87FA7FD553562939189B62FA5CDD084EEB01D3522E59144E6400B36FE218F979D1CAC88D886184E7EF526451DBF4749DD49991493A4855F380B362359D0C424D5BCB828B30B47EBBF24C1745E745FF4DCF4CCAC787031608ECAD24220E99900F76D86C4963380F2899D5CD639295E867E9E75C7FC01EBF257E516F400470270AE9272CE1AE1106C0A33F26723CBDB9FEAB50455D844FFD48605CB79D4A97A5A5F601416C0B22C712431704B9364E3A10486C0243853E1D5DC273FF7A01B47F87AF9F31C5614949E9604B7B5240643CC9E046F2C80156BDC4DA0D2A89A229B42EA29B904C93791AADDA156C4223C9B4867E37C6AB80955026C3359C692A80939E3D6E5481E53C9BB310856923F44A18609DC27E313F7F86FE3DB2602D0BB8891CC976A3459FE79AA4A71F506020DF5D3ADA8794890CD8AA360926F0E2357DCDEF48F1BE072A82F7876779558CE023B3E3AD7E6F71EE9058098CDEB995E251B775D18646E6F84A3B2C5E0E46353AF7D9E997B571945783A9C37C37A5D35238566AC8C07875B91AE2C9692E705C828D0467EE8DCFECC174539DDC998A82264E2758C774F668598A0FC0228AE19C33ED6092702576ABA14959C8DF3FDF2A0990F9CCE6F32A47F428213AF44A864FE2CA34E92201619DF6BABAA63BED5B92C8D1A0AD8863C0F669F4906CFBB3998DD5121AFB763DE2F79256B923C2AF81EDF3CB93B6622C46631E30B455052962231917057E4B63BE9A1D039325357DC1F299FA8E510F3417B1A5A6EE65BB9CB9944D56E8C2F5570642BE6FCA22AA743572727CEB225DADF2265FB14D23C273045FA9288443965EC6A89902F110ED8758C5A4B50DCEC14EF80541C267B265B222840BD9548340FA76DCEDD66D921D198F4AC23F216173232285B464EF897472F79B9C3A711CC3F1790B7FEECDDED154059D9ACAD8FF97639FEA2F4C5C037F48EE2774BE8112C95E355BB758D183C28289C34EFFD4A1F24F1CEE3669BC0447B4EDB23D8EA1DEEA853C60D490A3090B472F693BC82B5655CEE2C55809C50FE2D765A9005FC502D491B1F8CBB0AD41683919F00BE93D71F47F64DC78FE308556C41357D558CB3716CFD8D35B2E3CF3B6D92191BB4A39D7960F286531D886740A5F29A1075CFF4799EFA8CD16A4EB34C4C86580BF7ECAF72B84E53A10E65DB3264D18E346720B66A6A4E5CE38897E1FCACBBD20845B1C9ADE2EC0315A4AEAEF36F9D80E091E3F81D37CF18041A345FEBBF678B3809E4A71A449713772FE15A4FDED98A92EC920CC91997C256999E02690C9DBBD3F77013E30E8090B679F645E1394B79FAB664927FDC61C29C4F9D5D9F7C2333CC32ABF61120C2BFE8AFA5E1DE63B0DB7E9B80220FFA2C253400536E0FBEF0CCF463161B70593C061CE106B17C4A0F6664CD87E899200662EEEB68763BD9602A2DF85594F49CC5610D9E50B5678331D4B4C942B8C401DF7C202CFAE5DBCED73409B8921BDBD5493FDC5772BDC8DFDC9F685B7E5E733E13F5D271EE22CE6BBAD9EF8C6220D96F21D6E4374AAF4C9C6242BDC2C485FBFC9DC094CF762C695E6BD6421C80D89A9C8F5F21A986A48DC8EB89C8FE9DB218EC1639E3557BFC195C6CF9AF32AF66210EF8AF224D0E8FB2CCE501A65FA716D2955DDABABEFA38B1C613C6F211A088AE42BA2A69C4DCB007E4FC324D5414518C53B10EA4D33A4B5C264F0601A7D09928C97FB95122AB7134E9E42598AF4E9F135439B29CC09159A0DF635B09BD81C248DF1833029E554158972B042A391BF648B7F792A147505F5CB868A42E541A71C5388EB8B22D2D1172B9DC255364815A17CE8B521DEAF3B0CF16A9C4C220AC090324D5001A1DCABF5ECF424A1B5D9ABD04CB29DE1498959399BEC6E95F80E9B0E6503D43C9D62FA5FA5EF0809521E6EDED3FE11B8065E8BC4B9C92D68C72F6800B9E331AD7693E0F92AD2F4FA482930C84F110C1F4E83069C38FAF9C568BB9FA8E0F6693A91343B5A6AF46C22BBD87A7DD1A09690E6C3CC9FBC335B265C435830D23E0AC1968598163EB73CB7A8DDA3E314A2D5EDF65A23673E893168424645DB6A9909F6929BB1162CCDE446444C557052EA392EF0569F84C0CF53C385BC20320040A86FD32A54BBEAFA1C67B22E2F8E42264D611AEC68A383AE4EC306830BE70ECADD96A9FFCC72D8F6C58F806B1763065DF14B230B36EDF5F4EC3BDE57547701AD25254756852CF6956E70C9002F464B0B86C8CA9F9D175091E9A7878B735E780F3621E801B3C8D4BEA445DF22AB2D4053E5833838DCD2E3A579D76CA7FE258FCBADCAAB946A4E694627AB8C46C4AC775B3FEAFF9C7F555184668D1BFFF59C0BC818FB053B87D22B1081D6765EB39F0BC79A04ECD87A948C0A733002BBFA741380B6C5EC6CF639FCD6F3F038492D34E30C62AB7203A77AF846988DC27DE720BBCEC5FA7500F2D682AF9B280757A3CF4EAC030F23E98977E64C268B4168A32D0CFDBE6CBAA03CBE18E0BA6EB063B221CC7BFA35747579DB00F54027844028981D3DBC31A0C75B0AF5C70DA961FD8D7BD6A2196C4E983D833F47AA1D706145D490EED5F195489084FC6F336F7F849735C50F91951E5EC761C9423381CB8F85CC137D5AEC2C7641945A112030BBA4E326DC3F92312D3C5458FF7EFE760715E887645AADECD2476025BB0B0A91FF321E44C59F2120E91517305B491462DD4B19E695A49847F13D71B01A62AB7D77ED2F3D57E18A707DEAB7DB8F7D40F7FEB74901D13E3F4446946C24D6E3256692C9D9C5CC728F69CB023187353E9A888243E37AFADC1D893808715D6338D8EE0025B9961AA2F1F34577FC3F6D0D41BFFF6C045901CD29D1AD0002F6744E662E2F1A9287D49C52F1C8FB788495A71962CD55F5C20C1C65249D640FC96F2B6621115249479E68014B5823D721F54C18B2CA29C8D09316603430B87FD86849D59D361944A67B01B113CA6D192915E8BDE0AF94CA4978B2CEDA41132F2ADDB36B41E1CF32041DF56ABBDFD83354AC63E08315BC103ACDC27196AB86675D4AF549A4BD24F615171703968596CA08334BE94343111BE2A3ABB9A2F201D1FC731CE8674CA90D5CD55B39AFEF13578A198EC47663F46C59943027895B70126942CB44B4D61BAC38CBD3CC70C69466D6BBECFB38D93A03348F16757EE8825AD3AD1E2BCA21705495294C62A3D63A1B253D2329739DE884A751E3F7BE6AAC2DBD8DDD41F929A3856E611CB4F51B8193EEF9656EF84E6F038FA6414F1A1B50961D943B454761BF1E6AC1F297A7531B6E9F168A93E27A85D9D74D2A2CFF89B760159E93C3FA33B146E74A2945EBDBFD3E8C67A9DA1408D13842F133C95AD549C789B6105D17D6DFF7033D28BD6734F5BBB91A483E244EDA83DD78410AAEDDD72F616E775708AFE042AEB918CFCA615C489A68C399CD5B7066D2895BF83B36D4DAE0E6AD5349D449670B6B666211A99AA2C4578D66BA26E29025D0297CCA835F0930FA6B94C82317FA58269CF868A27ACE44D8E06C2C7C83F4B37ED3F104754B5C1220E6A882CC94D4CC0EE0F7903E534F5A9013D10B6B9D2DC04701F915DCE3827ECF3C0F1337BCC382663F3D0FF060C6D7B377135A55CD64854A330461ABE169046BA689824E911E0AC6CC2E5A9EF4D2F12C77C8FF0A7ED63A1046857FA43C16B46CDD4A9197186516E44CCDEC4B0A507D2649CA08D9C5EA9A65818A8571B975D7E45BB2F0EACE228D87A3A92066C5C52DBCA0B7F0737D997CAD5DB4DE59EB9A2185C9B35276B1CC142A62DACFF3BC0411F567B9796E5E20A9E10575478CBB2D34F0EAE42E720B8E9D36568BD774C0E2FCE7F2A75C7FE1F2A7B66DE8248D6426DFE9A57D25A16549BB53FB0FD2E8B3C0D1D11C597545446237A322CB0D93C0141981260CED6B49457DFE70CADC9B9E1B1A3F503AEBC55704B7A72676515C29B5AC9F82B16C6F9EF639C293B4F827A2DC5C8503A500DD8709DC59AE2884936EA4DE7DD70169DCCACC9766B09E294103D77F27CC179B00A34599BBB06DA3BCD6BDE1C40E66B80824E26D0337C64819AEFC96263E6540F95A622D5FBC19497CC2B325842452622968B71C42254814D88D14F26B20CC59E576757D56F19397501D4BABD8474137168C650A38577C49EC4C7127A189A2C2BF9B5593A0F5E6460E4FCAAB49F2152B750DCC433CBC35445232882E34500FC55A945CF551B82C0C2299EC10F586072B7912DF262AF317CE729330437C1A4AFF6CFB434688D51F80BC027463F9BA563453D17D2D1B822036EFC7DB2CB90B4D75273A93022ABD4E4B98AA74AFC6C47C7FD3304F44C468F47DB5C46CCDC3E4907262D373583FA706F959A6823721B95F26AAC9AD213841193B4C4E0A3E8F6F91EBC08D24465D44635DBBE33488BAD4887DBBAA9CBF44994CEE5FF4E347342CEAC981E2432815C36E613EEA363DB3A4F3804CCE428D8892E7087A8294F3E200BEC0213C97FC584F3CF99A83311CD87EA59812AAC9865CC0C75D437B5ED086ECC60BD9B77BAC23F64525F368B12D3FF14D27C2BD4A1E8C31FDEBAE2ECA7C2755A09363284DE60BB53779767B6A345EAEF9E19D9C78B2714FFD962E3C7B092B9B62FCCF358C011E1102650196B434D89217BD048ED654167A46DBD6233557399E40A8964E8C066F05262CED4FC3F902FFE6E52AC6FDE4F8FD44078EF7C154F47F5203032E807753B48B853719F2FDC471B7DD43402D91029DC859BABB9349D09A9F7332F4F5B9241D73E1DFF9662E943F859B20F50A99F44540B6D619240136BD0E979EE054996D48D95394456AB0601D3E70FB4DF15F484B1DD9EB0D3EE4A89033D1325B3A1FE62DBD1C52B7740D27A1CD52C019B592ABF575B0D324AB40EF8CE91F9FB58A1E3201CD57EC156F62C96D77A33BBFD623C56B43028E8BD107C6417A90B4B91623A669BC5621B649E9AC04B78502756A9E66E53DFD753F2FB11509F068378B1086258AB50CC3CA277F3C61D32DF85219348B664439BD6F3602A77EABD32ACA69D36F72EEA07C2F4FE64AB2CC723E108FF048996854E79CB607C773589B0AEB277886BCB92AF860ED214567A150B92EBDE2903563AA81DB07884C22722E213350F713B04977C0F53626B31C8D5E1A98349E7F75981A45E20B1626E76DE1E1B7383D6C180E3D1DB9C5575FC1CEC4BEBE0AC665CB0615D8D3F6D70AAC7331DBFDC1F3D12EB4D5BDA494E6414FFC5E2ECF7BF8289834EEA392982C380461585E6DCDA6327601EA92817AC138141E671592AB06DC0DDF45D168C4F87F9E0AB3545ADF0984BF9F2DCC28EDD3ECB8B05C0CE3E20720027898B749A7B192CFA8AE63F50EA19FB48E6742B13F93EEE6AB29CB2243760CD997EA0511829CBFFC930EA6ABBE971A8FF3CF94864C477FF9D14A6ED81CAD998AB0BCD95CED88C9AAF4C13008E11916B5836C48A4CE85825FAAE257F3E95B4C6217253E99E0A44CDAAC804BC3D4B43CA6166A08394E84865BB89EDF2F7DE1F9548BB65A4AFC6EA58B691D6B0CCDF4AA3EEBC68BB6FF1824ED74A942BCBB0758E8BF49804A5359D74CEE5DC5FC40E2325846C02C4BE136EA951D40CC78691867CFCE2495E261EB6F667E440BA8D4FEC506E68C05F0604DF3D751AEB398501B31ABC353D8C90DA4EFF5432A2B997373827D98D822014825B9D9AABAEF5ED4CD2C48D3C07E2046B7EF2243077083FB73641E3DEA4E65F14A3931A5AF7654AA7684A279D13C9B79642525EA99E05EC65A8E2CA64BC5966F2C5C7DDED2A7C8A948B87494A9C6D034E50DE30473D2F558E8A51F5FB2DFB788F05C34082180F4F85FC1366F3F60BE4AE4E5605B3819B5655E5E47414735B4EA1E7DAF1F14EC3736716B591BA7444C1412823E63794CFA009F42ACC012D4F789AA4F7EE07A5255C4D17A991D61E35D57E7C3B72C4BAF4E8BB8F669F9B70F0B72052235BA66DD65589EC37ACE7C545DCDA2D18679B37F7C4DD53041B9805D800634E700315A8E971D4E0A1CAE51AB899ED55526EDAF7DB5FC1A9AF234B47A7A880288DA3DEB8C3C6DE9C1268672313EE6A99A50D85A24C6BD850A7E23FE194A734E9AE3186445B6C93CC776D7E13E47F91E652D28FBED0928DF6352D83E01B7681937A297B7D121AA9793F1D108D490C9529E70EEFD23F0ED7686B33D024FC4F27DCE7FB7608DEBF6F2DA0D06E3AF4A8F0B57670CFB7BBC6CFB78377067ACA4EA210F33469292322503B0BAD6385C9624FACB8681445D3C93B0A483B902C44D41AAED1392DA396B2D8570F60AB90E8876F1772974D4FD359A731C456CC9484D232D74F45D4DCADC44F560773A7396D2C4FE7397D2EF7C5ED6745263FAE1FD121F99960011F17CB224D477DF864C40769783A1DA42BB33ECDDC60E1DA5FF1101AC1031A9A2A7B396F05F9DE7C234D0E656514B8826E8BA8DDB08A3F3B839032E28F044D3EDB7A12C606896A3963593E346D4D367E954DE0CCC2D2C8087AF7BF17BDD301B1FF81154DD7E3EBD3AA5332E40E4E3801972D9A262E2BFF68819C63E4FCAA254D88ABF4D4A3F766315EEE1B2524DD89CAFB2788EA145BADFFFEC094054D3B0D50D80D35401CFFCFF93FFE0AE72B9177C21EF28FC590D81E053240DE801FBA2D0A923B5964EF10BCB8A309D36D0B16010B9CBC15FF8F86174DA1A80A0AC75DBE27D063F0F651934D75B818457B3118C9325A49A99726D0247CE54A836C39611F8335750101FF5948B0CDBA2F3BEB177F082C25C59E930EEC1F627E0283C617B82FBA5BEFE622752BE6D755943457CC21D06C65D90A3C8F3CDD783DAC8EE42EBC4C921667436A28C8DA75E843398EC1C9E0FD57B887F9D735EF3E94CD3912C1A710F041D739C5A3F9BD2E2D276FC62457BAA098FBF0DEECE425C919AF257CC5B04920CB309064E75A08E22D7A59F6D2863A2701ABE712568FD4575FAA4C0E74B2FC93CE444ABADF3CCA7730E7E8E9FE4E3F2F9517743FA93FF3C469AE412084A3DE546AAE0A3F298179BFDC00482252D52802870A1BFBC89087A6FABDBC422FAD557C129555F4CA54BF04DC20050379D01DE7B38D0AA276D9DDA6EB0A238A3EA7C30D47DCA6A9A7576DA62FAFA4EE400913DBC8E85F27558B58B48334450BFD652D28550E9D8D1646318AF63D72B61DD7B37502CFD5FC64D7505A763E2B414F84A1EA59902E5027C054A41194A05498222642DE7BB189AB9ABE0E24446E686AFC226E30586C7ABF87F7E97A4DE80F84DC0CE739C481C41834474382070DC588B6A73122AF2140C14BC374E4E74CE3D5CE1737575E2AAB70B7B03CA5A0610D42C77CBAF27C2E5D5891ABB2CB4A9086B25745A9C67247765C2C6DD2465533768326F7A231FE996EEABB92B4D1258CC72D6C50311B3E8A943B512BC7CCBE571BDB2D4CAF4F97A31D44D9BFA2EF5EE4212CA7BB28EAEE40DF9799CDD2459F3D33D00792AEBEA55562E44732F3C71AD4BFC4D0BCD1CC8CED44B97F18B0D424373DA08A108DF6E4A1FFB8E8D58E35F4D25335FBD964C9E833AA82BAB75D94610D4C97BA29137B191EDA5CB50332CE2A885B60C2D09E1462E96448873B09A7473E2D91076044106957FC454B4A00A1AA52CF4634D32FA09B0C77820EFE5D6768D0B19F749A3AC34B39226F2600ED3EDD74B60294F7E6F81C123D6F66835E0D35C3A7715F7AB5B8F5A7CCC75353B6F8783CC1B1EB0F42F40201A1C81AEE484B9FC1096298A37CBBCA7764818C22F47A4C552BF0B9BAA63C11A4AF978BB91254345B00CF278E024BACE2D9D426AD1F00A8C59677F98938E3846EC5CEC0FECF856A7C3ABFB421F302A91B191490520E0F3771706EAE30EE419DF4AC9FE5554D96068EBEDCF8414F8A6876103B74071BCEEF5A1C1B4B9BD809259315C59A51EFD59E2BB204F0440F63C4FFB01B6578FC70434F8645E3141AEF6A76C96D4DE64A25678BCC85AB23A458168770075A8156ED3BEF0962D0AD5EDB31858076D77E8CF082265AE0026F323536D8246D29EC9B2BF5193A395D73D4A5CB1E953B247BA4C7EE262230F115A1F824526C03F100416B24F811C7F9FE8D2D1FEDBDB63AA4E5FE6903D1147DB6D9D40365F36811BE82766606744F004545858466D7FE91BBEB1A786F155C01FB5B35A5EFA8C4B9C34E61571927EF8E14CABC77D0CCDFBB1AAAAB08B20E274E2759C939614D437BDB663B6830418D5AD3205667151324DFCC07A256A9048A316217BA425796A2247558B4A3F80467FFBDFD116567AC07D4347E1962A61744942070754658C1E4AFCB61389E2E83C4D799C3F7C775C68433AEDC0C5E0595B707B6979C82ADA0CA6A5D6554544E1551CE392EDAE38099D0309210CEF341C2D28E865C6108178F4E73F5A90E55B31A2A56AAC60AF519CBF0DED830CCDE9A16A4C83669EBFEF9D6F7EC508DB6AC5E0DCC37D40C4CE21701EB86C9E32F616DD505739EF1499FD7F84241F7C42024B6030BD534D00071EA9A92E49DF20B7A1E3F2D746B6532DA8D3C4B9D4C878310F754305F54E65FFE92753EC8B0169BAC36BEB45D197D5416C39774B5F521BED0A36A03CE01A45842CD2B01D710C1A57BE6507C88E688EDDEED5F6CA6E300401E7C3503DA3D2E8B38088B50A63870C2E570DD80894DD8C0345F119FE28D15E86AA1F66855ABC96523D854F26AE8001F93CF53C2DF753251DB872DE4F90FAD8F67158F617CB79FC10EC1D7277C9B4096A9286977DEE34C0AE120397AE1DF7AAC6A25F093C5F15F03900A4EDB3C906E23FB941A5688A3C7F8E12219685A94BD6F8EBC0E07353D44F94AD37CCB0857A4E9EA337FB2F6274750E9D5EE6CDCFA35630F5B244747F84D487D976857486352167283C5A56127CEA6257D42459B9149F8FDEBF950242AE7E848C580B59C979562CBAFEC468BAFB7DC08E81A82C6C77E6AD56BDE3913088EFC491C015D29DE12977000053ABB4512C695807E6004FF8C44543905BA4F4CEA5FBAC21CF647E488E23F3083B69B1C983DB4F14D1A8BCE5E8216801B8F7EE1BA82BEF79A9E6B74C5CAE04C07FC81973634B9406EEA0E625CF0AEAFA4BABF61D986B3AAFEA2ABD253A6326581A87318B57CFEECBC6117955FD7E606E7FC6DE9C2664866D91A81DC32EF3023693DD27847D2D5541FBA9DAE10C5D23D1AD7AB865E0A5C07B0901EE75173D2E82653A430A62B2D72EE7244C49AFD94C450D0D647F512C48196D6C435449A839A7C2F9DCE02A260414FAC688998BA0116BCDFBD4CE75683E8E47DD49A7FC6335AA1BFF34D6B2433908C49DD58229006D08978BDD1599D366664620096816A8C7F35F5F13CA22A44087DC1EDCBF4DF11839893CDDF45598D56D62DED55D893F18990F2DDF2DF8A425B1B0C5538A624F1383D91A6632F43F60DAAC62FCFED6858D20EC63BB9521B6D2A2C9E06B53888C06512E4AA1C06638A525C0C857A3F8A0F5CAF3702C46B4566D48971A59304EC864F5F14B71EC305A9B98626B76D54ED9D4029225732C379170438B6D85369362422FF8E83E8EE88699C465C3C431CC85C9170713DDF39D8E35269A7D2867B9930937580C0617DA1459FB0AC6DA2F4C3EA98FEDCD5449CE45B72D303486423FEF64FC984FF89D5285EA23A7449EB3D3F9DB077AF62A1B006798D495CA306D0ADA4AE2BBA33DC626D3E4E203223B7CFBF8C484A34B0E3D5EBFA666CFB7028A394B1A0C94707DC0770A864534E974FCDA31C2D0688E3E32AAE09484CEF9022F881D99FD7C06C91FA5501FEA637295A7326C97D1175C5C199400757E9D433BDA3E0146ACC21B3B6EDD3F4CEDC60146EF5837575BBA0C4E6240AEAA5DDC91ADA04CAA97D679B429D74B4BF5BA7B582177E68C8E972763CFB698CCC51EE592A5C8F192DB4A32399021113E3ECD7D57F6168DEE6C9119ACF4756694953923CA5C24A78B5B0CA1D5D36D1745267AE8EE1C0AA61A1461B7E4AC8608F2D0047230D355AA50FA0C0FBE364553F966FC732C1912B844DC6AB9AA983DF1DF6D7298131120A6DA226FD560D28325543B63A4278559016716CAD26D9B6A2E22F50D015EAC948EA27946C46B81C58B408A543E2D336A64BF94EE1609BB9D37D55F2BFAA31F30E03C1E9E739D2DC3AFEC1D5797A6A539FC86B03A5D4925A91D88961AA980F3F3DBF6CDE447C818B3C84CF6AEADB14956018AA463CDD260F7DBB268573E5EED8D9B0EB0075B5889A7447A31664A7394ED5000835939E5F9B2BB6CE447279B78FB800545984D8D264CD7E7C167A2CC79DA84E0B84C139D18C2B2AA518E6553D3698110DC4C946E9E457CD4EBFAB9313F7080D893DEAD32B0D1130F0181BB31BB5D53E14363409C417EEAA1C8D3247109E48F81F08040CD573D77CFC9F7EB0397E3F48316BE862B35673C7E12A9A3F70D23EBED6950C85B15E53597FCAA99B745A0297E49B6BE7814924DC706C87843FDF2FCAB9231D9CCB0B9916CF7F7684DE60B200168AA7F2067404CBF4BFCA0757951D983B81AF8A2EE3389C809B55B7E77E79021FF0732D25ACF44068D16B30FC63F71664E9169C83D54EAF175991C8908F604D9E7A815686A4A22CB962A0A0BE0DD2B61BCC47CFC6C36E40DC0A89314B65EA4F8916CFAC052C00D076FC796DDF9FC8A668F03AA7E722F55E9C67D28D7529D797CC0E2F44E5E928350F15D26BE79C2AB0606305EF3B588B8662A6C77F94689AE9DFCFC2BF3A4005B925D55F3CBA89022DF045BE35BF697EFDC94028BFD05FEFD255AF550C04F1BCDCDD7AFCF49482867E2E1BB29C2C13F1DD2FC0F3AD5D49C0432D777FFA8515EEB86C42D0E4691634D097757CF5B353A5046DF458F2D0EF7160079C1D695E6B26BB41DA1CFA94AE6CF54711F98D4F954F58A151AF721714C5F0B99BE03607E4797D89B21405CF013EB7A49C9EC6F47C2944F3F0F38D4ACE2BE99E2A7CD2D278CBE0020CF9A35028624476FC422E1ED39EADA1D20B552BF190E84CD67BCBD426486987E987F006CCAB3302BC96B26DDB0A999562CDA06F10CF1B1EC97D949C5E7FE71DC079764C91455440767A2DD3D7547DCB60D479E57BC514CF22B15031CB59BB6D7FD868230D9C15C82A34E410F75096D144CC2E74BA2740FDCDDA752518200C56B129CD32A768AA7CD1FE2ABF203DCA490DB445E689284E2DBDBB23F9758D5E98A825FF2AF6048EAC1249FFBBC9DB6C895A94331924073635DE517928AA624B53250D0DAC2C551ABE0CE660E782B537BDA2C1869FEDBAABAA8CF56F33C51249ACE674371D3445E606C84D2BEDA85540E96D2BF1677C4171B00DC9EBAEF1FF60D5F91D140A0E0D4FA9B4421C4DBA2D89D48481CE78AA5CA546E27E4C70688D9BEE2E6FD34959088F4D67FF002BB961AF3118E4CB9D3F2126D102358A488DB8A843656D048B4B59D229F60FE094D434CCB4E827A599741DE1B34C1818B390A6A839055D69D0B1917197060854A64AE1B97F10CCD33FBE564EE0667A7B6FB87610437F08E5B34C1FA55B960E0EFB1359F4C16559CD56F06A75AD0FEB6FFCC538B230A6C9A281CE7D0F43B36C22AE247D08D7D591F3AA8DE1F6DCBD3978E5199D5597CFC209DFD4B9B6C7338F893852A7A01CA24E661F67C72FB4090333F39F42B94386F16B5651110169A4CFA1986948DD8B0C829A61913E4AEF90F4CC957CCEC21095C907F22D72042301319D8CECF69D42D5048661CC42E571A314E2877C4BB98B5CCBCB69664DD6B71F77D19E5373185F3F0116D6C6DAF6CF23B772AD40075A3DF12E19FD34772FCECCE5930B1219921A339B7218719F16D6A7C624D163D407F10C475C95804E34EFD83CAA01E2A1AC18B5C0EC280BACE46D302042E3BA6D987C93D13F73B7CB140BC7360B950232BC38428608FA40D063175425F121FDED9D3C3C5B52CEF74905375FB6C500B3C9E1F245C83F28C3F0E9A2C9F0F86AF14D763DA4702157957FFE120BE05D13268AC9A483B2B0CCE24A0248D99F18ADDC351098FBCEB029AC7175A854915EC949E17D7736C56AB6AC036BE37A7AF57CA68D64EAF90F51FE4E6678E57A648ACFBB6C2F2F254943B7E9657DCF266EB9EEF04F41CFCDE0B4F90874003DA7273BAAEE45452EBAB442C24B52007E0B43776D8B308C490249BDAEF439AE7F67267C0F15D327B75D6918E0FC38524BB7E1F4DA0B77398BFBF6A9E8ABDBE1025150DC8BC4B01735E549EC2B9A4ED8B81FEB9C983A71F71510D50DA2658DBEB6B3BDEA602CB8A4BF4F642311CA4C43C690F5FF079906DD4413323F1E56C85A8E685BFF8814F4A79C479B28BCAD67714D383905E6E60935CCF6DDDFC1279A5546F6318FC8256BC39C5E314E372FDE245DD8CC7C477E7647EDC65DDBB45142918AF3463EA63A61383C030A8CA62E45777D60999DAA294CF9BDC2B6D50E8B3EA82543C0E8178491D9E2A869F0E6527E7DC61294C7732C81B39B98C9803E0EAD18EC555E494B36E8AF4461F29DCC36577B0369FFD23221C748546972AD721A04DFC125CA8200A503F88EE5B5DFF3DB335E4E95A11098CB0D3EBDD255313CF2EC1CDDB6790CDDF0C58BF7DE53854927B325E87018330DC3F168156DE8159949B9D09DD801ED5F8FC11807B9173705B9B98FAA1D79EB25E5676478F245DC9C554AA2F45EA29FD87B48CAF915C5EED81AA629BAB65DA7D9FB4DCE27E76A355C7CC4DB87DE76892204732A02CA0A5FD4B59275222A7C5A60CFBFD56971B571AF4DCD1636585628EDE1F9331B64879AF8DA080425ADD2EA51540A1A9152F9F30752D6B7682AF1A12FB627F4EF7455286773EAD3461F65F272D005F386E9B4644D32658AB92247B29F4A58E65DEBD9367ACF082537832D72730BCEE5C4C0CE3748A9B66A6A31D8794743263FCA66D5228C1983842E25132CC75A9A6FD9EA2A109FF316699CF4E3A1225824A040268D84D625F197CD33F5DEFD21EC854D983E477BB679A1CD34A310F9AA56F2892AB707DB827CD2226E8E6D75FBF46B3EC2C722A1E7C3F0B3DA5DFC055C7BAD5E5F704666953521231C6E4BAAB1BCCF91DFCF54FE88B1B69BBC477046312A219D1EE09ACAE7A6B039F546BBBAE621513CAB21AAF8F032FD25569BE4624D1178C898AF14FEFD3917899C5D12090C7F9DC0A799DB1B6D9BDF67859805CAB54A1D4831C8CD0E4BE09FD90D9F9D6E855E78E86A3918750E27C8D0CAD281768FFF3C30952E8D91975E4F5F639D625C91EDB72ECEF28E635F53216841299630DEB55072ECCC8EEC999D72D3AE7CCD4B0538B56852D791081B2FE6EC8A2CF65E8E5142D91641227FE89F096D0B5E52CC8D818E3FDC13BBD8CC734F7020C53BBAAC686A0867A4B7988A27C6B4FF9B61E55317928E0390A245C4624FDCF211125DA1CC1D3AD219F9FCCCB9EA4751C3262C42D11DB1E220758A7C84973E49956B0265F3ABFBD5FD72C49759228CE48E8A2B3FA794C662EFDA7EA130B67CFD27A10937F81AF4FCD9C20F886CC8EA328C535CD96CCA29CFA3D7D54EEC5B8B2E1DBC186FC9B1A2E7A93ADA06C05732650E67ED980254EF7E61AE6A89847BB4EBD48ABFFA09B9986A87ABF4DFFF2C8D5F735EB2292F742D7208C672F70979D85D524B723B7DFE6002257B0984F0C80C02CB9F113B10BFC25B05B9ED0185CE9541D8E40B829BAE192125A6E513814BF2D018039F47B589869DF0EF1DEE1BB39FDFA7B64B713ABD2D3DB1DD7FC2A83C0A525C06193252C1F8ED4A50692287C1669C1AAB51F21E209A83984C74A95774FC18C21E1F2555FBA1E0BB38569B14C3752322BD827B5011E451BC146BEB0BB96C7E2DFA23351D9BB0F638FE483CC9A72C46DF8144E058B1C4E6F4E03AECD098B9CC9AC19BA095E3B5ACDF855B3CAF4CC87478A4AE113DC0043442E7D068A236330FDEB540B8A9FE116480F000BF05DE94263017CEB9391C27E50CEF233CD4F1E66D2AF205BD878957E580899B90C150D072915A6FAA270CDCE68483AD7116747FA139A3644B481C65BDA5D8A0B6C7F1D98A677A713960A84A7932B317004C6AA647CD814DF96246036BA4666E7663F14D438F8F3D8D8CE9A022D4FDF29374554BCEA2189A4D1F2CF4836A0C6B5775E9A9CF6C55EAD48DD64D1767D7AE08C5B039FD91F405067B842001ECA2F8538BD480516FF26EB76AAC39839478B36FD569E38E5225CD62E7BA76BD5D6ED5BFF74DFF72EB65A30E31018DB93DB2D8D9C87E6912675BB952AE193C0695BFFEAEF2D28B0DEFFCB0F8474DAE117FBAC8C2B4911239649FDFC020CD4D7B71D23A8DF5D5A52B16B9DB33C8CF7C68EBFAB99311F71ACAFBC0557552B209949FD304E916205AB556C2BEFC677B8E471E3324FB891D23F441ADAF156FE4E6D2170FD05D57E900FA93B30E15E66BA90FCC9B71C447DCC7368ED2B52C4331F5223A7EFC6F055744A5DB216108D00D5F6C27C77375D70D6ACC17E972ED9141A54184E75013B4E11BD3E4B50602EE103BE6B5E4688F0940C3585525C806BD8CE50017981AEF539B7FE8F40094479F7483A09F2B6E3C4AF1B2A0CBA2570063C145B5A44301E732FFDFDBC32A49B180C02AC10677FE6AAD5FAF138894CAE52FCE73DBDD643C9A34974360BC4B63F90FF5BF35FC72F006CC6C22A9FED19AD68C1F5CBA56DCFCF0D2A3EE8E9F531704AB53354988D1F6807183DAF84B78636B48291378410EAE72477A7B03D033887DB846BDB44451ABE5EEF369F0DC7E1D35F35DF8D8549E093B321E37FC87BC8A713DCF2EA256B1C7BC76E19791A61B59F40AD76E332AC3D5C80B4B1A49C86E5AAD12578904B91C196F65C95065428C3330F5391A4D5A368B52C9EEE5E2EC8A4418834DA501A1AE2B63F3A294BF68B1AA8EFD1C98DCF287F26EDAA6D311B1824B2EBCEBD54062792CBEECDEB38E9F5630C8572ACC14828B29C377AEA4C68D71AB243D4B5415772396D142757BC68BEA053BE9B4E667545D7ADA372E511AC81D1B125D0434E0FAE1BA3B8704207BAF984F6C82700C29026003A34BF314F6AD485D0CBEEAA237089C34568F33D822A2EC1A297B8716E5D5E13FE209B07D071083407E52CC13987A81876602D74E7FF962392508B8AF6080CA1F95E59174372949D849FE706C2E4B0B9E1AECE4F876AEAC168FB4AC548E74778DFE247CFDED187EDDD9A40C42FC87809CC0AB8FEAACB3665F1A8E427AFF0D32B2F53E83E19C41CDCBEC41B5E40CBD613BA6D628A9B327425C4FC463B51CFE4D016795169B5F48FE287205099928F1E767804FB2315906BACE18A27E5241E4D267CD79BE82B24F5C7CDE76B0FA919662D4C7481614C61558CCB96EEBBEFA0025D26177360DF64E219A8AE93AC8BF63D154068038F4553E1F1EB8E5FDDBB9DFD9DFBD34C1347DD3E47CB7BC043535350D55D43057B77CC58DB9FEF658E839F5BA5B7687B198F75063C58EFEF2CAC01FE3581C0C644693206BE84EF41495BCEC3E5DD27700587FECF36CD92821E0E2A7799C94503571A62CB0657D94A483E4500C60E3DE494E34B5012C550D6CC9794134635EBDB9D6D9352318CA1B84C36D792134936A88C6F2AA5BB675791E6AAD18391E68086A27E19E1AB900601445F00EAEBA1A1727AA496D655C46F7C52FEEFB574286465C4EE858BD84A286B1C91C8D078D3B4E850260FF8C54D3BE001768D7533C65CB708EE39F44E8E387890DD9164D66EE3D7BA51243615730D4C3B8EFAADCEC8B76D54AFF2BF1EFFBEEA4143A741FFC4C6D04B9C82ED71890EA9532C4EE10A8319F651AF75F9CD029003AA75B224003137BD53866FDF2664C83D1DA497C41BEF9E03E44BD0F056D25733005B12A414E17660DFD3281ABBD5135C914807713EC3E0A0EE87098914ECC4BE7252B34A05505463FAB1085409591B8CCF2C7E58F5AE8CDE06A8CB010A9154D0A224CBE0ECA525222CEF71656A63A06B6E47FC5636695B12180702C36E3BA943E5DF308D933C8426D63AB0686C0D01DCAC6AA2148359065B8E77E1F0392799B5F6C6BDA6B78D4E5D3894050DDCE23FE35ECBF3AAFBFF373A6C8310DA419C83987F11C79EB3C2A3242D45D2094B3C153113C1131006E61DB06823C940CD32918F246BA7A0D4BC7165D19992A2E6D750E79C3CBBB84935A6E8B5BA1EF596DB1DEFAD272F20EF26A0B47B2D37C8C56A010FA31F381D60310377534970D8B320841E5F86EABBDC970D0127531C88A58A1035D4EBEDBFBB4ADB3AFACB6C1929D55AA61BE34157634BCCB7C5F7D212CDB0B8055771056D65ED593B769C8FE91D30A011BFE4CABCD5B19DC1446EB032A8728F3CA4FB8A07D49109EE6C6E37925A1C577B211DB4231B7FFF9CBCD47E3EBBAB07890E9B40855C225D2413A009ACFF8C988B2B89655F500D6E171655542226419134FE6B636337FCB0389AD623EFA6C41B7ADBD01934354C4C5289927E637DEF00BFF6587B6D520E6B7975CEC897012E415DB69D80001D3435FB6417B6609CDCE98521F032EAE00B1551D2134CB6C9FE2215FBF14171C7F944243902A7293DC2047296C603771E6F63C0EA0633BCB46A1A6EA05DF0BE4A86EBB826FEE0598C304CF6AAD88CB88599F069D099DECF3D23923EBBAF15679211D97EFD5F2E2AAE19A721FA406BC38EB32194B8C466DC7CB3C8C559DF6C3E53729439C855D7FE29BEBF8B5D3830469403B77316C2D498B4437BDDFE47508E142B7148CA84DC12AB9AED7D9EF4A5ECE7F244F4C9B9C82D89F025F60BAB119A67FD82BBCD09BA6C00E288C5564D3E265F761414DB7F0DA4D030FC32C586361E4D6E0D1D2F2207C887E60D7B569B12EE084C440F41E8C1D29F7BFDE1E678AC3448B9F5CCD4310C5FCFB7324806CB0E55FC12E95DCB1BD86E2548DB8DC9FF858E7B7B1FF1A00E5A6DFE7ACB74A0A4C96537F7889EF4CFA37115529CB677256F6F7BBF11AF8636D3188D8C9AEA894B1FCB2CF7F375CD6B27C573859D697E487EC44F5F0E5A840C777DD23AEECD31A2FBFC56C5BDA7331443B7B7DD0F32FDE1F4D8F2A0B27E23FBAB0159614A911E4FCCC2F8D500A1356233C3CDD4F08F6A4B2DC7BDA2039C6F365B3E16057B0EC54FE55B832DA676983DE355D34CAB52C94381FCD3EBA37C8D10B12E376B1BEC5FF56EFF79C590720F39D63BEA9F826350183716BD8F1ACCB9CDEA6E6FC4344F5D2012E2DB86F5C4ED0AC21FEA42EA464995F0F24DDA662E0D35D16215A97C6AAFCCE9D53FA9110FB84B371461A26C3B4B74B8E83F3C21F253B06490F34FA77B2F943FD373F2677C794CCE389D385D229503A44BE6C86FE5F3AD9E8105A73C809273462B423272BE53062A2685DC0E65FEFB6F2A2DB5F4443E22295DCAAEC1E5348FC70E4A09FE187A0C99CCB779AB6E61C035098E3E85FE9BB9CC879B79CCA0DC3D7E3D1772AA0DE400AD58ED5B01D9F7A6563F4C02BF282FD56C730B326ADBA145BE901D8F5EC2370716709A451E5C2F22B1F5B3EC6B669E193990A5B96B7CFB61EC0D780D903AE72C1D53828B895ED9441ED97D5C7F2478B8A30ED70D3C79D0C07248E11AEDF13728424B7463B0A8237649431C140122C575EBD4D005FBE172DC7D501224E1D75E8BB12A9F99AB9D3C7AA13E2C547B9D3075ACA6FD4F9F718C3FBC627CF024BA61193DEC4C9AFA3CD802B592472689A39FF72505AD0B0F93B45893E5AA7D63BAB56232426ACF7CCA2C9209290F55C0807E58AC09B373707F656EE8FEF5CFFC88BD382318AEB977E70DF3EEAD8326ADCD956A2901E701475801B203D5B1001F0F391C815872F373FAED743FD19734B7FA8BD1E02D529FDF2444DCC8D14DE0CC5B6566CF98538788314B020480E6866EAA9D27788F22B4A3BFFCA8C6E656C530C52F513C98D7D983F419BE96E4FF4F018E0A393F6D03B288F77A25E8DD57CFA112C11FF7C60655931409C7D15C0701E222BAF5A3A47D0A39515A5EC283823ABD2AE7BC93D654B8B2244F061C20804A6BEFA05127B4BC4F06B7B5A8999C4E8305AF731D080BAC2F517C13A98FE71B282AFD986B60B0759BA49D669A846D8D5FC567FE48E38E09081D5415D5D850282D6BC9D5CE1478085E6612DD36F28E3F6D084ADA4D2B360BBA38AE5CA1ED5FA8303EE57F5BB67C5006B888765FDBFEA950D903A4B2375207E1D39C254BF6C879AAEFF0C5C182663075EE3E52FF95DF7EF8F28FA90992E44C1295D98E0C45AD7A13E0FA7A515DF1569FC499E0BA3AC9E86F7DF1C330F9F6049EEE79F5E96D39547D315CD33D213D98EE161CE741F4B712A1823E584392B580717649C1B8DC9F8E3717C816DB6233CFF8B2E35ED97999A534B288AC6C0410D0B4D5C37DFF7DAAE8FA43A1CB027A14A37A3B0AB65AFF3385CA05E83FD7DE65C300E044F43A81052B7FA33CC6D339BAF4818F0D1396EE275F56447AB6167CB20078D01CEFA8DE3AC8FB69D3D2857D4BC52C708BB65A1E4AD33E1B0F4B8E0F099F58CA642ABA9DE2172A978219FFAF1CADEE4AA5C8735BFDD277C829F28832F9419942F46D67B455FBD6885726AF6D67DB5C8306D5158B92FB353B764D79931A9696CC5926548E6A3F548DB5569C617EF2E17125D2C20AC07A5D498F588E2CFB00227DB91BC5AF59BCBF2CD733D098F61715178F05E03B1D9E6A6513FC1E4B6B7D7336030E7DE7C6585620F69DE08E53402826C9178E1423484BAE2D36ED3B919A1095DDAF45F76ADE2B700D71772F250EE41A41CB92933D2FAD81321473852E13F711569A5141EA1F88B7AA46E9E2F38C9AF904C9DE1B17B8A30931014A42EF05F31CB5FB2FD33D333913312AEA1C90A69BF1AB3F71E255DFD858DAC0DCC3EF107781596D8C6E0F764F6677A746D08A6548B616AB0AA1C1B696E4A8F55554D5438DAEFCFDC3DBA17C66D59E399B8DD0382C9CEB7E74820D876F96B7E7CA5DF556C9FB3D9387FD6B91C1C2C49E19FEE5486A7C327AAA808804BADBA153549618560CE1EA14CB5CB3986BC0D2FC9201258174B9216B497466F26F8940F935C73B5E9C5A2BE8A978120CE2A56D2D292BF53FDF3E117EEECD631AD97A67ACF25AC7123848B96FB851611DD9C8F3948B89623B2D2F64BD1868098650EC702745763E7C320642AB48E854BC144AC279672B5F56FCB430BFFF5D7203CFBF7732ACB1B1E0B763F00FC09582682EDDD2080678A8308F4D10CF8E20DC8657143D79130C3E99CDFCD07F5E714E940D421D9C78E79226726649BE3C8ECCB08BAC9E4C221AE1E16CE9E7F583910B7AEAC4ED43DEF7276823099D2ACED856288A43EB239BE8E4B6EE907C9198383268673990082EFCF45335C1746450B4E9FF0BDD6EA7FE91F2AEF93DFC9D2FEE725B94D2C9B3F69BEB3815787B989E3920B0CF68EC31E0FEB3777B6E58425E915AE753E52076D359FF02D239B7CD91CF684E1C823171F7C700FDF74379BB4FB62DCF4D8F53A52A4E456F4938439CD3299A720C16AE993F4411C8849B06DDE0102E6A304D52893E7E6DB3CD445B2894506AB95E0B843645204FFF9FD3AD4C365E055CD88677A718C4838E3DA76067685C4B7B87FD88456E95633F00EF8AE13FFAE778F6515292820C924A287E00640B74361364D4BF324AB10A1EA76140D97FE8C75927B48E2F776A4A69B37FCCB9E469DB599C5B194F7D37B359EB6A0CA52090997CF714254F4236AF93FA0950EFC0EA83B8704D466061E34E2CEEA106D872649FE3E6B9D2996FAB04B21585F4FA3B843BF350C9174F3F31AE851A0F4D99B28EB483C46BCF7F5890B4A59B15826A647BD24B766711234FF0E8218471BEAEF9F9EF714EB638BFD68B38C94095E69B59D0B0BA47AF19C19A8C70F1066DAFF7631E2E6CABD8B214A622A46B4AC2250B1F214F701284E860744387DE67B3896D7A442AEE7DE49F1AB1D501771A4486846D4D8B6A7C3D285E2B090085C5984B55545DCCDDBE5506DF6622600BFFE7D1D771E1B166D57A8D8555760DD7436BE8F96328EE2C6EE1225BD5849C5DB8E17A4E8AC72E1A7D33C30D38ED109243390A96E5535EC5338D6F2AD54A156C2B144980BBB7A3A514A0562195F27578DFA3F98E2AE3305DE82CC880AC90D731F937876548625E9A13B4C7E9F0E7E8FFD3ADFD782AB0E87A195CA1A6C93F7720A793EEA9FC03EE035FA2B6B84E033F3EC07C20829BC8F2B89A08E923FA31A8F6E2561CFE154B324C06A7F7C81AABE98F6EA7A2A4E7BDA7A132D5EB1225751DB370DE588ED7BB09C66C30144ED16B0FCBAB347C9018AD95B80353E67F8EBE6EFD0652F3EAB81A9510274EE528D12D18E85D5BE37D913BFA495FA07F83CD1FFCC965C1D82C5B34FB638D9C2E84A1DE4592A91BEE3A10C6EF0538ABC90516D72B9C116E68A8EA65F8568DBB7106B897DFD2788891F43BC031FFB391BCF53C3A57086C3D2A0820857035D792AF14AC5EE369720037762617D8D3A40392549132D604BB5A0C7EE5AFDDE5BAF92960863052F0F61DB02DCC3729FD6EAF30C149CB686DBB9EB82A3D48CE461778CE665A41CA98BBB75F97D5CC79D19B6FB835106AA648ACAA2A682443F74594D108565E72378F83D070109A0F7326C79D4F2B60437BE746CA64571A5DCDCFC4BA14C56F4687A99E8CC2D24507FF5FE785B047318C1283BD82AEC86D8C7B64530348674BBF54A94D0286D051F7F78457DEC0613BDA1CC48AD17573176A19DCC190D9C7E65FE360AF99FE7B08E3239F4BE44BB0CE1BA9E91876176BFEADA9CB5647F4EE40574AD4B50536745BC2901A980A533C9662C21578772324CE991271283C322B856A258886D9A31C2A4D0D11E981268977BF3DCDA0E849138E60A902FE2F488660BA89E79B1479A711F69DAE36DA825177B82C91B9F1A738E47BAAC005382B76E13F4CAF06507CD732E4F0882BA2A39CBE5D0F44CE8980857353D18C9FF1C467ABFA32B5FA97925B7EAB356D7BCF7242E8A3D5CA3A9EC2C99D78DBE65F38BCD7D9E9C8406FA951667A18295E6C9CF18A8D1B67E600AFA48C1F6E39828E2EE2BCD1B82CCBBF2EF68C3EE3778F05180FAAA55757492F5EA6E456C69089C41DC07A5A63407F002CED899145DCE6299E0ABE0C264D7BCB0130AFF8183EF06FC1C47793150F91EA1048DBA18C13F77E66F09FA5D09FEDB29D90862D63877365BCCF7F7835003060F34CC223983CA4EDBA1DF263B9E368553416A084E592E0474C8F499BA349F1A89261385006EE8A8C5D85EDB0A0A3BA4A49715126FE4C9C1DE9C8A9FDA9416726B771963A119A066805B6B1B3143D49AFF8A5F4391C1D583A49BE962F71146093E3C3C4F4A5671762693FE680FCEFD710353B2A619BD86C2E849D5E57CAEDC9EEFD9A6FD386F9C9C242AF57928C6504D37F905C6C3EA5FDDFF43ED8250B3A2F01DB256D1D625A7DF5A76109EB3E040F15A255BE32EF76D07F02557E44C317F754487E26983039AE29D3BFC178E9E7391672130478E719218C364A4DB06CC058F724709459C6CBFB70B9BB8AC139841E241303C1B2F8C4F4CDE02D2C933A7CB563564707634ADCB04F8836E02208A62DF2C9B5BEBC8CAE819BA5E93EA3D3D9519306ADC3EDAD6532D76A397F72E531C9555CC98E3F3D96C0A611DC86A4364DC894E539B1298F327B95E643467C85785A41067EEB3E782117515AD99CFD8F53DA4A48B5E28C0D4097A1DADD3DC0C5FAD81EB71C7D9E51D083482A02B51E9ED2D0665A85C0A8A525D78FD60089DCAA10ACA3D617AB07BF2611943DD9C4F83749130FE5D90BCD1AE39E1C187EBECF8C9883FE5C529B5CC3D3840C776D090CC6E534AFCCC019FF477AFBF3ADB6F3C88135B89C35D8CDB9250BD5951CE412FE45BA7961865A872DCCBC62A52BD3237C1802D25B223C177096E9737CC25C15ECBDF51E1E22310DE3B28BB90FEE7281042781E1C34B75681DB7FFDE6B4E8E4EB07BAF847A4393656F3A999648980CA27B75B307C2171CEDBFB0B74768DEBEA0DEE6DA35F065FC6CB2761D6053EDD8194C43D70665078F45D183124C1E3CCA2C70AB38235992E7FC26A1CF33011E35C4BF027B71DCC8779A236746302FA323668AF39A6FF5EC46894BB2659B0B0083CDD5D27E96BA480A643F361541815B55175123A370560583F7977271F94136D19CA52547FA353D5C0DFEB0F20B22590AF375B3A990892C3EBA0634C2FD3E3F1BEAEFA0416B473321A1A888FC880D390F4CD16AA531C0E07AEAE5ACB86F88CD1D708DCA145C89142DFD66E940117D176DF8C7E69A499B9DE2C7B8B26A8D76C121C7BA8313EF1623AED855A78DFE41DACD81E74A104B332651931430574710ABC508A7CC9645574DB4064174565F8DC6EB5046F303A44AFAB2E17026F4E1721B49D48E5A9BED5BE8D7B2B40CBC081312AA5A95C28A2B81008FF5E1792A3E9EECE9074E65221B1DB3F9B5BC01F0C509FF5734300860F5951C2CC926BB6FF70B57B43E45B446AE6FDCAB8FD187E1208849ACB075013A3B8216F8D1A99D0A0054C40578F1B0CE747C1B3D01BA416E161E3EB5468BEB873FABD96DD26D0CE5661A11D1ED9A21299818B02642AA4C5D63C39500AF59A558365DF0F13E30A02C723CD6520C42FB1416102A479688BB4426E360F6479CEADE6AA7C867B593542D772B465CC47BAF8242524EE10F202498F7D1E2525A47AEF803DDA745637D834C83DA2F4EBB40883372716B2B2B19AA5718B1BD0926192B8388DAF503ABE6FF2E4D9CDA68CED764554F05B81D239C0E9843E4D672872491452784AE9936335AAA318B42EFBD19AC6B6869B93161842DEEB268C9657EA2D1C5C498E26629A4E638DEC294A109166C1A50E5604750006FEB1E44B971FC43205487B321E074B90FDE7B1729453EB5610484A490932142CABF933B8F63B69849509E4E8018D0D1572F6BBBE17E41201BF40008D8E5273479975ADF24A26BD781B875C6E30D0749054982E652F955CCF62FF5B2941804FB8137CAC9EA7E3A3AA33A1852D09C206FCE40AD6E04D892976188D37393DE2FD8409317D277DB85762A55CC6A900C0C836626C47C808EB274EDBAFA3D0AC0989C725AF06421B216CCE2923A9A7B660093760FACA20D9520449A237FEBC06982FE68E58A3AFC91C4B2328501352982B2AD532F48EC100A80914A73F92362743AD4134E66A9A0ECA0DC6F6FF2F9C7BF2A00A015FFB8E65320DA600C4C4802E740EB19B0C1B4184DC12272FAFEB1CB68B8CFC20CE17D541A092DCC6EEA3C019C2B26BACCA7F67C0295677DB08A5326EFB66E22225084D0A2CCE8C7C865731DE18F14418DFB12AE4C27D19549ABD49813B419E2CB787E7742DA32BF886AFD144069A7F3DE47128A263FE363F8DDB014176237D78294ED0D681A7A726ACE9E5445E1C946667C55FA323D4A0663C3B3D151A162E4CC2C66C4601DA654C3F53B2C4C586FA7728A46A753883772F47F3BBF9EF19A9ACCCEFBB45E53232ABAF10F258C41E4CD1FEA3E52D7C1059D81DB704D26A2DEB0B4788033F70AAA3785CBAE0DD486D87A39380B2C4319F35534892AB9748443CE5A2C783C79945FE7ED225E1069BE87504FE4504F0D99941646146FAD22338335D0013039FF848942CE513BA630D9AAB006D835167AFA89C2D51B22CD446DDAEDE42A59C7D1D9A84AACD6336E5373AE47CAE957B6F24F3D15D9311A393DFA0A99EFC9291B21E21CEBBFAE0722969ABDA31D0DA2BD0C3DC84AA24BBA13C13A3CF20BE620BA28832BD4A59527F134FBB4BEEB1F6DCBFA6284B66A167993F3CD5317236C4BCA396DB547B2DFA536876D7EC84E8CA6AF6DCD717588775CF9133110B9789A07D6ABD3182C7DFE12C609B34439DC2FFEECA68F505503A57AFC2C9B4AEE5168EA940613611D8CA642CEB022506ADBE3018BB7A6B53195BBECD649318D9EBC8E96F03280871106DBE61B95AE5C73EB0FAFB47EB3BCD6D81CAC7DAC430382B8E536EC65D4BBD54E93A875C066479B05079C1CB45ECB4BD7B328BFFDF4CE60F2184A9CFD9BE8D9117C972DC2D0342820762D0464BE4FAFA814976B22FCEC0E1752E9527DDF1C6E57C469E41B8EDC47177AB2EDC94A9B71C464E335BE5F741F70BE9102B9623438D04F175A358170057F11BE67119152458638495B7CAA857985AEC77ED77909A9356C1F27B672F3FD60DA1A24E619086DB7A1E7C834BAD78F937B19700C7CEC7F38AF9AF3761C34D313F07937CB11975BF30AFEE6350D2DB5C9E99E8FFEDEAA1D7EFAEBBF114BC0709A2FD5739A4B61A793A54DF253156FA089387C2F9397A9BC02460A7091C9F2B80D503B11911696ABF9EF9FEB54383C7E05553D418466BD895917CA9891B37376A3EB80EEC8C816506AB888AF00FD56E25AE2A856C9B7AA1598F65981CE483FD7D32CB9F689394B297D107AAC6740245019BC5C692FBF12547FED4711103D931D8E958B65AE4C51735879D3AE8DB4F73EE5D77FC5DB41EEC6162A0D83ACCD6DCAF1A4D752CB0B26903C2063DCC606C29D58333BD1C8D8B3FA2499C3316D36DC64DF455F03D1EE363EF3D74CE94972C3A464BEBA2615BEA481F3087C93237CC9542A8F84EA94A12E1ECAF6301ED1242F0BF1C1E976E501B8101F8B6CAC1CAD6C8E17C70942DB8D2890A339EA56DB4696596847DFB4ACD8AB26CA9EF206C794BC830BA5F47C98EF1F0F1252BF7F51C9C37D88365FAA2AF0539A254366D52E1F460A490B46CEF1E70B679C0C8191BD9BDC3DA597DAD66EA6B12D68BAFC3958F2DB8B3702685AEF62EFDE71116AF13F0045D36829D306B7D6A5117C538106E0BC97CFB07DDFB3CDF041B4B636247F8EAA74BF9B51241F0C847564EC677E5C4934570EE88E166C9CA18F67550F9E34776267EACD15CFB809DF20A9343A84FE970530F1F5B694A3F20FED045CAAC2ED108CE251B6DE6BA97CBE456B11A33B9BED3D915AB9D290D7F0B37AF2DCB6DB6F6F7A2A2DB38F50906C0988964FD1DE90AFF7CCFF335B3AA5468FCCE9197429BD09CD32BEC78FC63B21B4233E67F985FBC596B62BEE4FDD9582C82EF684B67DB538F22E83F66AD54A2268E86AE283D0233DD7F784B63B75A817DF3438234AC2B20C44308DFD4DD0EF157DCFBBE66F0B6CAFCFF5C394125E09CBFC0FAE9E4EF1C1B183E0620319DE7BD4234DE7F1AEC9976DE9106B05BD4F81431554CF074642021769CC5A3F242E7AAE5AEF7CCFCC7FB3D2AAE162E1C81D62FBB2B84E86E3FFB11757108EFE4B1F5AF447BB3C01F1CA199D71FBB597C241CF5DD80CE95CD45AED9AC39211E004CA19EDFC175843D568AB3AA37CF1A3211E1A4741DAE5053D26A227CAF5DD622B764002878050F8579EA97B3C8B706F6CDCE0DB2DBDC2C5F006DC35D79C1FECCA604EDE199071A659E3ADA5612F1AB680D16A94EFA58D00A39B2C12797F3BDD3B1C5A99FC4675841D0975ED48F237A9B09E14A0096579753A74CB38F8DA9FC6845D08764FF0FCD1482D10A8FAED9E4C4B901568196FC2D822A99EC9A9B19352E4D968043B6CA72D51233F512BBEACEE372CB229FA9DC828DD9A76BAFAD9B818567C608524D59448C658EE3AAA94467011EEC589A87D0B86397CCF979F5870247C950CB89BA34DFA35DFF8FFFB2D7A8DFA76AE022D828836F0191DB883E7F35FC1CDC6F39F3AA9770595BE6619132AB774A41F22F366F9356BB60B89CD0EBE5B676393BC0E6C1660BD173B12443E93B75A8DAC24F6DA06CB0B9EE79C06BC785279D823A54B0029B1A1C423E7DEA94312407443C318C81EF74D3C039DFE74F7DD98108A633FD535C336A15DB3FDDAE84952CD557FB6646289CDDD399157E3B46D27E605839FB841E5440B61F049D84D6745A92F22F9B0657C185BAA08B4561786207CF245497360D201CB63A7EE713F4CC2DDB095ED2C54ECA12F915E82873BEC43102D3D13BDE31B8F015E4EA536699A02E74FBBA684689C951214AD280A700C449072702C34F854C00CC34DB8F8AD24358A0C1773B0C4CBD0B32B0ECE2F8DA167033973A53BB8880D0E668AEE2D9A9FC7BECD9A511852C02502D20BC4359259C7054526B4AF27DC692392771EC68AD3154F8F6B752FD569002F00AA4B1E664783274DD17E0BB4E75827078926C23E4F51CD6190F89ED41ED9012BEB44F251FF49E2AFF95505A5274E110C5907FBCB8BFACF896146C8CBF8D363089B4FAD3A9CBD875E6F89905C86E533A9E2782B10DDBCE311420CB7521D3009578C3ABC4CDF63382508FBE6525C8F063BC4500AD441ECFDB63735DB30F2CEE6C3E145D8E2F808D2B91D10F7703977CBE97071EBC97415CAB34AB5A8952CC4398ECBEF766AA3D7533796086E465AB8B762FF042415B46B623AA44CC165007F4998C3B899A1131029EFA133E454019EDF44362870C9B444E94C8469A67C956D7C3D3E9FD8399559120652ECA8BA20EC859D0336BED6B0B8E971BBA0BD400FFFB4B1FB86E3FE5CA55D52C2DAE24CD81AF13BB413D6D65E57BC033AEAF43BDE17B5C212F5E2C362A6F7EC455AEDB7CFD3C28B63F15B9E1E212E55E9355595799FAC1E4465B5FF65C337E5A3990659A504F9B59F472D49DBB891A388D744D42F8E24980FA493578B93484F275B50B7F28FA9DB39F0EA766DF7D08E104C6E15065CF24C055C585D6A57606537BD7C49B19810816392DBF7FFAFE0AA1BAB364C441F38E3234A0163C87A9F91F6B9704F7FAE710582F2321F333368CA502287B4C297025D6AF5003598FCC38DE509B5A94231B9AE0323B1EB40E5B33C33D8047AE6FADFB594AAFED86D1E1862871FEC42CF63DEBB28BCA762E20609175726649D5E15E9B78D9D7A652B8C18C75C1CE5C8B71C2B454324F76A2393CAB59018AD9DC5CF475E1F64086E5BB30EDA2ABA4ED5342F2B14E9EE3645B1A81554E1056E509684935F31736D2999060C79111FC5BFCCD4C883A88B00B1ED866FA79559CF0F26C2FF2A785FA1DA400F984D1BCD0C4E2108E9BBE84A11C8763C7E41688FFC6A0D4E6E1FB49166B3E5119E15B0A8075F4D057E215EB73C2DC28DBDD27E156609ECF9B1680A60B051E8C8A3D1DBAA60327F9774F10B11A1047C2894463DD91D4684FFAC4FDFBD7962B899E6767F8BF0053DDF70C230BB6492AA5685C9B564E762893BF1728FDCBF565D4D060901C221220855D8BA9573EE7618D57795121DB4CC36FCC08BB0F16CED331C7107562C3191308FB3D24C2F2A9057F0E20AC5AE0B965105CEDC4B8F938880352A0206B8AADE2301900EF8D57E3982D671C75BD92EFAC36943DC5B5CE0BB57AA4E30F5FAE3CA4DD9F7640F0BCE55909FD8892AB7E685346A6131CCD485E55A2B13E358F327E67AA40B1AAA06D0C543915B6D8759CE948689613EC81F5427F66E78F954EDE3F35E90EC4621F97E1262FDADCAB0533801BAE543E3877A3139313DFF9BEB62331B2B83DEC963ADF2C7A41EF04A9AA44EF27F0104D738F240E4FF069775B59FCDCC9E5ED5736449D01A21A95A00932FC6A1C8F956C51AC0E54BD1C3BD17D6A0BA8E20D2A77953D9843384C432A6261391CA32DFB43DBE911863BB048A0050233720F394AE88B31FEB8DF594F51B1FC901CD7B5EA76D2E74B731C9702F4760D4E9B20E5E64F142FC67B9392EE4E25F9507DD7CEBA453A60A79A3AE650567F28CC7FB3925B50BD0FDD0D09463417F8C1F366702EA99D8B11DB87DE44CF01435C0881A034EED6323CB2F412B10EA64CF5C4874D021B83231A724A52FFB9ECD799484C6D0CB5AAEFB9BA922E0B105123EB107AD4A5C0722F87C5DB2AF3FE0909ED5CAA2161C37D63E41374966399E6B49FDFAD5CA4C6FEF37F5E604F170451B58659A9CCE3869F6A0D7AC6C646468A6E191FF15AD3B358CDE7FCAEFD82D029E2EFBE25CFD4673F27FA50C91A174D6587E8DF8C292CD3793E12864E8C58DAFEAC9DB16AD1A4F4824CF3F1C8946DDC6652FF9A06A6B52B8C5E8F19EEB3AC24CF44F203C348A0CB5CDA091334FF56C70A9A3F525ECFF9F7038083FF16EDBFF0D15CCCD68C4687B8D8F4FAC06D26DFE04582D3D6F492D1D70BD5F82D186DF4D388DFFFE3C324B5B889B27BCFAB3B62DB3CD36AE6688A5B7D4CB72F97A3270CA3E6B1D409DAAB4F5A5DCC22F635ED80357054EDE1584AB2C075A7308ECB9DBDD6C9CAA98BACB022F38539962498664A887A4B339243A272C3A67DDED6E53F7D9518FDE264CC9601D4C09CB664FAAC6ADD3FDA94BDEA7F576173C5E459A2A82B7CE0F94875DE458BA5B7552800D097BB6C7BBA09C8EC501A9321E653BD089A67E0DD17A5E5A5D8B310D5AB524C1510683E0A319782CD982E2FFA2A42F1A3C426422FD790827474EE0CB3B8AE951F820056291323500DB2D4A235F4CA5383EE1858D65285CE76A844424D5C66FAFC1CBA30C0FC8596C1B7F33E35320BD6B05EC63AACEF7D9F5889AD2BFE078A51BA87B3EFFC8C577F50C7C63F0E9D9AA63DF49FBCA71E3A240F8C77778660FE7A7346EB6F1408F7C5308C38EA4274D615DAF07E0CE8C792D02B99C042D9CB32F2518C724A8FA0936DF6FC280DDA2410227E96EF4254092D227B76E4E7D8A295830C6BD34C747E89A1B0E83BEA029B8F0287B52713D23CC2457B572C7602AB7FDB33D07EBE6F3136A70B9E2FC710937FF54D4E7A91617C82B2B542114D93060FCB53EEE5050C796AFE050FAECF2272C0AAB1A3D3664DE2792F6683385774052D56D0114C92AF4302E828501876A0835589B093B365DB65E0AA1DB6C4134E9D66CB16E1B5E6CF03ECA562BFDE45D2245BB262DADA06298A408BA6452F3662CE7548E858EBC79BBC7C602C48C6816235B6C0643F5BF76D60F3710C4F677A7EA3CB2397F8AE74B960FDEA0995B3E531C3229558D9F15988EA4EC950F6F6A939B76F74C4E82DD549E93625B4F5F77DC079158C53B98F44A7E2008770E695F395B28182F939BCEF87E5625A4FB2CF90E7EF16549378F23B4845008E70F8D8047B39B20B8F3E52DAB497DAE626237F1419BF55CF5C7E79636DE7568DC9C8386020718B3B867107D58E4D25DF65C590F2083A06F0A09A04E3DD44ED3413E1C819225AD68CA16F3B62A10761F61D6B7CE345980706578CE3DE4357BE7C8C266EDA674243ADDD65358A1A1FE0A1D99A8A2201BEFF68DED00FDD694F5EF45A833A1BFB4F40C5C1D061C81540FE78B3C09BE25BD088934EA947EBFCC2AB5FBB5A15173B5609D2E6279DCD2086D9E367F4C8DC45F14C511C72C29E940B6E54741E9A864F6B3D802D9E0E5C11C57D106B64D4D9FDCD6083A30E97AB7E6A5280E12FF95F10B7E8F187EF37DAA881F94ACCBD8E226181417AD93D2DEE5542E1EC3B015C0365ABE3DEE46EFBFCC4AE176A8442B844AFC03A8867846F2B56F9EED963850B4A6B9821C362BF07C38FB9EEBB9D7B5EEFE1C0F145196F82E83FCEED7CF0643DE6C115404F7A037CD43B0794593739C3609056C3771E1B07228743AAF3BF2FAC2809B1E64B72D07F93D705CAB483D750F72C2A0183AB03610E17C17594E51298E7F773308D041B5B619AC8B0E03E38968C3B079E82CB983BD86DF4AB1FDBFB6DC05B5FFE4C486D9C1351E5C428557C5D976DE18046C1E770DDED81584EF284346EFDAED440B609B1B78BCCB72C9B5F1C2C2C075DED249207B575A13EA907C0DE0F808C39D626259051D9FDF950218B5C4622F7C0673B9EA9E81041C5D5DB0636B5331F3F1388F40A98F50FA523D0033249F41317FB769D42097BD68EE2DFE9FD0D87CDB508E8FA8DDBB99D54E11D07F8C097B4DE7389B0D560EE9E5A5D722C6A82BAE5B285AE6E31962ABCDE821F82601535A778E955E211C8F9C0F00A61434DA177A6DF7FDEA8957EFA2B14F8C3A2067E3CE6D364F3D31B0DA16B447717DA5C3E8602A7DF0B5B45394605A713297B6FD281DCE032CE595B02CE74981F79C26093767ED0663498A1B670DB31681CA0950E7B2FEC7F9472802A4486942A3B98903CF078095957A13C8CC7BA989E77B0EC7D35A982E31B30DC5E94FE4E8809E3972AC5392AAEC15CFE41BF6A884E5D8F826AFBFC4CFBF88F853E512F3EAA7B61505F3E2FDF934C39428534317718397C515085969A9EAE816ABCE967C8A6375DEF19D88810F507078AE82257699AAAE7C7374EF36683AE333CCB5DCDC26D84C9A8A9FE033AE9E4FFEF340262A3CCF266E1F654C2D626E3EE8441B2484BEAAA7F3BC65DF634A28496AC94A16CCFE6D9104FE8F01CF653380759AC4FB03A6D353FAFC36677D04D8AF3BD2A9E29D2446DDC52974202067F97C68C509643319DE0B72615695128EBF371D4CBE06AA975E9E0FA8C38D9B99C83E26A47D5709D12B9C680928B6964E2ED7CF45E67251704BFBEC6C9F860A9CA918F73F64B4D06AAFA75B3DFF9A560BED6D2429E0900E9498B0335ABABA7CF083F1C2B0194951E690FD56AF2A265E2226AFF70AE89979B413A88FF20499F2E07ECA7BD2191F7904C1AE238422B48B4509C5F2120A23FF31A6DC12835C3BE4505EA2FD282F11427D149FDF87A36DB30E93B39AA313541C443D1CB55A8AE0FAAB74107041B769E98D817E244D5951366776E0F8DFC8A542E114E74353D1CE8876A75F096ACE6F41196CFCEDEE3E3B7D01C91CA94203C0433BE725F0195CBF04D6B47A569D65E722B63EE355AB2462743C4126E8630DCD7B730D5548F1392524AF4621BDFCEB6E142A90A0B5F300212C9D75FEA9967E35E5F8DB2EC6B63B4F91EADABB2BD4E2715D037B74352BC0DC8A9416C57BF563FC53DECE2A3F31D00EA9E024B19A53CC318FA53D8D24120CB86881A454410E2A346C8EF0309F787776E49756416A0BF8B0C2107A2E091BA899C07D596BBBCBFABD71C9C7F94D703B028BDD246BA8565239271B776FDC59605495F23E948D4F1D0CA1A499453800D45701674088BB1E0675D4F0C991C3E2CF3806BF50813AF6F56833FE199EC6F40126BD2C2A912CCDD9E0FB6EEE054BFE088DC6F89945E15D37459D6426632FFD52455AA908461ED4F5096F45059DBDA4F328272B8D533EEE3CEA5354E7B1416F5C1F85CDC4305A4A54E8F9B140E7631C63EA98F8B04C023080A8E2E3A86524419BCABA4340740F73B647B5C168BBE821190FE4411D91AA5A20DBC665BCD24CB63205A714F1F8FD1E3530052EA08B333A7E2757B80857C0F6C01728119B4D764D4800C889FDA2B3B9AAEB4C587DB3F93398A19C7B8409F0D7B98B40450DAA5FD5A0D108090FC27146CD68528204F8CFA80D2030926BA36D3AD1D7A239B9C7976CE466D65AE947DB91CE89A1BCBC4CA81EDD23A25CA33843D0E596BE0438BD7899B148350416B8D752C2F7A5CBDF1E57970C0DB6C8C8470455674A0D7AD880489D9F29773FA7F497C5F29ECE4B10F425A623C7BB4D12EA3BC3F75FC83C094D706281FADFA7C3C88082F6DBF66F6AAD75C5571BE21A30C1DE05F7F8145FC3E9606C60C0B1672EFAE3878D5B738F8C203F43FBBED0DDECBD7BCA5F6E657D878F6B020EC06466080FF9C56A71052CE4B4BC24198E8FFC0B10FF445FE7784D45E2E07CB7DD4B982397051929E8FCB2F327F917410D097D69A3DEC3323752D1C5AE55845F122F3250ACE19E158B15B672FEE5B27DE459F7DFAF7D46A1E20A680818D641335DFB2DAEB85B9F5CD8A74E22025715D6A79DF312FABE2824D2263884118C2651BE716C79CB455DD41C3F55E3BD27CBE9789E2D7845CD351B43C13652E3AE1C33069FB5EA2D4C3410963235463E36E682952C85204F913937132328DBDC84E479D2106337677173C9BF4AED7A62D2E450D982E33DADDB02931459FF7C96FE0CA4722BFBF2477357040BB0413AC401D6548F7691FD528BEBF08C120419845C622A4EAC33E441C080F97E6DA5F0409FA9BAC760FBC1A3919206A98DE42A4269D0F7A58E709A921BF420D3D47E1D1911A7117E74181BEBFD09B9B81F026DAFF19BAD0A8F45B9DAC3D67A727310191D0AA7C8AA0C0A79D7DB225CCF4C1F0C3685CF76E504831B505275B6868B85D8DF0797D15AA6D533A928D9E375835B72CB9923846958AF834464ED9701B13835E03C4B27C5F6A8BA37D530CDFA93B5DE4825A69E5C919E2E29105EC47EBAE88243F496BDF91A72D119F26E5228639C932266126717205C890B96FAF31CC909AFB349AB5A923B31F7B97DB79BE2EC6569C3E92E6F979FC7269E7FE9A3463BD0CAFCA662716F2AAC1002225556215F419CFF504C8F658F640E5A87987962AFFF5BF59A2D62AB24C4F35E8B24E7115C19BEA2871851FDC92CEF661FA3AD29273D13C6AA4163D7346A29FA6468183B9DA5CE6279F5D6B7BB815B41FD3BC782B35B7EC2D03A75B36640F6565434BA9BC144A1EB24937DD5DDCEF8EC5495046E949018FCA240FE51ACEFFEDC268905BC80278A0D1F5409E605CCF90818E63F32DB02ABFDA174F36F7FD4249C4C7006D10BBAE571F3A656DB0E01F1285E59664A6637457826E359DCF3158D30D52762F30EB0E4A94F3A2283C61A9854435C48080A9E8D96021D3B92986FE8DB113FF666C8CE7FF4F78DFB4126C71359EFC232FD749D00A68BDE31F8B0F64F4C28EC627E32CC2583BF771C2C4C99B04EF9CBE453B9BE088212BA446A4935870A83A1702B37437A666180EC915A8E7711FA16897EF5B91837B59EE598D38D4CDC94E27FF0679864F064D3199113FFE0D121CC49734DB7EFDA3A3C368FE32D685B202FBCA5081B854A618C730225D6C2144A343DCC2B1BC31CEF8EF0ABE5BEF44CBC8D4788A2F4F71533B8BD9D01C1A76193AD3DADCDE897D894F39C666B11D085B6D27DA7DCDC1F2CE7DE93C63A9E1D56704772F2417123EE095485B39FEB8D48D6FE7948662A877C52DE9A1BD33F733392F035B7017189941920761ED922006C211D66A25BB6B2788492C4C7EF388955AC3A52C9D946A0A37971386ECB5B383C8E5EC8F5E2BD5F6B4A29E7F63F1DBF4E79C442A202FD406BDEB495892CF2A9A9BB3BE5F45571510DDA05F7DC93A14312D774B1DE2E40969ED959E8D89441FB593007B52D1C97215A66BC6DC6788B527A339298FB4D14896C20EE15740B6C996A37BF8BD71FFDC1301A052A96C9741C268133DBEFF05E9E7E114B186A801F5D4CB4C3580F180115F117D872B8D1A365AEA409557BB13D036A3F1D18CFA4AE3B538EC3943EBF64C8C43104C1CFAC83D0E4A983265659B1727BC2641C4C1A4325553830FA0197D867F6EAE646F699FCB62C984E63951B007534C04A692C0BCCEA335391EF5511B49FBA2DE4FFEBAA29F105D9A9888C06DDD3EB4BCC9C15F5A4CD5D178996B18B9E451F275560BF1C5D071540D4FAD7166A51FFDF1E01BF28BE295A1CC244A01E8EE0189A6298A82C4B93B43CD9059F819AD62095FA4DC190288A778EF5695AE0E14C0B647FDD8ECCD6BC386AFA828C5EB8BF8C26E37F30C115C7F527382C19BF2C731E8405100631FEB104BF00E0F4AA0B10BFBB0CB302193AB3C033388A2D100E220A50A08A6AF272ABC3DC426D88C7EA5E2A986DB35650D807D830E5DC515A6B3780280CBFB83C1C60DD9300F00C25BF5774DFA657285AE1748A9840ED993262CA5A62B2B29FCDDCF46911B80DA06BEFE7AE2C9B2225BAE78E8DAB27175D78A96A5AC7AB4C851343C3B103D0126FD6FCBF736A0D62F1B0E2CD059B7ECE554368F104E76DA6839C036251965522D3B421FF34D09A1566678B8B0A68031A22373103D63D1E7E34406E968E29226B6BAA6DF6EC027399E8C7ED179693A7BA7C52B73869436E14F83548A65A901003FBBD59F7E55BAA38809E06B612F05B21FBE96C2BBFA824AF4819686F8BB97846A12F312144A10E388C7123E396C0F107F55B88105CD7323D353FC1A64528AE57FC7FF6958A25DA869E8F552A3F247170A4B2CBE093770280371D26D120E4CD99D42D5FEFBFA7BFE2E372181E73C90FEDDCB7E6A66FF9882D6F5EB9D2003E3F16FE9A22076E92DC5047706F6198D419E8F4FDAF7E84F2CB2CF98B342E74B8E6A605F3F5D3504B2EE59A73E516D3FB4CBA40B9D165682A95A5643CE5EE949041E320CD96852DD37573395AADA4A644D500A70BBC565236637D293BC21927F786BCECC961D0946B02660F7C3BD2BCE0398F3A05456C9D7B698FABB0A0C9FB75D830EA74513030ED001AA6657083E7AA02043CA88BF244D8997A9A51759AD10380719EB1506F8214AAF4126D031AFE2444A8622C80F9BC0CA1798F06B557A9CB0613934A6B6E9F6334004959C44758B68BF067959A78ED20BB90FA4874DD405AACA5991AC496A35E5B6B64DC54D17C743F820F96EF14BA3EE8ADC6DC36DAF14D3B49E9C9940C5CEF190F90D7C7298F1586942F190DB541216F34F2AC06B304873E3F82BA907BB85D646DDD99BC004278E78FF60E032EC82058A3A7DE2C64A55CB4B10C89C6D5C11C9B3E342EC8920DBD0A38A830CA2FA8517FEF6051D21C7284FA56BC53B67761A021D51C78A38B66873DD0C0F5029017BE6B823382C8E68555DF30E5ADC0D46F65B99014863CAE4D92DF3D17EC97DE17EE486A262A2F50348F6975E46006C6CF4233C1142792B8FBC74F1705135E092AB022AE44114B298BF5B5C95B597909482EB32CC79AE2477FC12E2BB6858240994A5015C67129BC5CAAA35C368D854285D39F9EF873374A70EBD302DE85871A5C9AB7B8348533D5857B01CD45AB1912BD34992C255E4F48EE35BCD4B9214B4E087CAEA03A6CE4E67124A5DDFAFCC47307F127839D9969E46A32B9EB76449C7406C25857CA0D332D0082EF24B2C58B8AEEAA935161D0EC4735174DA1E5ADE682084E3DC61EA3F0747E4570520AED3640DE434AFE4075C6900801EF1330808F35937CA7E089134134A206FAD5DA95542DB57D1ADF8293020A6DC11A2AED289976214DC1D7BDD07016ADCC3E62D9D630C0DBD7EEA8EBDC5A40E20B47762D14213DF9B6FF690C3655A7DC399D80FA81DFB22730AFCD03544C0A3ACE1210BC044B0B35B6D630630BF13F2FAC2B9294660C95A3678199B8C681B0BC38A53B5322864717BF2023FAC93B207DC6B2D7E2229D3D1FE5FDBA576C3DD1F2AC94A7AE50B2214DDD00745A33AF4C87EE1A4D1285853AB0788B50C7DFBFC63AFE9B1A09932D309FDD80E563DC69E75674DE56D6F1A09E9CE973AC5A65C394193EA2B563A7856382837C4110EE9B39CE38F61FCE965804E43EF191422A270517501146ED4DE717F93703CEC3C6F2508008CF2935E57A5D1894CA6B5FEE5AFA46EA10A4BB3A88D3BCADF01FC22264754876E3FB0D4AE47F611061DD1BD4D4B8DD324D6FFDF376BA43EC3D735CA6912066DDFD53B619E4D7EAC5F24AC6D1667FBF1220D132FA0B1D45F5753B6495EE702CD80206E1AE3228B76F054234A129E19253A799527DA5444B796F907614AFB46BC4A3EFBE6309BCD109D0BC7FB13CF14477C9DFDA7F9F06C03F346616FF89B792DB6D624B8560456FEF03648014DEC140926580460A20005F68534CFCBC60AAB3E70049BDEA9BE72ED6D77899D0948593BFE9E86F515FA385DD0100BC5311050AFC28D650A2367BAFED9B46CCECDBC3686E2E2A68C7819E92FA95B4C35466EDFD42E387768325C5D069B5561F6DE56F167160A747795613A310CDCF11B0F20DC97E79BEB0B85608D4A4DD1F9A763179D7C6754805101268962F163607F4AD655419A9F83C0C7F952351C4F958958B02C8F89930BD04A823B455E5389A6E7E42D3C88651B07F39C893851E0020B3E29193513AF26D0E713E721E7F228BEA6F5359FBCFBEE7305D13A13E70433EAA0914F67232971484B6F71EC8011D59E5584F66AF1E4BDE497F6B0823108E1F2FE6E3AFA96BBC477898E66223F4D9C2D147C2CE1ADE7F89AE237FBA73EAF34BAC66A6200BDF2A5374C965E0949136865F3DC7997C5A56A0CC11D5839534CB38B19359672F87F38B436AF8330CF5140BF2CD791231C00BB6837A636ED6D1A84FCA4379A4A3D428B4601B2DB2E7DA7FDA0B5C89E2F5EBA9AFBD2A79DAFCC4BC923F792D7B89B947EE754F2ACBF56C7FD5F3E1C12E57838991E42E7696763BE2345FEC4BAE2929B340BB9D7324706FA9045A1A5926ECF6C28961FD6B0E47FAC5B30001FA5174444B88705F26B7434DFF57FD455F8FB5DBC03AF47480FEFBFC618BA636565694B0BBDFA4972388A7C2361EB3E60F1409FE6FB7789D4737E192B20ABEAD4C47CEA3C4F1CF1D6234099EF1000A0466626814625091E04430BD5975400809A2B32FDBD9709A720BC3E98F43D34A0B21B48B19F2F2360D6FC63CCFCD75E4BC92BEC930ABD4695D77BC4E3AB5BFC728F6622C6B884E104B223BD755B9463C73CE758C2803780864661B6AE8896B837B1742E691EB7D849039DFF1DB4DD94A5F4CA329442D0C88C82FA757E6A5B59B50D350300A05D1B1080952B4B592F6BEE7ED6E00CCD708FB9B00CB82715466385A68F736AF5E8851794ED1B15F010F5BABD580BEA08575B477D0C2ED9720F7B9DB1C0A36346230E4A7F60FABB90E078AFAC520994D0A3246E23DC956A1693C14FA53FBC8F6F4A32DC368D737600346D541626E53A70FCD6C7241FF72F704B56E00FE7CDE1FA0BC661C06E0BA8E53526316CCD4D95B5B1A7963843D3E770886153A9F07878513556D96B9A9975BD3FEB9AF8B590A0D694A2B8A05C472E710AD0BD3CE0D5A486171FB6EBC86881DF263030B93872B8FFFFC25AE2183D36B745FBF77AE3A4AE1E20CC0161D6D25E0073194875EB63037D090D24814B7C216AF36C730C3511E7CD078E6DFA2413CAF021519EEDD84BF080FC2855C56DFA417B1BC780BFB493D4E188DAC1F5AC22B10086FB25C6CB36B063389FE23967ED7DB0216B324BEA5E3BBB681199DDC3E20EC52AB4BEC2BFA30771A1DC93FB9FBFD3012654E2659B2BA50CB4576C0E5FD754802D6EED142641015E2F8611FC086FE07B0EFBBB558DFA00EA6CFA4A88B10F622AC9F88601C7F9154D96A7ECA64AC94BA8447ABC3BE3CE52FFD7E4AA099DA0E8F94FDE925E1531407DCF6B424BCE09C50A32A5A28FA918D55F9034A17E4D960B33F5F12D913217AD71A2AF22D11470EA8CCB1BE9BFB2136FE7CD27A0E1018EF3EADDFDA3364660812C3F6FDE5CA5693422EF482AE1FFA3203E8A484508DFDB05B9B09A59C6127A61C2E0741B197FD5B0953263E6104F97432742B0B961540AD913607B47C19AB724349E137021AFB4E7165048177FF0EC91B3D97702606359FD7ED8F462FCAB76588737D608B39F8929ACB8E75986E7AA9B3F9E247B8BAE1C20EC718E8029DC04ACEB173FBDDEADC7D8832DA4A706E9AC3E0FB3AEFEEEDC929275DB6C87E808A9D767D5B2410C1F4703A7332B462026925C109DF008BCB88D4B8E00FD48B9F4D9A9ECF183B86B1E4E92A0B0800FFB5D9B952F2DB2E1A7919249365071A9EF92643FCC42DB8D2238CE448CAA2554649C54169F0CE03B954D0AB967C6221289A1C87BC6AF70D1E72B8317DAE35C090E745334F1CD614D4C5EE39DD1E51FF620E5B1D39A7C037B2C9167470341F11A16C090F6AF93DC74CDAA9AF7A127A32474836ACEE3C1DFB02715198C8ECA874E28AEC8AE59DB426122BE9FF1F5216FE65183873249A5C19F79E61AF2E333616BA950B2056D1DAE4A87527499009435C0D8323DDE5A27F4248FA58FB8A8396E1EC18CBBAF1C5C01BBEDC96E932F4498A4F12213732D8DC521AB56BB995F07C393298231EFE664245ED02FC19FF139D47FA550E47A3A884916958434733101844FFDA1037E7F4A85905EB7D8DCBA2B3589202678AFB4607C5FB482DC96B6153F024ACB774C7426A088720CAB5E509479EB6AE309C25837911D321FFFAF6B5F5D746C2D1CA870F9733313EFA6349169810376D73C9EE0477CCBD3D31AFB111B28D47BB1569D0B1F174493A4B5244E22391D7F15C7DBB9AE01459329D5F58A4DF2393AC208362E50489B87E3E9A7D5473F9AF417E366F4037AE71B7CCCD0CEF85A581B9EE38FC45510A48403E12564288C0BECD1BBB67C4318F52A88867E58220FA43D88004B4FADAC785C6C85B79FFC6FEF97F75989A665ED93D77A78638B1C214EADAFC24B007B2429FFA1E9ABDA087D0911DED012DE1D7366507CA44BC5CFDF805BEED4CDF2BB8E1DA6F4EB154B7E503F9A7DD9B31B7612A38118567951D184AA6829D69B7089A559DDD8C4B21E9535199A0479B1B7085072505161CD528BD82BAE9ED2C30665317280BAE1BEF3ED3434C7A02B243FD9E3DE7671CA7D7A907D3D23E2EAD56A74B1A047AEEBDE58CCC9F09301F87C8C24B9B3AC129984A6EC4D0EC7541B96759FD8764CD9BAE4FBF0E2166389446A41AF86187DCD54CE274E346397EABD20B1076C242FF2B5D9BD7173B22CFE852BF092E19D850575304710EB1A91AEA36AF3D4E57BBE9FE0134E8FD7D5C35BFD06A8920F109CE7C2DC3A32720081542DA5CF2D7A72514526CB428319C1A974B88C848080058B7C9F5EA80F68073B57FFDE037CB04162537460FFB630B53E1BEDF303DE5A137654D7386C62B44C8F98914B0F27EF7EB55FE726656C8BC9A4B93E09C224699F340183C25D5C2F8A11153255DF5E749805A9BB0BF253F846ED4E87D12D517C508059F6B0790B8850A462BAC89BE0CF6DAEAB971DB73E93B940CDDA05740C4E2F7AC115A75F280BA7ACE6B237D84991CC5C1E0C45AFE44EEFDF3F119CE0022EDEA602254626A5A7CFA7AA83BB12A6B57A989344F5D3CDF41224A41F6217BD52B8E587E1314228C8DDF84C67A02A54D46B5AB5DED1DEC3D0438F2B3953B8E620D0DEE39914242B4569648D8CD7C42896589B014C80EBBA772A0F0F32F559445EFEAFDF2DBC0DBC9FF1F4F0DE478F64729DCEFACD9864591C18FF8D0E7BF049BED035D42C6E00AB3AE5362CDA40FC5F0A8243068F9B910ED5611CCF037889C81147FDE7E717DDAB3B49D1FA18BFA025357C2AFA9D2DEDF7D5D86ACF06CA5BA7D40C7E8431B6E7E4EECD9F2361D726A26BC2382011A3CD1F7CCB625C0E3ED17E54545C424AF277A1FA03C1974DA63107F58D8ECEA6A5C468F249A5AD50D222529C79C25214508A1A0C6D5F48BBDD19A467D005E7599CDC0879F0BEF76020F88360410C2D87FD0855D4019C0D80BF68237F07D31A126C12C130C78883D6E958E272D2D3E6AA26F7F83838DA49563E8C21289F554AE7A10E214C299573104D0185BF7C27FD5708FC278506C0C5B458B7F369128F8FC93D625A74C3D47F2480BBDE060816057090E5BA46C4E316F7D8EA80C496EE046D7511499DF23D7ADBC69F722A48A90CF2F3A7C84ABF3AD8DBA94058F7C492C4D619462AE241F99F3B121D0890997FE43B087AD8B60C3BA1BA9DBE8C79B42A143C47181335BA92CF4701CB07D5F9537A1C7338E9B12D9C0664C8A2F2E4720AFA5047CE290D39DA6938388E9A05982FB83BDE723E4444A78ED7B4F22F25FDAF70C814C0ED5246AB643E183A34E33ED050AAD6D9F5128ED66CBF378F24D85C00DFC6E2E10588FA635D57F0E4D98EC84F690E556B8BB5AA9D71AB2C01F5B6EAD743A165DD151D591FD2881B83CA1563AD72D439D9FAAC1358B39E1F3BF05D67ED69023C843799BE6C5301D921A633A70CA87CB1A1C5F8BC931D37ADE608F52633EC11452352ACCE6605527D5123DFDECC2CD2A78E21BBA233F1A378F98DF25 \ No newline at end of file diff --git a/assets/resources/subghz/nice_flor_s b/assets/resources/subghz/nice_flor_s new file mode 100644 index 00000000..2e4e2fa8 --- /dev/null +++ b/assets/resources/subghz/nice_flor_s @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz Keystore RAW File +Version: 0 +Encryption: 1 +IV: 33 69 62 1D AD 20 1B B8 A1 6C CF 6F 6B 6F D5 18 +Encrypt_data: RAW +9C1D6E6733912B28AC0FF1A191660810BDFF00D19BF7839AFF5B2AAFBBC91A38 \ No newline at end of file diff --git a/bootloader/Makefile b/bootloader/Makefile index 534346fc..08dcee83 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -1,5 +1,6 @@ -PROJECT_ROOT = $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) -PROJECT = bootloader +MAKEFILE_DIR := $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +PROJECT_ROOT := $(abspath $(MAKEFILE_DIR)/..) +PROJECT := bootloader include $(PROJECT_ROOT)/make/base.mk diff --git a/firmware/Makefile b/firmware/Makefile index cf100a21..3237ed59 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -1,5 +1,6 @@ -PROJECT_ROOT = $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) -PROJECT = firmware +MAKEFILE_DIR := $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +PROJECT_ROOT := $(abspath $(MAKEFILE_DIR)/..) +PROJECT := firmware include $(PROJECT_ROOT)/make/base.mk include $(PROJECT_ROOT)/make/freertos-heap.mk diff --git a/lib/ReadMe.md b/lib/ReadMe.md index f29f028b..89a09f41 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -20,6 +20,6 @@ - `qrcode` - Qr code generator library - `ST25RFAL002` - ST253916 driver and NFC hal - `STM32CubeWB` - STM32WB series cube package -- `subghz` - Subghz library +- `subghz` - SubGhz library - `toolbox` - Toolbox of things that we are using but don't place in core - `u8g2` - Graphics library that we use to draw GUI diff --git a/lib/subghz/blocks/const.c b/lib/subghz/blocks/const.c new file mode 100644 index 00000000..15719b2a --- /dev/null +++ b/lib/subghz/blocks/const.c @@ -0,0 +1 @@ +#include "const.h" diff --git a/lib/subghz/blocks/const.h b/lib/subghz/blocks/const.h new file mode 100644 index 00000000..57b47d50 --- /dev/null +++ b/lib/subghz/blocks/const.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +typedef struct { + const uint16_t te_long; + const uint16_t te_short; + const uint16_t te_delta; + const uint8_t min_count_bit_for_found; +} SubGhzBlockConst; diff --git a/lib/subghz/blocks/decoder.c b/lib/subghz/blocks/decoder.c new file mode 100644 index 00000000..d2237f6c --- /dev/null +++ b/lib/subghz/blocks/decoder.c @@ -0,0 +1,17 @@ +#include "decoder.h" + +#define TAG "SubGhzBlockDecoder" + +void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit) { + decoder->decode_data = decoder->decode_data << 1 | bit; + decoder->decode_count_bit++; +} + +uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len) { + uint8_t hash = 0; + uint8_t* p = (uint8_t*)&decoder->decode_data; + for(size_t i = 0; i < len; i++) { + hash ^= p[i]; + } + return hash; +} diff --git a/lib/subghz/blocks/decoder.h b/lib/subghz/blocks/decoder.h new file mode 100644 index 00000000..a2d14f95 --- /dev/null +++ b/lib/subghz/blocks/decoder.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +typedef struct SubGhzBlockDecoder SubGhzBlockDecoder; + +struct SubGhzBlockDecoder { + uint32_t parser_step; + uint32_t te_last; + uint64_t decode_data; + uint8_t decode_count_bit; +}; + +void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit); +uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len); diff --git a/lib/subghz/blocks/encoder.c b/lib/subghz/blocks/encoder.c new file mode 100644 index 00000000..e4b1ddba --- /dev/null +++ b/lib/subghz/blocks/encoder.c @@ -0,0 +1,3 @@ +#include "encoder.h" + +#define TAG "SubGhzBlockEncoder" diff --git a/lib/subghz/blocks/encoder.h b/lib/subghz/blocks/encoder.h new file mode 100644 index 00000000..5c84fcd1 --- /dev/null +++ b/lib/subghz/blocks/encoder.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include + +typedef struct { + bool is_runing; + size_t repeat; + size_t front; + size_t size_upload; + LevelDuration* upload; + +} SubGhzProtocolBlockEncoder; diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c new file mode 100644 index 00000000..811a5fa7 --- /dev/null +++ b/lib/subghz/blocks/generic.c @@ -0,0 +1,118 @@ +#include "generic.h" +#include "../types.h" +#include +#include + +#define TAG "SubGhzBlockGeneric" + +bool subghz_block_generic_get_preset_name(FuriHalSubGhzPreset preset, string_t preset_str) { + const char* preset_name; + switch(preset) { + case FuriHalSubGhzPresetOok270Async: + preset_name = "FuriHalSubGhzPresetOok270Async"; + break; + case FuriHalSubGhzPresetOok650Async: + preset_name = "FuriHalSubGhzPresetOok650Async"; + break; + case FuriHalSubGhzPreset2FSKDev238Async: + preset_name = "FuriHalSubGhzPreset2FSKDev238Async"; + break; + case FuriHalSubGhzPreset2FSKDev476Async: + preset_name = "FuriHalSubGhzPreset2FSKDev476Async"; + break; + default: + FURI_LOG_E(TAG, "Unknown preset"); + return false; + break; + } + string_set(preset_str, preset_name); + return true; +} + +bool subghz_block_generic_serialize( + SubGhzBlockGeneric* instance, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(instance); + bool res = false; + string_t temp_str; + string_init(temp_str); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + if(!subghz_block_generic_get_preset_name(preset, temp_str)) { + break; + } + if(!flipper_format_write_string_cstr(flipper_format, "Preset", string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + uint32_t temp = instance->data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->data >> i * 8) & 0xFF; + } + + if(!flipper_format_write_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + res = true; + } while(false); + string_clear(temp_str); + return res; +} + +bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool res = false; + string_t temp_str; + string_init(temp_str); + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + instance->data_count_bit = (uint8_t)temp_data; + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Key"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + + res = true; + } while(0); + + string_clear(temp_str); + + return res; +} diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h new file mode 100644 index 00000000..29bb5f48 --- /dev/null +++ b/lib/subghz/blocks/generic.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include "furi_hal.h" + +typedef struct SubGhzBlockGeneric SubGhzBlockGeneric; + +struct SubGhzBlockGeneric { + const char* protocol_name; + uint64_t data; + uint32_t serial; + uint8_t data_count_bit; + uint8_t btn; + uint16_t cnt; +}; + +bool subghz_block_generic_get_preset_name(FuriHalSubGhzPreset preset, string_t preset_str); + +bool subghz_block_generic_serialize( + SubGhzBlockGeneric* instance, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format); diff --git a/lib/subghz/blocks/math.c b/lib/subghz/blocks/math.c new file mode 100644 index 00000000..55ad7cc8 --- /dev/null +++ b/lib/subghz/blocks/math.c @@ -0,0 +1,9 @@ +#include "math.h" + +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit) { + uint64_t key_reverse = 0; + for(uint8_t i = 0; i < count_bit; i++) { + key_reverse = key_reverse << 1 | bit_read(key, i); + } + return key_reverse; +} diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h new file mode 100644 index 00000000..48eb8a8f --- /dev/null +++ b/lib/subghz/blocks/math.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include + +#define bit_read(value, bit) (((value) >> (bit)) & 0x01) +#define bit_set(value, bit) ((value) |= (1UL << (bit))) +#define bit_clear(value, bit) ((value) &= ~(1UL << (bit))) +#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) +#define DURATION_DIFF(x, y) ((x < y) ? (y - x) : (x - y)) + +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit); diff --git a/lib/subghz/environment.c b/lib/subghz/environment.c new file mode 100644 index 00000000..46d0d368 --- /dev/null +++ b/lib/subghz/environment.c @@ -0,0 +1,67 @@ +#include "environment.h" + +struct SubGhzEnvironment { + SubGhzKeystore* keystore; + const char* came_atomo_rainbow_table_file_name; + const char* nice_flor_s_rainbow_table_file_name; +}; + +SubGhzEnvironment* subghz_environment_alloc() { + SubGhzEnvironment* instance = malloc(sizeof(SubGhzEnvironment)); + + instance->keystore = subghz_keystore_alloc(); + instance->came_atomo_rainbow_table_file_name = NULL; + instance->nice_flor_s_rainbow_table_file_name = NULL; + + return instance; +} + +void subghz_environment_free(SubGhzEnvironment* instance) { + furi_assert(instance); + + subghz_keystore_free(instance->keystore); + + free(instance); +} + +bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename) { + furi_assert(instance); + + return subghz_keystore_load(instance->keystore, filename); +} + +SubGhzKeystore* subghz_environment_get_keystore(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->keystore; +} + +void subghz_environment_set_came_atomo_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename) { + furi_assert(instance); + + instance->came_atomo_rainbow_table_file_name = filename; +} + +const char* + subghz_environment_get_came_atomo_rainbow_table_file_name(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->came_atomo_rainbow_table_file_name; +} + +void subghz_environment_set_nice_flor_s_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename) { + furi_assert(instance); + + instance->nice_flor_s_rainbow_table_file_name = filename; +} + +const char* + subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->nice_flor_s_rainbow_table_file_name; +} diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h new file mode 100644 index 00000000..2a2d04ce --- /dev/null +++ b/lib/subghz/environment.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "subghz_keystore.h" + +typedef struct SubGhzEnvironment SubGhzEnvironment; + +SubGhzEnvironment* subghz_environment_alloc(); + +void subghz_environment_free(SubGhzEnvironment* instance); + +bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename); + +SubGhzKeystore* subghz_environment_get_keystore(SubGhzEnvironment* instance); + +void subghz_environment_set_came_atomo_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename); + +const char* subghz_environment_get_came_atomo_rainbow_table_file_name(SubGhzEnvironment* instance); + +void subghz_environment_set_nice_flor_s_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename); + +const char* + subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance); diff --git a/lib/subghz/protocols/base.c b/lib/subghz/protocols/base.c new file mode 100644 index 00000000..90096c2b --- /dev/null +++ b/lib/subghz/protocols/base.c @@ -0,0 +1,64 @@ +#include "base.h" +#include "registry.h" + +void subghz_protocol_decoder_base_set_decoder_callback( + SubGhzProtocolDecoderBase* decoder_base, + SubGhzProtocolDecoderBaseRxCallback callback, + void* context) { + decoder_base->callback = callback; + decoder_base->context = context; +} + +bool subghz_protocol_decoder_base_get_string( + SubGhzProtocolDecoderBase* decoder_base, + string_t output) { + bool status = false; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->get_string) { + decoder_base->protocol->decoder->get_string(decoder_base, output); + status = true; + } + + return status; +} + +bool subghz_protocol_decoder_base_serialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + bool status = false; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->serialize) { + status = decoder_base->protocol->decoder->serialize( + decoder_base, flipper_format, frequency, preset); + } + + return status; +} + +bool subghz_protocol_decoder_base_deserialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format) { + bool status = false; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->deserialize) { + status = decoder_base->protocol->decoder->deserialize(decoder_base, flipper_format); + } + + return status; +} + +uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base) { + uint8_t hash = 0; + + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->get_hash_data) { + hash = decoder_base->protocol->decoder->get_hash_data(decoder_base); + } + + return hash; +} diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h new file mode 100644 index 00000000..4232bc14 --- /dev/null +++ b/lib/subghz/protocols/base.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../types.h" + +typedef struct SubGhzProtocolDecoderBase SubGhzProtocolDecoderBase; + +typedef void ( + *SubGhzProtocolDecoderBaseRxCallback)(SubGhzProtocolDecoderBase* instance, void* context); + +typedef void ( + *SubGhzProtocolDecoderBaseSerialize)(SubGhzProtocolDecoderBase* decoder_base, string_t output); + +struct SubGhzProtocolDecoderBase { + // Decoder general section + const SubGhzProtocol* protocol; + + // Callback section + SubGhzProtocolDecoderBaseRxCallback callback; + void* context; +}; + +void subghz_protocol_decoder_base_set_decoder_callback( + SubGhzProtocolDecoderBase* decoder_base, + SubGhzProtocolDecoderBaseRxCallback callback, + void* context); + +bool subghz_protocol_decoder_base_get_string( + SubGhzProtocolDecoderBase* decoder_base, + string_t output); + +bool subghz_protocol_decoder_base_serialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +bool subghz_protocol_decoder_base_deserialize( + SubGhzProtocolDecoderBase* decoder_base, + FlipperFormat* flipper_format); + +uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base); + +// Encoder Base +typedef struct SubGhzProtocolEncoderBase SubGhzProtocolEncoderBase; + +struct SubGhzProtocolEncoderBase { + // Decoder general section + const SubGhzProtocol* protocol; + + // Callback section +}; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c new file mode 100644 index 00000000..90220cd3 --- /dev/null +++ b/lib/subghz/protocols/came.c @@ -0,0 +1,313 @@ +#include "came.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define TAG "SubGhzProtocolCAME" + +static const SubGhzBlockConst subghz_protocol_came_const = { + .te_short = 320, + .te_long = 640, + .te_delta = 150, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderCame { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderCame { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + CameDecoderStepReset = 0, + CameDecoderStepFoundStartBit, + CameDecoderStepSaveDuration, + CameDecoderStepCheckDuration, +} CameDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_came_decoder = { + .alloc = subghz_protocol_decoder_came_alloc, + .free = subghz_protocol_decoder_came_free, + + .feed = subghz_protocol_decoder_came_feed, + .reset = subghz_protocol_decoder_came_reset, + + .get_hash_data = subghz_protocol_decoder_came_get_hash_data, + .serialize = subghz_protocol_decoder_came_serialize, + .deserialize = subghz_protocol_decoder_came_deserialize, + .get_string = subghz_protocol_decoder_came_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_came_encoder = { + .alloc = subghz_protocol_encoder_came_alloc, + .free = subghz_protocol_encoder_came_free, + + .deserialize = subghz_protocol_encoder_came_deserialize, + .stop = subghz_protocol_encoder_came_stop, + .yield = subghz_protocol_encoder_came_yield, +}; + +const SubGhzProtocol subghz_protocol_came = { + .name = SUBGHZ_PROTOCOL_CAME_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_came_decoder, + .encoder = &subghz_protocol_came_encoder, +}; + +void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderCame* instance = malloc(sizeof(SubGhzProtocolEncoderCame)); + + instance->base.protocol = &subghz_protocol_came; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_came_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderCame* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short * 36); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderCame* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_came_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_came_stop(void* context) { + SubGhzProtocolEncoderCame* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_came_yield(void* context) { + SubGhzProtocolEncoderCame* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_came_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderCame* instance = malloc(sizeof(SubGhzProtocolDecoderCame)); + instance->base.protocol = &subghz_protocol_came; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_came_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + free(instance); +} + +void subghz_protocol_decoder_came_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + instance->decoder.parser_step = CameDecoderStepReset; +} + +void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + switch(instance->decoder.parser_step) { + case CameDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 51) < + subghz_protocol_came_const.te_delta * 51)) { //Need protocol 36 te_short + //Found header CAME + instance->decoder.parser_step = CameDecoderStepFoundStartBit; + } + break; + case CameDecoderStepFoundStartBit: + if(!level) { + break; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_const.te_short) < + subghz_protocol_came_const.te_delta) { + //Found start bit CAME + instance->decoder.parser_step = CameDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = CameDecoderStepReset; + } + break; + case CameDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_came_const.te_short * 4)) { + instance->decoder.parser_step = CameDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_came_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = CameDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = CameDecoderStepReset; + } + break; + case CameDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_came_const.te_short) < + subghz_protocol_came_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_came_const.te_long) < + subghz_protocol_came_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = CameDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_came_const.te_long) < + subghz_protocol_came_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_came_const.te_short) < + subghz_protocol_came_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = CameDecoderStepSaveDuration; + } else + instance->decoder.parser_step = CameDecoderStepReset; + } else { + instance->decoder.parser_step = CameDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_came_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_came_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_came_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderCame* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); +} diff --git a/lib/subghz/protocols/came.h b/lib/subghz/protocols/came.h new file mode 100644 index 00000000..9b0e3f80 --- /dev/null +++ b/lib/subghz/protocols/came.h @@ -0,0 +1,63 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CAME_NAME "CAME" + +typedef struct SubGhzProtocolDecoderCame SubGhzProtocolDecoderCame; +typedef struct SubGhzProtocolEncoderCame SubGhzProtocolEncoderCame; + +extern const SubGhzProtocolDecoder subghz_protocol_came_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_came_encoder; +extern const SubGhzProtocol subghz_protocol_came; + +void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment); + +void subghz_protocol_encoder_came_free(void* context); + +bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format); + +void subghz_protocol_encoder_came_stop(void* context); + +LevelDuration subghz_protocol_encoder_came_yield(void* context); + +/** Allocate SubGhzProtocolCame + * + * @return SubGhzProtocolCame* + */ +void* subghz_protocol_decoder_came_alloc(SubGhzEnvironment* environment); + +/** Free SubGhzProtocolCame + * + * @param instance + */ +void subghz_protocol_decoder_came_free(void* context); + +/** Reset internal state + * @param instance - SubGhzProtocolCame instance + */ +void subghz_protocol_decoder_came_reset(void* context); + +/** Parse accepted duration + * + * @param instance - SubGhzProtocolCame instance + * @param data - LevelDuration level_duration + */ +void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration); + +uint8_t subghz_protocol_decoder_came_get_hash_data(void* context); + +/** Outputting information from the parser + * + * @param instance - SubGhzProtocolCame* instance + * @param output - output string + */ +bool subghz_protocol_decoder_came_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format); + +void subghz_protocol_decoder_came_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c new file mode 100644 index 00000000..4cde0186 --- /dev/null +++ b/lib/subghz/protocols/came_atomo.c @@ -0,0 +1,338 @@ +#include "came_atomo.h" +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoCameAtomo" + +#define SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE 0xFFFFFFFFFFFFFFFF + +static const SubGhzBlockConst subghz_protocol_came_atomo_const = { + .te_short = 600, + .te_long = 1200, + .te_delta = 250, + .min_count_bit_for_found = 62, +}; + +struct SubGhzProtocolDecoderCameAtomo { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + ManchesterState manchester_saved_state; + const char* came_atomo_rainbow_table_file_name; +}; + +struct SubGhzProtocolEncoderCameAtomo { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + CameAtomoDecoderStepReset = 0, + CameAtomoDecoderStepDecoderData, +} CameAtomoDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder = { + .alloc = subghz_protocol_decoder_came_atomo_alloc, + .free = subghz_protocol_decoder_came_atomo_free, + + .feed = subghz_protocol_decoder_came_atomo_feed, + .reset = subghz_protocol_decoder_came_atomo_reset, + + .get_hash_data = subghz_protocol_decoder_came_atomo_get_hash_data, + .serialize = subghz_protocol_decoder_came_atomo_serialize, + .deserialize = subghz_protocol_decoder_came_atomo_deserialize, + .get_string = subghz_protocol_decoder_came_atomo_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_came_atomo = { + .name = SUBGHZ_PROTOCOL_CAME_ATOMO_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_came_atomo_decoder, + .encoder = &subghz_protocol_came_atomo_encoder, +}; + +void* subghz_protocol_decoder_came_atomo_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolDecoderCameAtomo)); + instance->base.protocol = &subghz_protocol_came_atomo; + instance->generic.protocol_name = instance->base.protocol->name; + instance->came_atomo_rainbow_table_file_name = + subghz_environment_get_came_atomo_rainbow_table_file_name(environment); + if(instance->came_atomo_rainbow_table_file_name) { + FURI_LOG_I( + TAG, "Loading rainbow table from %s", instance->came_atomo_rainbow_table_file_name); + } + return instance; +} + +void subghz_protocol_decoder_came_atomo_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + instance->came_atomo_rainbow_table_file_name = NULL; + free(instance); +} + +void subghz_protocol_decoder_came_atomo_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + instance->decoder.parser_step = CameAtomoDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case CameAtomoDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 65) < + subghz_protocol_came_atomo_const.te_delta * 20)) { + //Found header CAME + instance->decoder.parser_step = CameAtomoDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } + break; + case CameAtomoDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_short) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_came_atomo_const.te_long * 2 + + subghz_protocol_came_atomo_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_came_atomo_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = CameAtomoDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_short) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long) < + subghz_protocol_came_atomo_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = CameAtomoDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** Read bytes from rainbow table + * + * @param file_name - file name rainbow table + * @param number_atomo_magic_xor + * @return atomo_magic_xor + */ +static uint64_t subghz_protocol_came_atomo_get_magic_xor_in_file( + const char* file_name, + uint8_t number_atomo_magic_xor) { + if(!strcmp(file_name, "")) return SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE; + + uint8_t buffer[sizeof(uint64_t)] = {0}; + uint32_t address = number_atomo_magic_xor * sizeof(uint64_t); + uint64_t atomo_magic_xor = 0; + + if(subghz_keystore_raw_get_data(file_name, address, buffer, sizeof(uint64_t))) { + for(size_t i = 0; i < sizeof(uint64_t); i++) { + atomo_magic_xor = (atomo_magic_xor << 8) | buffer[i]; + } + } else { + atomo_magic_xor = SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE; + } + return atomo_magic_xor; +} + +/** Analysis of received data + * + * @param instance SubGhzBlockGeneric instance + */ +static void subghz_protocol_came_atomo_remote_controller( + SubGhzBlockGeneric* instance, + const char* file_name) { + /* + * 0x1fafef3ed0f7d9ef + * 0x185fcc1531ee86e7 + * 0x184fa96912c567ff + * 0x187f8a42f3dc38f7 + * 0x186f63915492a5cd + * 0x181f40bab58bfac5 + * 0x180f25c696a01bdd + * 0x183f06ed77b944d5 + * 0x182ef661d83d21a9 + * 0x18ded54a39247ea1 + * 0x18ceb0361a0f9fb9 + * 0x18fe931dfb16c0b1 + * 0x18ee7ace5c585d8b + * ........ + * transmission consists of 99 parcels with increasing counter while holding down the button + * with each new press, the counter in the encrypted part increases + * + * 0x1FAFF13ED0F7D9EF + * 0x1FAFF11ED0F7D9EF + * 0x1FAFF10ED0F7D9EF + * 0x1FAFF0FED0F7D9EF + * 0x1FAFF0EED0F7D9EF + * 0x1FAFF0DED0F7D9EF + * 0x1FAFF0CED0F7D9EF + * 0x1FAFF0BED0F7D9EF + * 0x1FAFF0AED0F7D9EF + * + * where 0x1FAF - parcel counter, 0хF0A - button press counter, + * 0xED0F7D9E - serial number, 0хF - key + * 0x1FAF parcel counter - 1 in the parcel queue ^ 0x185F = 0x07F0 + * 0x185f ^ 0x185F = 0x0000 + * 0x184f ^ 0x185F = 0x0010 + * 0x187f ^ 0x185F = 0x0020 + * ..... + * 0x182e ^ 0x185F = 0x0071 + * 0x18de ^ 0x185F = 0x0081 + * ..... + * 0x1e43 ^ 0x185F = 0x061C + * where the last nibble is incremented every 8 samples + * + * Decode + * + * 0x1cf6931dfb16c0b1 => 0x1cf6 + * 0x1cf6 ^ 0x185F = 0x04A9 + * 0x04A9 => 0x04A = 74 (dec) + * 74+1 % 32(atomo_magic_xor) = 11 + * GET atomo_magic_xor[11] = 0xXXXXXXXXXXXXXXXX + * 0x931dfb16c0b1 ^ 0xXXXXXXXXXXXXXXXX = 0xEF3ED0F7D9EF + * 0xEF3 ED0F7D9E F => 0xEF3 - CNT, 0xED0F7D9E - SN, 0xF - key + * + * */ + + uint16_t parcel_counter = instance->data >> 48; + parcel_counter = parcel_counter ^ 0x185F; + parcel_counter >>= 4; + uint8_t ind = (parcel_counter + 1) % 32; + uint64_t temp_data = instance->data & 0x0000FFFFFFFFFFFF; + uint64_t atomo_magic_xor = subghz_protocol_came_atomo_get_magic_xor_in_file(file_name, ind); + + if(atomo_magic_xor != SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE) { + temp_data = temp_data ^ atomo_magic_xor; + instance->cnt = temp_data >> 36; + instance->serial = (temp_data >> 4) & 0x000FFFFFFFF; + instance->btn = temp_data & 0xF; + } else { + instance->cnt = 0; + instance->serial = 0; + instance->btn = 0; + } +} + +uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_came_atomo_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_came_atomo_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderCameAtomo* instance = context; + subghz_protocol_came_atomo_remote_controller( + &instance->generic, instance->came_atomo_rainbow_table_file_name); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%03X\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h new file mode 100644 index 00000000..ee816f61 --- /dev/null +++ b/lib/subghz/protocols/came_atomo.h @@ -0,0 +1,24 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_CAME_ATOMO_NAME "CAME Atomo" + +typedef struct SubGhzProtocolDecoderCameAtomo SubGhzProtocolDecoderCameAtomo; +typedef struct SubGhzProtocolEncoderCameAtomo SubGhzProtocolEncoderCameAtomo; + +extern const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder; +extern const SubGhzProtocol subghz_protocol_came_atomo; + +void* subghz_protocol_decoder_came_atomo_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_came_atomo_free(void* context); +void subghz_protocol_decoder_came_atomo_reset(void* context); +void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); +bool subghz_protocol_decoder_came_atomo_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_came_atomo_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c new file mode 100644 index 00000000..54c6585e --- /dev/null +++ b/lib/subghz/protocols/came_twee.c @@ -0,0 +1,445 @@ +#include "came_twee.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/forum/showthread.php?t=635&highlight=came+twin + * + */ + +#define TAG "SubGhzProtocolCAME_Twee" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') + +static const uint32_t came_twee_magic_numbers_xor[15] = { + 0x0E0E0E00, + 0x1D1D1D11, + 0x2C2C2C22, + 0x3B3B3B33, + 0x4A4A4A44, + 0x59595955, + 0x68686866, + 0x77777777, + 0x86868688, + 0x95959599, + 0xA4A4A4AA, + 0xB3B3B3BB, + 0xC2C2C2CC, + 0xD1D1D1DD, + 0xE0E0E0EE, +}; + +static const SubGhzBlockConst subghz_protocol_came_twee_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 250, + .min_count_bit_for_found = 54, +}; + +struct SubGhzProtocolDecoderCameTwee { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderCameTwee { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + CameTweeDecoderStepReset = 0, + CameTweeDecoderStepDecoderData, +} CameTweeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder = { + .alloc = subghz_protocol_decoder_came_twee_alloc, + .free = subghz_protocol_decoder_came_twee_free, + + .feed = subghz_protocol_decoder_came_twee_feed, + .reset = subghz_protocol_decoder_came_twee_reset, + + .get_hash_data = subghz_protocol_decoder_came_twee_get_hash_data, + .serialize = subghz_protocol_decoder_came_twee_serialize, + .deserialize = subghz_protocol_decoder_came_twee_deserialize, + .get_string = subghz_protocol_decoder_came_twee_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder = { + .alloc = subghz_protocol_encoder_came_twee_alloc, + .free = subghz_protocol_encoder_came_twee_free, + + .deserialize = subghz_protocol_encoder_came_twee_deserialize, + .stop = subghz_protocol_encoder_came_twee_stop, + .yield = subghz_protocol_encoder_came_twee_yield, +}; + +const SubGhzProtocol subghz_protocol_came_twee = { + .name = SUBGHZ_PROTOCOL_CAME_TWEE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_came_twee_decoder, + .encoder = &subghz_protocol_came_twee_encoder, +}; + +void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderCameTwee* instance = malloc(sizeof(SubGhzProtocolEncoderCameTwee)); + + instance->base.protocol = &subghz_protocol_came_twee; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_came_twee_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderCameTwee* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_came_twee_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_came_twee_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_came_twee_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_came_twee_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_came_twee_const.te_short; + data.level = true; + break; + + default: + FURI_LOG_E(TAG, "DO CRASH HERE."); + furi_crash(NULL); + break; + } + return level_duration_make(data.level, data.duration); +} + +static void subghz_protocol_encoder_came_twee_get_upload(SubGhzProtocolEncoderCameTwee* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + uint64_t temp_parcel = 0x003FFF7200000000; //parcel mask + + for(int i = 14; i >= 0; i--) { + temp_parcel = (temp_parcel & 0xFFFFFFFF00000000) | + (instance->generic.serial ^ came_twee_magic_numbers_xor[i]); + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_came_twee_add_duration_to_upload(result); + manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_came_twee_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_came_twee_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_twee_const.te_long * 51); + } + instance->encoder.size_upload = index; +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolCameTwee instance + */ +static void subghz_protocol_came_twee_remote_controller(SubGhzBlockGeneric* instance) { + /* Came Twee 54 bit, rolling code 15 parcels with + * a decreasing counter from 0xE to 0x0 + * with originally coded dip switches on the console 10 bit code + * + * 0x003FFF72E04A6FEE + * 0x003FFF72D17B5EDD + * 0x003FFF72C2684DCC + * 0x003FFF72B3193CBB + * 0x003FFF72A40E2BAA + * 0x003FFF72953F1A99 + * 0x003FFF72862C0988 + * 0x003FFF7277DDF877 + * 0x003FFF7268C2E766 + * 0x003FFF7259F3D655 + * 0x003FFF724AE0C544 + * 0x003FFF723B91B433 + * 0x003FFF722C86A322 + * 0x003FFF721DB79211 + * 0x003FFF720EA48100 + * + * decryption + * the last 32 bits, do XOR by the desired number, divide the result by 4, + * convert the first 16 bits of the resulting 32-bit number to bin and do + * bit-by-bit mirroring, adding up to 10 bits + * + * Example + * Step 1. 0x003FFF721DB79211 => 0x1DB79211 + * Step 4. 0x1DB79211 xor 0x1D1D1D11 => 0x00AA8F00 + * Step 4. 0x00AA8F00 / 4 => 0x002AA3C0 + * Step 5. 0x002AA3C0 => 0x002A + * Step 6. 0x002A bin => b101010 + * Step 7. b101010 => b0101010000 + * Step 8. b0101010000 => (Dip) Off ON Off ON Off ON Off Off Off Off + */ + + uint8_t cnt_parcel = (uint8_t)(instance->data & 0xF); + uint32_t data = (uint32_t)(instance->data & 0x0FFFFFFFF); + + data = (data ^ came_twee_magic_numbers_xor[cnt_parcel]); + instance->serial = data; + data /= 4; + instance->btn = (data >> 4) & 0x0F; + data >>= 16; + data = (uint16_t)subghz_protocol_blocks_reverse_key(data, 16); + instance->cnt = data >> 6; +} + +bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderCameTwee* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_came_twee_remote_controller(&instance->generic); + subghz_protocol_encoder_came_twee_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_came_twee_stop(void* context) { + SubGhzProtocolEncoderCameTwee* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) { + SubGhzProtocolEncoderCameTwee* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_came_twee_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderCameTwee* instance = malloc(sizeof(SubGhzProtocolDecoderCameTwee)); + instance->base.protocol = &subghz_protocol_came_twee; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_came_twee_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + free(instance); +} + +void subghz_protocol_decoder_came_twee_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + instance->decoder.parser_step = CameTweeDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case CameTweeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long * 51) < + subghz_protocol_came_twee_const.te_delta * 20)) { + //Found header CAME + instance->decoder.parser_step = CameTweeDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongLow, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } + break; + case CameTweeDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_short) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_came_twee_const.te_long * 2 + + subghz_protocol_came_twee_const.te_delta)) { + if(instance->decoder.decode_count_bit >= + subghz_protocol_came_twee_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongLow, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = CameTweeDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_short) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long) < + subghz_protocol_came_twee_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = CameTweeDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_came_twee_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_came_twee_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderCameTwee* instance = context; + subghz_protocol_came_twee_remote_controller(&instance->generic); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Btn:%lX\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.btn, + CNT_TO_DIP(instance->generic.cnt)); +} diff --git a/lib/subghz/protocols/came_twee.h b/lib/subghz/protocols/came_twee.h new file mode 100644 index 00000000..044839b9 --- /dev/null +++ b/lib/subghz/protocols/came_twee.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CAME_TWEE_NAME "CAME TWEE" + +typedef struct SubGhzProtocolDecoderCameTwee SubGhzProtocolDecoderCameTwee; +typedef struct SubGhzProtocolEncoderCameTwee SubGhzProtocolEncoderCameTwee; + +extern const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder; +extern const SubGhzProtocol subghz_protocol_came_twee; + +void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_came_twee_free(void* context); +bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_came_twee_stop(void* context); +LevelDuration subghz_protocol_encoder_came_twee_yield(void* context); +void* subghz_protocol_decoder_came_twee_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_came_twee_free(void* context); +void subghz_protocol_decoder_came_twee_reset(void* context); +void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); +bool subghz_protocol_decoder_came_twee_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_came_twee_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c new file mode 100644 index 00000000..aa337f00 --- /dev/null +++ b/lib/subghz/protocols/faac_slh.c @@ -0,0 +1,218 @@ +#include "faac_slh.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolFaacSHL" + +static const SubGhzBlockConst subghz_protocol_faac_slh_const = { + .te_short = 255, + .te_long = 595, + .te_delta = 100, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderFaacSLH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderFaacSLH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + FaacSLHDecoderStepReset = 0, + FaacSLHDecoderStepFoundPreambula, + FaacSLHDecoderStepSaveDuration, + FaacSLHDecoderStepCheckDuration, +} FaacSLHDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder = { + .alloc = subghz_protocol_decoder_faac_slh_alloc, + .free = subghz_protocol_decoder_faac_slh_free, + + .feed = subghz_protocol_decoder_faac_slh_feed, + .reset = subghz_protocol_decoder_faac_slh_reset, + + .get_hash_data = subghz_protocol_decoder_faac_slh_get_hash_data, + .serialize = subghz_protocol_decoder_faac_slh_serialize, + .deserialize = subghz_protocol_decoder_faac_slh_deserialize, + .get_string = subghz_protocol_decoder_faac_slh_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_faac_slh = { + .name = SUBGHZ_PROTOCOL_FAAC_SLH_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_faac_slh_decoder, + .encoder = &subghz_protocol_faac_slh_encoder, +}; + +void* subghz_protocol_decoder_faac_slh_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderFaacSLH* instance = malloc(sizeof(SubGhzProtocolDecoderFaacSLH)); + instance->base.protocol = &subghz_protocol_faac_slh; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_faac_slh_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + free(instance); +} + +void subghz_protocol_decoder_faac_slh_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + instance->decoder.parser_step = FaacSLHDecoderStepReset; +} + +void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + + switch(instance->decoder.parser_step) { + case FaacSLHDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long * 2) < + subghz_protocol_faac_slh_const.te_delta * 3)) { + instance->decoder.parser_step = FaacSLHDecoderStepFoundPreambula; + } + break; + case FaacSLHDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long * 2) < + subghz_protocol_faac_slh_const.te_delta * 3)) { + //Found Preambula + instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + break; + case FaacSLHDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_faac_slh_const.te_short * 3 + + subghz_protocol_faac_slh_const.te_delta)) { + instance->decoder.parser_step = FaacSLHDecoderStepFoundPreambula; + if(instance->decoder.decode_count_bit >= + subghz_protocol_faac_slh_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = FaacSLHDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + break; + case FaacSLHDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_faac_slh_const.te_short) < + subghz_protocol_faac_slh_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long) < + subghz_protocol_faac_slh_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_faac_slh_const.te_long) < + subghz_protocol_faac_slh_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_short) < + subghz_protocol_faac_slh_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + } else { + instance->decoder.parser_step = FaacSLHDecoderStepReset; + } + break; + } +} + +static void subghz_protocol_faac_slh_check_remote_controller(SubGhzBlockGeneric* instance) { + uint64_t code_found_reverse = + subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t code_fix = code_found_reverse & 0xFFFFFFFF; + + instance->serial = code_fix & 0xFFFFFFF; + instance->btn = (code_fix >> 28) & 0x0F; +} + +uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_faac_slh_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_faac_slh_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderFaacSLH* instance = context; + subghz_protocol_faac_slh_check_remote_controller(&instance->generic); + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_fix = code_found_reverse & 0xFFFFFFFF; + uint32_t code_hop = (code_found_reverse >> 32) & 0xFFFFFFFF; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%lX%08lX\r\n" + "Fix:%08lX \r\n" + "Hop:%08lX \r\n" + "Sn:%07lX Btn:%lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + code_fix, + code_hop, + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h new file mode 100644 index 00000000..d3ef2cd2 --- /dev/null +++ b/lib/subghz/protocols/faac_slh.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_FAAC_SLH_NAME "Faac SLH" + +typedef struct SubGhzProtocolDecoderFaacSLH SubGhzProtocolDecoderFaacSLH; +typedef struct SubGhzProtocolEncoderFaacSLH SubGhzProtocolEncoderFaacSLH; + +extern const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder; +extern const SubGhzProtocol subghz_protocol_faac_slh; + +void* subghz_protocol_decoder_faac_slh_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_faac_slh_free(void* context); +void subghz_protocol_decoder_faac_slh_reset(void* context); +void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); +bool subghz_protocol_decoder_faac_slh_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_faac_slh_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c new file mode 100644 index 00000000..d45c2257 --- /dev/null +++ b/lib/subghz/protocols/gate_tx.c @@ -0,0 +1,308 @@ +#include "gate_tx.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolGateTx" + +static const SubGhzBlockConst subghz_protocol_gate_tx_const = { + .te_short = 350, + .te_long = 700, + .te_delta = 100, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderGateTx { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderGateTx { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + GateTXDecoderStepReset = 0, + GateTXDecoderStepFoundStartBit, + GateTXDecoderStepSaveDuration, + GateTXDecoderStepCheckDuration, +} GateTXDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder = { + .alloc = subghz_protocol_decoder_gate_tx_alloc, + .free = subghz_protocol_decoder_gate_tx_free, + + .feed = subghz_protocol_decoder_gate_tx_feed, + .reset = subghz_protocol_decoder_gate_tx_reset, + + .get_hash_data = subghz_protocol_decoder_gate_tx_get_hash_data, + .serialize = subghz_protocol_decoder_gate_tx_serialize, + .deserialize = subghz_protocol_decoder_gate_tx_deserialize, + .get_string = subghz_protocol_decoder_gate_tx_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder = { + .alloc = subghz_protocol_encoder_gate_tx_alloc, + .free = subghz_protocol_encoder_gate_tx_free, + + .deserialize = subghz_protocol_encoder_gate_tx_deserialize, + .stop = subghz_protocol_encoder_gate_tx_stop, + .yield = subghz_protocol_encoder_gate_tx_yield, +}; + +const SubGhzProtocol subghz_protocol_gate_tx = { + .name = SUBGHZ_PROTOCOL_GATE_TX_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_gate_tx_decoder, + .encoder = &subghz_protocol_gate_tx_encoder, +}; + +void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderGateTx* instance = malloc(sizeof(SubGhzProtocolEncoderGateTx)); + + instance->base.protocol = &subghz_protocol_gate_tx; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_gate_tx_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderGateTx* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_encoder_gate_tx_get_upload(SubGhzProtocolEncoderGateTx* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_short * 49); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_long); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderGateTx* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_gate_tx_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_gate_tx_stop(void* context) { + SubGhzProtocolEncoderGateTx* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context) { + SubGhzProtocolEncoderGateTx* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_gate_tx_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderGateTx* instance = malloc(sizeof(SubGhzProtocolDecoderGateTx)); + instance->base.protocol = &subghz_protocol_gate_tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_gate_tx_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + free(instance); +} + +void subghz_protocol_decoder_gate_tx_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + instance->decoder.parser_step = GateTXDecoderStepReset; +} + +void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + + switch(instance->decoder.parser_step) { + case GateTXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_short * 47) < + subghz_protocol_gate_tx_const.te_delta * 47)) { + //Found Preambula + instance->decoder.parser_step = GateTXDecoderStepFoundStartBit; + } + break; + case GateTXDecoderStepFoundStartBit: + if(level && ((DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_long) < + subghz_protocol_gate_tx_const.te_delta * 3))) { + //Found start bit + instance->decoder.parser_step = GateTXDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = GateTXDecoderStepReset; + } + break; + case GateTXDecoderStepSaveDuration: + if(!level) { + if(duration >= (subghz_protocol_gate_tx_const.te_short * 10 + + subghz_protocol_gate_tx_const.te_delta)) { + instance->decoder.parser_step = GateTXDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_gate_tx_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GateTXDecoderStepCheckDuration; + } + } + break; + case GateTXDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gate_tx_const.te_short) < + subghz_protocol_gate_tx_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_long) < + subghz_protocol_gate_tx_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GateTXDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gate_tx_const.te_long) < + subghz_protocol_gate_tx_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_short) < + subghz_protocol_gate_tx_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GateTXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GateTXDecoderStepReset; + } + } else { + instance->decoder.parser_step = GateTXDecoderStepReset; + } + break; + } +} + +static void subghz_protocol_gate_tx_check_remote_controller(SubGhzBlockGeneric* instance) { + uint32_t code_found_reverse = + subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + + instance->serial = (code_found_reverse & 0xFF) << 12 | + ((code_found_reverse >> 8) & 0xFF) << 4 | + ((code_found_reverse >> 20) & 0x0F); + instance->btn = ((code_found_reverse >> 16) & 0x0F); +} + +uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_gate_tx_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_gate_tx_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderGateTx* instance = context; + subghz_protocol_gate_tx_check_remote_controller(&instance->generic); + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%06lX\r\n" + "Sn:%05lX Btn:%lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/gate_tx.h b/lib/subghz/protocols/gate_tx.h new file mode 100644 index 00000000..fea5422d --- /dev/null +++ b/lib/subghz/protocols/gate_tx.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_GATE_TX_NAME "GateTX" + +typedef struct SubGhzProtocolDecoderGateTx SubGhzProtocolDecoderGateTx; +typedef struct SubGhzProtocolEncoderGateTx SubGhzProtocolEncoderGateTx; + +extern const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder; +extern const SubGhzProtocol subghz_protocol_gate_tx; + +void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_gate_tx_free(void* context); +bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_gate_tx_stop(void* context); +LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context); +void* subghz_protocol_decoder_gate_tx_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_gate_tx_free(void* context); +void subghz_protocol_decoder_gate_tx_reset(void* context); +void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); +bool subghz_protocol_decoder_gate_tx_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_gate_tx_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c new file mode 100644 index 00000000..8c32fcf7 --- /dev/null +++ b/lib/subghz/protocols/hormann.c @@ -0,0 +1,331 @@ +#include "hormann.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolHormannHSM" + +static const SubGhzBlockConst subghz_protocol_hormann_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 200, + .min_count_bit_for_found = 44, +}; + +struct SubGhzProtocolDecoderHormann { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderHormann { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + HormannDecoderStepReset = 0, + HormannDecoderStepFoundStartHeader, + HormannDecoderStepFoundHeader, + HormannDecoderStepFoundStartBit, + HormannDecoderStepSaveDuration, + HormannDecoderStepCheckDuration, +} HormannDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_hormann_decoder = { + .alloc = subghz_protocol_decoder_hormann_alloc, + .free = subghz_protocol_decoder_hormann_free, + + .feed = subghz_protocol_decoder_hormann_feed, + .reset = subghz_protocol_decoder_hormann_reset, + + .get_hash_data = subghz_protocol_decoder_hormann_get_hash_data, + .serialize = subghz_protocol_decoder_hormann_serialize, + .deserialize = subghz_protocol_decoder_hormann_deserialize, + .get_string = subghz_protocol_decoder_hormann_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_hormann_encoder = { + .alloc = subghz_protocol_encoder_hormann_alloc, + .free = subghz_protocol_encoder_hormann_free, + + .deserialize = subghz_protocol_encoder_hormann_deserialize, + .stop = subghz_protocol_encoder_hormann_stop, + .yield = subghz_protocol_encoder_hormann_yield, +}; + +const SubGhzProtocol subghz_protocol_hormann = { + .name = SUBGHZ_PROTOCOL_HORMANN_HSM_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_hormann_decoder, + .encoder = &subghz_protocol_hormann_encoder, +}; + +void* subghz_protocol_encoder_hormann_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderHormann* instance = malloc(sizeof(SubGhzProtocolEncoderHormann)); + + instance->base.protocol = &subghz_protocol_hormann; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 2048; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_hormann_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHormann* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHormann* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = 3 + (instance->generic.data_count_bit * 2 + 2) * 20 + 1; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 64); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64); + instance->encoder.repeat = 10; //original remote does 10 repeats + + for(size_t repeat = 0; repeat < 20; repeat++) { + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 24); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_long); + } + } + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 24); + return true; +} + +bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHormann* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_hormann_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_hormann_stop(void* context) { + SubGhzProtocolEncoderHormann* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_hormann_yield(void* context) { + SubGhzProtocolEncoderHormann* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_hormann_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderHormann* instance = malloc(sizeof(SubGhzProtocolDecoderHormann)); + instance->base.protocol = &subghz_protocol_hormann; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_hormann_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + free(instance); +} + +void subghz_protocol_decoder_hormann_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + instance->decoder.parser_step = HormannDecoderStepReset; +} + +void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + + switch(instance->decoder.parser_step) { + case HormannDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) < + subghz_protocol_hormann_const.te_delta * 64)) { + instance->decoder.parser_step = HormannDecoderStepFoundStartHeader; + } + break; + case HormannDecoderStepFoundStartHeader: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) < + subghz_protocol_hormann_const.te_delta * 64)) { + instance->decoder.parser_step = HormannDecoderStepFoundHeader; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + case HormannDecoderStepFoundHeader: + if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 24) < + subghz_protocol_hormann_const.te_delta * 24)) { + instance->decoder.parser_step = HormannDecoderStepFoundStartBit; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + case HormannDecoderStepFoundStartBit: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short) < + subghz_protocol_hormann_const.te_delta)) { + instance->decoder.parser_step = HormannDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + case HormannDecoderStepSaveDuration: + if(level) { //save interval + if(duration >= (subghz_protocol_hormann_const.te_short * 5)) { + instance->decoder.parser_step = HormannDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_hormann_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = HormannDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + case HormannDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hormann_const.te_short) < + subghz_protocol_hormann_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_long) < + subghz_protocol_hormann_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = HormannDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hormann_const.te_long) < + subghz_protocol_hormann_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short) < + subghz_protocol_hormann_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = HormannDecoderStepSaveDuration; + } else + instance->decoder.parser_step = HormannDecoderStepReset; + } else { + instance->decoder.parser_step = HormannDecoderStepReset; + } + break; + } +} + +static void subghz_protocol_hormann_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = (instance->data >> 4) & 0xF; +} + +uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_hormann_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_hormann_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderHormann* instance = context; + subghz_protocol_hormann_check_remote_controller(&instance->generic); + + string_cat_printf( + output, + "%s\r\n" + "%dbit\r\n" + "Key:0x%03lX%08lX\r\n" + "Btn:0x%01X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/hormann.h b/lib/subghz/protocols/hormann.h new file mode 100644 index 00000000..c5748603 --- /dev/null +++ b/lib/subghz/protocols/hormann.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HORMANN_HSM_NAME "Hormann HSM" + +typedef struct SubGhzProtocolDecoderHormann SubGhzProtocolDecoderHormann; +typedef struct SubGhzProtocolEncoderHormann SubGhzProtocolEncoderHormann; + +extern const SubGhzProtocolDecoder subghz_protocol_hormann_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_hormann_encoder; +extern const SubGhzProtocol subghz_protocol_hormann; + +void* subghz_protocol_encoder_hormann_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_hormann_free(void* context); +bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_hormann_stop(void* context); +LevelDuration subghz_protocol_encoder_hormann_yield(void* context); +void* subghz_protocol_decoder_hormann_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_hormann_free(void* context); +void subghz_protocol_decoder_hormann_reset(void* context); +void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context); +bool subghz_protocol_decoder_hormann_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_hormann_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c new file mode 100644 index 00000000..d97c90c4 --- /dev/null +++ b/lib/subghz/protocols/ido.c @@ -0,0 +1,218 @@ +#include "ido.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocol_iDo_117/111" + +static const SubGhzBlockConst subghz_protocol_ido_const = { + .te_short = 450, + .te_long = 1450, + .te_delta = 150, + .min_count_bit_for_found = 48, +}; + +struct SubGhzProtocolDecoderIDo { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderIDo { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + IDoDecoderStepReset = 0, + IDoDecoderStepFoundPreambula, + IDoDecoderStepSaveDuration, + IDoDecoderStepCheckDuration, +} IDoDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_ido_decoder = { + .alloc = subghz_protocol_decoder_ido_alloc, + .free = subghz_protocol_decoder_ido_free, + + .feed = subghz_protocol_decoder_ido_feed, + .reset = subghz_protocol_decoder_ido_reset, + + .get_hash_data = subghz_protocol_decoder_ido_get_hash_data, + .deserialize = subghz_protocol_decoder_ido_deserialize, + .serialize = subghz_protocol_decoder_ido_serialize, + .get_string = subghz_protocol_decoder_ido_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_ido_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_ido = { + .name = SUBGHZ_PROTOCOL_IDO_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_ido_decoder, + .encoder = &subghz_protocol_ido_encoder, +}; + +void* subghz_protocol_decoder_ido_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderIDo* instance = malloc(sizeof(SubGhzProtocolDecoderIDo)); + instance->base.protocol = &subghz_protocol_ido; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_ido_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + free(instance); +} + +void subghz_protocol_decoder_ido_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + instance->decoder.parser_step = IDoDecoderStepReset; +} + +void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + + switch(instance->decoder.parser_step) { + case IDoDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_ido_const.te_short * 10) < + subghz_protocol_ido_const.te_delta * 5)) { + instance->decoder.parser_step = IDoDecoderStepFoundPreambula; + } + break; + case IDoDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_ido_const.te_short * 10) < + subghz_protocol_ido_const.te_delta * 5)) { + //Found Preambula + instance->decoder.parser_step = IDoDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + break; + case IDoDecoderStepSaveDuration: + if(level) { + if(duration >= + (subghz_protocol_ido_const.te_short * 5 + subghz_protocol_ido_const.te_delta)) { + instance->decoder.parser_step = IDoDecoderStepFoundPreambula; + if(instance->decoder.decode_count_bit >= + subghz_protocol_ido_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = IDoDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + break; + case IDoDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ido_const.te_short) < + subghz_protocol_ido_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ido_const.te_long) < + subghz_protocol_ido_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = IDoDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ido_const.te_short) < + subghz_protocol_ido_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_ido_const.te_short) < + subghz_protocol_ido_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = IDoDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + } else { + instance->decoder.parser_step = IDoDecoderStepReset; + } + break; + } +} + +static void subghz_protocol_ido_check_remote_controller(SubGhzBlockGeneric* instance) { + uint64_t code_found_reverse = + subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t code_fix = code_found_reverse & 0xFFFFFF; + + instance->serial = code_fix & 0xFFFFF; + instance->btn = (code_fix >> 20) & 0x0F; +} + +uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_ido_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_ido_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderIDo* instance = context; + + subghz_protocol_ido_check_remote_controller(&instance->generic); + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_fix = code_found_reverse & 0xFFFFFF; + uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFFF; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Fix:%06lX \r\n" + "Hop:%06lX \r\n" + "Sn:%05lX Btn:%lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + code_fix, + code_hop, + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/ido.h b/lib/subghz/protocols/ido.h new file mode 100644 index 00000000..c357725c --- /dev/null +++ b/lib/subghz/protocols/ido.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_IDO_NAME "iDo 117/111" + +typedef struct SubGhzProtocolDecoderIDo SubGhzProtocolDecoderIDo; +typedef struct SubGhzProtocolEncoderIDo SubGhzProtocolEncoderIDo; + +extern const SubGhzProtocolDecoder subghz_protocol_ido_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_ido_encoder; +extern const SubGhzProtocol subghz_protocol_ido; + +void* subghz_protocol_decoder_ido_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_ido_free(void* context); +void subghz_protocol_decoder_ido_reset(void* context); +void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context); +bool subghz_protocol_decoder_ido_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_ido_get_string(void* context, string_t output); \ No newline at end of file diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c new file mode 100644 index 00000000..1679af3f --- /dev/null +++ b/lib/subghz/protocols/keeloq.c @@ -0,0 +1,681 @@ +#include "keeloq.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolkeeloq" + +static const SubGhzBlockConst subghz_protocol_keeloq_const = { + .te_short = 400, + .te_long = 800, + .te_delta = 140, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderKeeloq { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; + const char* manufacture_name; +}; + +struct SubGhzProtocolEncoderKeeloq { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + SubGhzKeystore* keystore; + const char* manufacture_name; +}; + +typedef enum { + KeeloqDecoderStepReset = 0, + KeeloqDecoderStepCheckPreambula, + KeeloqDecoderStepSaveDuration, + KeeloqDecoderStepCheckDuration, +} KeeloqDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_keeloq_decoder = { + .alloc = subghz_protocol_decoder_keeloq_alloc, + .free = subghz_protocol_decoder_keeloq_free, + + .feed = subghz_protocol_decoder_keeloq_feed, + .reset = subghz_protocol_decoder_keeloq_reset, + + .get_hash_data = subghz_protocol_decoder_keeloq_get_hash_data, + .serialize = subghz_protocol_decoder_keeloq_serialize, + .deserialize = subghz_protocol_decoder_keeloq_deserialize, + .get_string = subghz_protocol_decoder_keeloq_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_keeloq_encoder = { + .alloc = subghz_protocol_encoder_keeloq_alloc, + .free = subghz_protocol_encoder_keeloq_free, + + .deserialize = subghz_protocol_encoder_keeloq_deserialize, + .stop = subghz_protocol_encoder_keeloq_stop, + .yield = subghz_protocol_encoder_keeloq_yield, +}; + +const SubGhzProtocol subghz_protocol_keeloq = { + .name = SUBGHZ_PROTOCOL_KEELOQ_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_keeloq_decoder, + .encoder = &subghz_protocol_keeloq_encoder, +}; + +static void subghz_protocol_keeloq_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name); + +void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderKeeloq* instance = malloc(sizeof(SubGhzProtocolEncoderKeeloq)); + + instance->base.protocol = &subghz_protocol_keeloq; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_keeloq_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { + instance->generic.cnt++; + uint32_t fix = btn << 28 | instance->generic.serial; + uint32_t decrypt = btn << 28 | + (instance->generic.serial & 0x3FF) + << 16 | //ToDo in some protocols the discriminator is 0 + instance->generic.cnt; + uint32_t hop = 0; + uint64_t man = 0; + int res = 0; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + res = strcmp(string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning + hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); + break; + case KEELOQ_LEARNING_NORMAL: + //Simple Learning + man = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + instance->generic.serial, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_UNKNOWN: + hop = 0; //todo + break; + } + break; + } + } + if(hop) { + uint64_t yek = (uint64_t)fix << 32 | hop; + instance->generic.data = + subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); + return true; + } else { + instance->manufacture_name = "Unknown"; + return false; + } +} + +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->manufacture_name = manufacture_name; + instance->generic.data_count_bit = 64; + bool res = subghz_protocol_keeloq_gen_data(instance, btn); + if(res) { + res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + } + return res; +} + +static bool + subghz_protocol_encoder_keeloq_get_upload(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { + furi_assert(instance); + + //gen new key + if(subghz_protocol_keeloq_gen_data(instance, btn)) { + //ToDo Update display data + // if(instance->common.callback) + // instance->common.callback((SubGhzProtocolCommon*)instance, instance->common.context); + } else { + return false; + } + + size_t index = 0; + size_t size_upload = 11 * 2 + 2 + (instance->generic.data_count_bit * 2) + 4; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 11; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short * 10); + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short); + } + } + // +send 2 status bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_long); + // send end + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keeloq_const.te_short * 40); + + return true; +} + +bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderKeeloq* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + subghz_protocol_keeloq_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + if(strcmp(instance->manufacture_name, "DoorHan")) { + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_keeloq_stop(void* context) { + SubGhzProtocolEncoderKeeloq* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) { + SubGhzProtocolEncoderKeeloq* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_keeloq_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderKeeloq* instance = malloc(sizeof(SubGhzProtocolDecoderKeeloq)); + instance->base.protocol = &subghz_protocol_keeloq; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + return instance; +} + +void subghz_protocol_decoder_keeloq_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + + free(instance); +} + +void subghz_protocol_decoder_keeloq_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + instance->decoder.parser_step = KeeloqDecoderStepReset; +} + +void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + + switch(instance->decoder.parser_step) { + case KeeloqDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta) { + instance->decoder.parser_step = KeeloqDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case KeeloqDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta)) { + instance->decoder.parser_step = KeeloqDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short * 10) < + subghz_protocol_keeloq_const.te_delta * 10)) { + // Found header + instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = KeeloqDecoderStepReset; + instance->header_count = 0; + } + break; + case KeeloqDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KeeloqDecoderStepCheckDuration; + } + break; + case KeeloqDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_keeloq_const.te_short * 2 + + subghz_protocol_keeloq_const.te_delta)) { + // Found end TX + instance->decoder.parser_step = KeeloqDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_keeloq_const.min_count_bit_for_found) { + if(instance->generic.data != instance->decoder.decode_data) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_long) < + subghz_protocol_keeloq_const.te_delta)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_keeloq_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keeloq_const.te_long) < + subghz_protocol_keeloq_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < + subghz_protocol_keeloq_const.te_delta)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_keeloq_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KeeloqDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = KeeloqDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +static inline bool subghz_protocol_keeloq_check_decrypt( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn, + uint32_t end_serial) { + furi_assert(instance); + if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) || + ((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) { + instance->cnt = decrypt & 0x0000FFFF; + return true; + } + return false; +} + +/** Checking the accepted code against the database manafacture key + * + * @param instance SubGhzProtocolKeeloq instance + * @param fix fix part of the parcel + * @param hop hop encrypted part of the parcel + * @return true on successful search + */ +static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( + SubGhzBlockGeneric* instance, + uint32_t fix, + uint32_t hop, + SubGhzKeystore* keystore, + const char** manufacture_name) { + // protocol HCS300 uses 10 bits in discriminator, HCS200 uses 8 bits, for backward compatibility, we are looking for the 8-bit pattern + // HCS300 -> uint16_t end_serial = (uint16_t)(fix & 0x3FF); + // HCS200 -> uint16_t end_serial = (uint16_t)(fix & 0xFF); + + uint16_t end_serial = (uint16_t)(fix & 0xFF); + uint8_t btn = (uint8_t)(fix >> 28); + uint32_t decrypt = 0; + uint64_t man; + uint32_t seed = 0; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_SECURE: + man = subghz_protocol_keeloq_common_secure_learning( + fix, seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + //########################### + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + + // Secure Learning + man = subghz_protocol_keeloq_common_secure_learning( + fix, seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_secure_learning(fix, seed, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + + // Magic xor type1 learning + man = subghz_protocol_keeloq_common_magic_xor_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + + // Check for mirrored man + man = subghz_protocol_keeloq_common_magic_xor_type1_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + } + } + + *manufacture_name = "Unknown"; + instance->cnt = 0; + + return 0; +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolKeeloq instance + */ +static void subghz_protocol_keeloq_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t key_fix = key >> 32; + uint32_t key_hop = key & 0x00000000ffffffff; + // Check key AN-Motors + if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && + (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { + *manufacture_name = "AN-Motors"; + instance->cnt = key_hop >> 16; + } else if((key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { + *manufacture_name = "HCS101"; + instance->cnt = key_hop >> 16; + } else { + subghz_protocol_keeloq_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + } + + instance->serial = key_fix & 0x0FFFFFFF; + instance->btn = key_fix >> 28; +} + +uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_keeloq_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + subghz_protocol_keeloq_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + + if(res && !flipper_format_write_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { + FURI_LOG_E(TAG, "Unable to add manufacture name"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_keeloq_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderKeeloq* instance = context; + subghz_protocol_keeloq_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04X\r\n" + "Hop:0x%08lX Btn:%01lX\r\n" + "MF:%s\r\n" + "Sn:0x%07lX \r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name, + instance->generic.serial); +} diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h new file mode 100644 index 00000000..61c9a0e4 --- /dev/null +++ b/lib/subghz/protocols/keeloq.h @@ -0,0 +1,39 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_KEELOQ_NAME "KeeLoq" + +typedef struct SubGhzProtocolDecoderKeeloq SubGhzProtocolDecoderKeeloq; +typedef struct SubGhzProtocolEncoderKeeloq SubGhzProtocolEncoderKeeloq; + +extern const SubGhzProtocolDecoder subghz_protocol_keeloq_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_keeloq_encoder; +extern const SubGhzProtocol subghz_protocol_keeloq; + +void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_keeloq_free(void* context); +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_keeloq_stop(void* context); +LevelDuration subghz_protocol_encoder_keeloq_yield(void* context); +void* subghz_protocol_decoder_keeloq_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_keeloq_free(void* context); +void subghz_protocol_decoder_keeloq_reset(void* context); +void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); +bool subghz_protocol_decoder_keeloq_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_keeloq_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/subghz_protocol_keeloq_common.c b/lib/subghz/protocols/keeloq_common.c similarity index 97% rename from lib/subghz/protocols/subghz_protocol_keeloq_common.c rename to lib/subghz/protocols/keeloq_common.c index 9f9d105b..9dfc0934 100644 --- a/lib/subghz/protocols/subghz_protocol_keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -1,4 +1,4 @@ -#include "subghz_protocol_keeloq_common.h" +#include "keeloq_common.h" #include @@ -8,7 +8,7 @@ /** Simple Learning Encrypt * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter * @param key - manufacture (64bit) - * @return keelog encrypt data + * @return keeloq encrypt data */ inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { uint32_t x = data, r; diff --git a/lib/subghz/protocols/subghz_protocol_keeloq_common.h b/lib/subghz/protocols/keeloq_common.h similarity index 93% rename from lib/subghz/protocols/subghz_protocol_keeloq_common.h rename to lib/subghz/protocols/keeloq_common.h index 110ce9f9..b281666f 100644 --- a/lib/subghz/protocols/subghz_protocol_keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -1,6 +1,6 @@ #pragma once -#include "subghz_protocol_common.h" -#include "file_worker.h" + +#include "base.h" #include @@ -29,12 +29,12 @@ /** Simple Learning Encrypt * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter * @param key - manufacture (64bit) - * @return keelog encrypt data + * @return keeloq encrypt data */ uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key); /** Simple Learning Decrypt - * @param data - keelog encrypt data + * @param data - keeloq encrypt data * @param key - manufacture (64bit) * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter */ diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c new file mode 100644 index 00000000..2a16bced --- /dev/null +++ b/lib/subghz/protocols/kia.c @@ -0,0 +1,268 @@ +#include "kia.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoKIA" + +static const SubGhzBlockConst subghz_protocol_kia_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 100, + .min_count_bit_for_found = 60, +}; + +struct SubGhzProtocolDecoderKIA { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderKIA { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + KIADecoderStepReset = 0, + KIADecoderStepCheckPreambula, + KIADecoderStepSaveDuration, + KIADecoderStepCheckDuration, +} KIADecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_kia_decoder = { + .alloc = subghz_protocol_decoder_kia_alloc, + .free = subghz_protocol_decoder_kia_free, + + .feed = subghz_protocol_decoder_kia_feed, + .reset = subghz_protocol_decoder_kia_reset, + + .get_hash_data = subghz_protocol_decoder_kia_get_hash_data, + .serialize = subghz_protocol_decoder_kia_serialize, + .deserialize = subghz_protocol_decoder_kia_deserialize, + .get_string = subghz_protocol_decoder_kia_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_kia_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_kia = { + .name = SUBGHZ_PROTOCOL_KIA_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_kia_decoder, + .encoder = &subghz_protocol_kia_encoder, +}; + +void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderKIA* instance = malloc(sizeof(SubGhzProtocolDecoderKIA)); + instance->base.protocol = &subghz_protocol_kia; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_kia_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + free(instance); +} + +void subghz_protocol_decoder_kia_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + instance->decoder.parser_step = KIADecoderStepReset; +} + +void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + + switch(instance->decoder.parser_step) { + case KIADecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta)) { + instance->decoder.parser_step = KIADecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case KIADecoderStepCheckPreambula: + if(!level) { + if((DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + } else if( + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta)) { + // Found header + instance->header_count++; + break; + } else if( + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta)) { + // Found start bit + if(instance->header_count > 15) { + instance->decoder.parser_step = KIADecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + break; + case KIADecoderStepSaveDuration: + if(!level) { + if(duration >= + (subghz_protocol_kia_const.te_long + subghz_protocol_kia_const.te_delta * 2)) { + //Found stop bit + instance->decoder.parser_step = KIADecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_kia_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KIADecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + break; + case KIADecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) < + subghz_protocol_kia_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KIADecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) < + subghz_protocol_kia_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KIADecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + } else { + instance->decoder.parser_step = KIADecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_kia_crc8(uint8_t* data, size_t len) { + uint8_t crc = 0x08; + size_t i, j; + for(i = 0; i < len; i++) { + crc ^= data[i]; + for(j = 0; j < 8; j++) { + if((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x7F); + else + crc <<= 1; + } + } + return crc; +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolKIA instance + */ +static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * 0x0F 0112 43B04EC 1 7D + * 0x0F 0113 43B04EC 1 DF + * 0x0F 0114 43B04EC 1 30 + * 0x0F 0115 43B04EC 2 13 + * 0x0F 0116 43B04EC 3 F5 + * CNT Serial K CRC8 Kia (CRC8, poly 0x7f, start_crc 0x08) + */ + + instance->serial = (uint32_t)((instance->data >> 12) & 0x0FFFFFFF); + instance->btn = (instance->data >> 8) & 0x0F; + instance->cnt = (instance->data >> 40) & 0xFFFF; +} + +uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_kia_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_kia_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderKIA* instance = context; + + subghz_protocol_kia_check_remote_controller(&instance->generic); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Sn:%07lX Btn:%lX Cnt:%04X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/lib/subghz/protocols/kia.h b/lib/subghz/protocols/kia.h new file mode 100644 index 00000000..2f2ccde3 --- /dev/null +++ b/lib/subghz/protocols/kia.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_KIA_NAME "KIA Seed" + +typedef struct SubGhzProtocolDecoderKIA SubGhzProtocolDecoderKIA; +typedef struct SubGhzProtocolEncoderKIA SubGhzProtocolEncoderKIA; + +extern const SubGhzProtocolDecoder subghz_protocol_kia_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_kia_encoder; +extern const SubGhzProtocol subghz_protocol_kia; + +void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_kia_free(void* context); +void subghz_protocol_decoder_kia_reset(void* context); +void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context); +bool subghz_protocol_decoder_kia_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_kia_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c new file mode 100644 index 00000000..a9c19f38 --- /dev/null +++ b/lib/subghz/protocols/nero_radio.c @@ -0,0 +1,375 @@ +#include "nero_radio.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolNeroRadio" + +static const SubGhzBlockConst subghz_protocol_nero_radio_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 80, + .min_count_bit_for_found = 56, +}; + +struct SubGhzProtocolDecoderNeroRadio { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderNeroRadio { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NeroRadioDecoderStepReset = 0, + NeroRadioDecoderStepCheckPreambula, + NeroRadioDecoderStepSaveDuration, + NeroRadioDecoderStepCheckDuration, +} NeroRadioDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nero_radio_decoder = { + .alloc = subghz_protocol_decoder_nero_radio_alloc, + .free = subghz_protocol_decoder_nero_radio_free, + + .feed = subghz_protocol_decoder_nero_radio_feed, + .reset = subghz_protocol_decoder_nero_radio_reset, + + .get_hash_data = subghz_protocol_decoder_nero_radio_get_hash_data, + .serialize = subghz_protocol_decoder_nero_radio_serialize, + .deserialize = subghz_protocol_decoder_nero_radio_deserialize, + .get_string = subghz_protocol_decoder_nero_radio_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nero_radio_encoder = { + .alloc = subghz_protocol_encoder_nero_radio_alloc, + .free = subghz_protocol_encoder_nero_radio_free, + + .deserialize = subghz_protocol_encoder_nero_radio_deserialize, + .stop = subghz_protocol_encoder_nero_radio_stop, + .yield = subghz_protocol_encoder_nero_radio_yield, +}; + +const SubGhzProtocol subghz_protocol_nero_radio = { + .name = SUBGHZ_PROTOCOL_NERO_RADIO_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nero_radio_decoder, + .encoder = &subghz_protocol_nero_radio_encoder, +}; + +void* subghz_protocol_encoder_nero_radio_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderNeroRadio* instance = malloc(sizeof(SubGhzProtocolEncoderNeroRadio)); + + instance->base.protocol = &subghz_protocol_nero_radio; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_nero_radio_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNeroRadio* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool + subghz_protocol_encoder_nero_radio_get_upload(SubGhzProtocolEncoderNeroRadio* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = 49 * 2 + 2 + (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 0; i < 49; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short); + } + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short * 4); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short); + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short * 37); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_radio_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_radio_const.te_short * 37); + } + return true; +} + +bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNeroRadio* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_nero_radio_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nero_radio_stop(void* context) { + SubGhzProtocolEncoderNeroRadio* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_nero_radio_yield(void* context) { + SubGhzProtocolEncoderNeroRadio* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_nero_radio_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderNeroRadio* instance = malloc(sizeof(SubGhzProtocolDecoderNeroRadio)); + instance->base.protocol = &subghz_protocol_nero_radio; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nero_radio_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nero_radio_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + instance->decoder.parser_step = NeroRadioDecoderStepReset; +} + +void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + + switch(instance->decoder.parser_step) { + case NeroRadioDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta)) { + instance->decoder.parser_step = NeroRadioDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case NeroRadioDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short * 4) < + subghz_protocol_nero_radio_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else if( + DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) { + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short * 4) < + subghz_protocol_nero_radio_const.te_delta) { + // Found start bit + if(instance->header_count > 40) { + instance->decoder.parser_step = NeroRadioDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + break; + case NeroRadioDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = NeroRadioDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + break; + case NeroRadioDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_nero_radio_const.te_short * 10 + + subghz_protocol_nero_radio_const.te_delta * 2)) { + //Found stop bit + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_long) < + subghz_protocol_nero_radio_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + instance->decoder.parser_step = NeroRadioDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_nero_radio_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = NeroRadioDecoderStepReset; + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_long) < + subghz_protocol_nero_radio_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NeroRadioDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_radio_const.te_long) < + subghz_protocol_nero_radio_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_radio_const.te_short) < + subghz_protocol_nero_radio_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NeroRadioDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroRadioDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nero_radio_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_nero_radio_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderNeroRadio* instance = context; + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Yek:0x%lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + code_found_reverse_lo); +} diff --git a/lib/subghz/protocols/nero_radio.h b/lib/subghz/protocols/nero_radio.h new file mode 100644 index 00000000..4cd6c506 --- /dev/null +++ b/lib/subghz/protocols/nero_radio.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NERO_RADIO_NAME "Nero Radio" + +typedef struct SubGhzProtocolDecoderNeroRadio SubGhzProtocolDecoderNeroRadio; +typedef struct SubGhzProtocolEncoderNeroRadio SubGhzProtocolEncoderNeroRadio; + +extern const SubGhzProtocolDecoder subghz_protocol_nero_radio_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nero_radio_encoder; +extern const SubGhzProtocol subghz_protocol_nero_radio; + +void* subghz_protocol_encoder_nero_radio_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_nero_radio_free(void* context); +bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_nero_radio_stop(void* context); +LevelDuration subghz_protocol_encoder_nero_radio_yield(void* context); +void* subghz_protocol_decoder_nero_radio_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_nero_radio_free(void* context); +void subghz_protocol_decoder_nero_radio_reset(void* context); +void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); +bool subghz_protocol_decoder_nero_radio_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_nero_radio_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c new file mode 100644 index 00000000..408ef064 --- /dev/null +++ b/lib/subghz/protocols/nero_sketch.c @@ -0,0 +1,366 @@ +#include "nero_sketch.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define TAG "SubGhzProtocolNeroSketch" + +static const SubGhzBlockConst subghz_protocol_nero_sketch_const = { + .te_short = 330, + .te_long = 660, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct SubGhzProtocolDecoderNeroSketch { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderNeroSketch { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NeroSketchDecoderStepReset = 0, + NeroSketchDecoderStepCheckPreambula, + NeroSketchDecoderStepSaveDuration, + NeroSketchDecoderStepCheckDuration, +} NeroSketchDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nero_sketch_decoder = { + .alloc = subghz_protocol_decoder_nero_sketch_alloc, + .free = subghz_protocol_decoder_nero_sketch_free, + + .feed = subghz_protocol_decoder_nero_sketch_feed, + .reset = subghz_protocol_decoder_nero_sketch_reset, + + .get_hash_data = subghz_protocol_decoder_nero_sketch_get_hash_data, + .serialize = subghz_protocol_decoder_nero_sketch_serialize, + .deserialize = subghz_protocol_decoder_nero_sketch_deserialize, + .get_string = subghz_protocol_decoder_nero_sketch_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nero_sketch_encoder = { + .alloc = subghz_protocol_encoder_nero_sketch_alloc, + .free = subghz_protocol_encoder_nero_sketch_free, + + .deserialize = subghz_protocol_encoder_nero_sketch_deserialize, + .stop = subghz_protocol_encoder_nero_sketch_stop, + .yield = subghz_protocol_encoder_nero_sketch_yield, +}; + +const SubGhzProtocol subghz_protocol_nero_sketch = { + .name = SUBGHZ_PROTOCOL_NERO_SKETCH_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nero_sketch_decoder, + .encoder = &subghz_protocol_nero_sketch_encoder, +}; + +void* subghz_protocol_encoder_nero_sketch_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderNeroSketch* instance = malloc(sizeof(SubGhzProtocolEncoderNeroSketch)); + + instance->base.protocol = &subghz_protocol_nero_sketch; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_nero_sketch_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNeroSketch* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool + subghz_protocol_encoder_nero_sketch_get_upload(SubGhzProtocolEncoderNeroSketch* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = 47 * 2 + 2 + (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 0; i < 47; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + } + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short * 4); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_long); + } + } + + //Send stop bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nero_sketch_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nero_sketch_const.te_short); + + return true; +} + +bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNeroSketch* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_nero_sketch_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nero_sketch_stop(void* context) { + SubGhzProtocolEncoderNeroSketch* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_nero_sketch_yield(void* context) { + SubGhzProtocolEncoderNeroSketch* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_nero_sketch_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderNeroSketch* instance = malloc(sizeof(SubGhzProtocolDecoderNeroSketch)); + instance->base.protocol = &subghz_protocol_nero_sketch; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nero_sketch_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nero_sketch_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + instance->decoder.parser_step = NeroSketchDecoderStepReset; +} + +void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + + switch(instance->decoder.parser_step) { + case NeroSketchDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta)) { + instance->decoder.parser_step = NeroSketchDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case NeroSketchDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short * 4) < + subghz_protocol_nero_sketch_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else if( + DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) { + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_short * 4) < + subghz_protocol_nero_sketch_const.te_delta) { + // Found start bit + if(instance->header_count > 40) { + instance->decoder.parser_step = NeroSketchDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + break; + case NeroSketchDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_nero_sketch_const.te_short * 2 + + subghz_protocol_nero_sketch_const.te_delta * 2)) { + //Found stop bit + instance->decoder.parser_step = NeroSketchDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_nero_sketch_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = NeroSketchDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + break; + case NeroSketchDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_long) < + subghz_protocol_nero_sketch_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NeroSketchDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nero_sketch_const.te_long) < + subghz_protocol_nero_sketch_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nero_sketch_const.te_short) < + subghz_protocol_nero_sketch_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NeroSketchDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + } else { + instance->decoder.parser_step = NeroSketchDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nero_sketch_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_nero_sketch_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderNeroSketch* instance = context; + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Yek:0x%lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + code_found_reverse_lo); +} diff --git a/lib/subghz/protocols/nero_sketch.h b/lib/subghz/protocols/nero_sketch.h new file mode 100644 index 00000000..5042dff5 --- /dev/null +++ b/lib/subghz/protocols/nero_sketch.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NERO_SKETCH_NAME "Nero Sketch" + +typedef struct SubGhzProtocolDecoderNeroSketch SubGhzProtocolDecoderNeroSketch; +typedef struct SubGhzProtocolEncoderNeroSketch SubGhzProtocolEncoderNeroSketch; + +extern const SubGhzProtocolDecoder subghz_protocol_nero_sketch_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nero_sketch_encoder; +extern const SubGhzProtocol subghz_protocol_nero_sketch; + +void* subghz_protocol_encoder_nero_sketch_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_nero_sketch_free(void* context); +bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_nero_sketch_stop(void* context); +LevelDuration subghz_protocol_encoder_nero_sketch_yield(void* context); +void* subghz_protocol_decoder_nero_sketch_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_nero_sketch_free(void* context); +void subghz_protocol_decoder_nero_sketch_reset(void* context); +void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); +bool subghz_protocol_decoder_nero_sketch_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_nero_sketch_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c new file mode 100644 index 00000000..387c3477 --- /dev/null +++ b/lib/subghz/protocols/nice_flo.c @@ -0,0 +1,310 @@ +#include "nice_flo.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define TAG "SubGhzProtocolNiceFLO" + +static const SubGhzBlockConst subghz_protocol_nice_flo_const = { + .te_short = 700, + .te_long = 1400, + .te_delta = 200, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderNiceFlo { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderNiceFlo { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NiceFloDecoderStepReset = 0, + NiceFloDecoderStepFoundStartBit, + NiceFloDecoderStepSaveDuration, + NiceFloDecoderStepCheckDuration, +} NiceFloDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nice_flo_decoder = { + .alloc = subghz_protocol_decoder_nice_flo_alloc, + .free = subghz_protocol_decoder_nice_flo_free, + + .feed = subghz_protocol_decoder_nice_flo_feed, + .reset = subghz_protocol_decoder_nice_flo_reset, + + .get_hash_data = subghz_protocol_decoder_nice_flo_get_hash_data, + .serialize = subghz_protocol_decoder_nice_flo_serialize, + .deserialize = subghz_protocol_decoder_nice_flo_deserialize, + .get_string = subghz_protocol_decoder_nice_flo_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nice_flo_encoder = { + .alloc = subghz_protocol_encoder_nice_flo_alloc, + .free = subghz_protocol_encoder_nice_flo_free, + + .deserialize = subghz_protocol_encoder_nice_flo_deserialize, + .stop = subghz_protocol_encoder_nice_flo_stop, + .yield = subghz_protocol_encoder_nice_flo_yield, +}; + +const SubGhzProtocol subghz_protocol_nice_flo = { + .name = SUBGHZ_PROTOCOL_NICE_FLO_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nice_flo_decoder, + .encoder = &subghz_protocol_nice_flo_encoder, +}; + +void* subghz_protocol_encoder_nice_flo_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderNiceFlo* instance = malloc(sizeof(SubGhzProtocolEncoderNiceFlo)); + + instance->base.protocol = &subghz_protocol_nice_flo; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_nice_flo_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlo* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_encoder_nice_flo_get_upload(SubGhzProtocolEncoderNiceFlo* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flo_const.te_short * 36); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flo_const.te_short); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flo_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flo_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nice_flo_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nice_flo_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNiceFlo* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_nice_flo_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_nice_flo_stop(void* context) { + SubGhzProtocolEncoderNiceFlo* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_nice_flo_yield(void* context) { + SubGhzProtocolEncoderNiceFlo* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_nice_flo_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderNiceFlo* instance = malloc(sizeof(SubGhzProtocolDecoderNiceFlo)); + instance->base.protocol = &subghz_protocol_nice_flo; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nice_flo_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nice_flo_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + instance->decoder.parser_step = NiceFloDecoderStepReset; +} + +void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + + switch(instance->decoder.parser_step) { + case NiceFloDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short * 36) < + subghz_protocol_nice_flo_const.te_delta * 36)) { + //Found header Nice Flo + instance->decoder.parser_step = NiceFloDecoderStepFoundStartBit; + } + break; + case NiceFloDecoderStepFoundStartBit: + if(!level) { + break; + } else if( + DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short) < + subghz_protocol_nice_flo_const.te_delta) { + //Found start bit Nice Flo + instance->decoder.parser_step = NiceFloDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NiceFloDecoderStepReset; + } + break; + case NiceFloDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_nice_flo_const.te_short * 4)) { + instance->decoder.parser_step = NiceFloDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_nice_flo_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = NiceFloDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = NiceFloDecoderStepReset; + } + break; + case NiceFloDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nice_flo_const.te_short) < + subghz_protocol_nice_flo_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_long) < + subghz_protocol_nice_flo_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NiceFloDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nice_flo_const.te_long) < + subghz_protocol_nice_flo_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short) < + subghz_protocol_nice_flo_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NiceFloDecoderStepSaveDuration; + } else + instance->decoder.parser_step = NiceFloDecoderStepReset; + } else { + instance->decoder.parser_step = NiceFloDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nice_flo_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlo* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); +} diff --git a/lib/subghz/protocols/nice_flo.h b/lib/subghz/protocols/nice_flo.h new file mode 100644 index 00000000..785866a0 --- /dev/null +++ b/lib/subghz/protocols/nice_flo.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NICE_FLO_NAME "Nice FLO" + +typedef struct SubGhzProtocolDecoderNiceFlo SubGhzProtocolDecoderNiceFlo; +typedef struct SubGhzProtocolEncoderNiceFlo SubGhzProtocolEncoderNiceFlo; + +extern const SubGhzProtocolDecoder subghz_protocol_nice_flo_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nice_flo_encoder; +extern const SubGhzProtocol subghz_protocol_nice_flo; + +void* subghz_protocol_encoder_nice_flo_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_nice_flo_free(void* context); +bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_nice_flo_stop(void* context); +LevelDuration subghz_protocol_encoder_nice_flo_yield(void* context); +void* subghz_protocol_decoder_nice_flo_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_nice_flo_free(void* context); +void subghz_protocol_decoder_nice_flo_reset(void* context); +void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); +void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output); +bool subghz_protocol_decoder_nice_flo_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c new file mode 100644 index 00000000..21be4450 --- /dev/null +++ b/lib/subghz/protocols/nice_flor_s.c @@ -0,0 +1,366 @@ +#include "nice_flor_s.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" +/* + * https://phreakerclub.com/1615 + * https://phreakerclub.com/forum/showthread.php?t=2360 + * https://vrtp.ru/index.php?showtopic=27867 + */ + +#define TAG "SubGhzProtocoNiceFlorS" + +static const SubGhzBlockConst subghz_protocol_nice_flor_s_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 300, + .min_count_bit_for_found = 52, +}; + +struct SubGhzProtocolDecoderNiceFlorS { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + const char* nice_flor_s_rainbow_table_file_name; +}; + +struct SubGhzProtocolEncoderNiceFlorS { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + NiceFlorSDecoderStepReset = 0, + NiceFlorSDecoderStepCheckHeader, + NiceFlorSDecoderStepFoundHeader, + NiceFlorSDecoderStepSaveDuration, + NiceFlorSDecoderStepCheckDuration, +} NiceFlorSDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder = { + .alloc = subghz_protocol_decoder_nice_flor_s_alloc, + .free = subghz_protocol_decoder_nice_flor_s_free, + + .feed = subghz_protocol_decoder_nice_flor_s_feed, + .reset = subghz_protocol_decoder_nice_flor_s_reset, + + .get_hash_data = subghz_protocol_decoder_nice_flor_s_get_hash_data, + .serialize = subghz_protocol_decoder_nice_flor_s_serialize, + .deserialize = subghz_protocol_decoder_nice_flor_s_deserialize, + .get_string = subghz_protocol_decoder_nice_flor_s_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_nice_flor_s_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_nice_flor_s = { + .name = SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_nice_flor_s_decoder, + .encoder = &subghz_protocol_nice_flor_s_encoder, +}; + +/** Read bytes from rainbow table + * + * @param instance - SubGhzProtocolNiceFlorS* instance + * @param address - address byte + * @return byte data + */ +static uint8_t + subghz_protocol_nice_flor_s_get_byte_in_file(const char* file_name, uint32_t address) { + if(!file_name) return 0; + + uint8_t buffer[1] = {0}; + if(subghz_keystore_raw_get_data(file_name, address, buffer, sizeof(uint8_t))) { + return buffer[0]; + } else { + return 0; + } +} + +static inline void subghz_protocol_decoder_nice_flor_s_magic_xor(uint8_t* p, uint8_t k) { + for(uint8_t i = 1; i < 6; i++) { + p[i] ^= k; + } +} + +uint64_t subghz_protocol_nice_flor_s_encrypt(uint64_t data, const char* file_name) { + uint8_t* p = (uint8_t*)&data; + + uint8_t k = 0; + for(uint8_t y = 0; y < 2; y++) { + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] & 0x1f); + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0xe0; + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] >> 3) + 0x25; + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0x7; + if(y == 0) { + k = p[0]; + p[0] = p[1]; + p[1] = k; + } + } + + p[5] = ~p[5] & 0x0f; + k = ~p[4]; + p[4] = ~p[0]; + p[0] = ~p[2]; + p[2] = k; + k = ~p[3]; + p[3] = ~p[1]; + p[1] = k; + + return data; +} + +static uint64_t + subghz_protocol_nice_flor_s_decrypt(SubGhzBlockGeneric* instance, const char* file_name) { + furi_assert(instance); + uint64_t data = instance->data; + uint8_t* p = (uint8_t*)&data; + + uint8_t k = 0; + + k = ~p[4]; + p[5] = ~p[5]; + p[4] = ~p[2]; + p[2] = ~p[0]; + p[0] = k; + k = ~p[3]; + p[3] = ~p[1]; + p[1] = k; + + for(uint8_t y = 0; y < 2; y++) { + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] >> 3) + 0x25; + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0x7; + k = subghz_protocol_nice_flor_s_get_byte_in_file(file_name, p[0] & 0x1f); + subghz_protocol_decoder_nice_flor_s_magic_xor(p, k); + + p[5] &= 0x0f; + p[0] ^= k & 0xe0; + + if(y == 0) { + k = p[0]; + p[0] = p[1]; + p[1] = k; + } + } + return data; +} + +void* subghz_protocol_decoder_nice_flor_s_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderNiceFlorS* instance = malloc(sizeof(SubGhzProtocolDecoderNiceFlorS)); + instance->base.protocol = &subghz_protocol_nice_flor_s; + instance->generic.protocol_name = instance->base.protocol->name; + instance->nice_flor_s_rainbow_table_file_name = + subghz_environment_get_nice_flor_s_rainbow_table_file_name(environment); + if(instance->nice_flor_s_rainbow_table_file_name) { + FURI_LOG_I( + TAG, "Loading rainbow table from %s", instance->nice_flor_s_rainbow_table_file_name); + } + return instance; +} + +void subghz_protocol_decoder_nice_flor_s_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + instance->nice_flor_s_rainbow_table_file_name = NULL; + free(instance); +} + +void subghz_protocol_decoder_nice_flor_s_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + instance->decoder.parser_step = NiceFlorSDecoderStepReset; +} + +void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + + switch(instance->decoder.parser_step) { + case NiceFlorSDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 38) < + subghz_protocol_nice_flor_s_const.te_delta * 38)) { + //Found start header Nice Flor-S + instance->decoder.parser_step = NiceFlorSDecoderStepCheckHeader; + } + break; + case NiceFlorSDecoderStepCheckHeader: + if((level) && (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 3) < + subghz_protocol_nice_flor_s_const.te_delta * 3)) { + //Found next header Nice Flor-S + instance->decoder.parser_step = NiceFlorSDecoderStepFoundHeader; + } else { + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } + break; + case NiceFlorSDecoderStepFoundHeader: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 3) < + subghz_protocol_nice_flor_s_const.te_delta * 3)) { + //Found header Nice Flor-S + instance->decoder.parser_step = NiceFlorSDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } + break; + case NiceFlorSDecoderStepSaveDuration: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short * 3) < + subghz_protocol_nice_flor_s_const.te_delta) { + //Found STOP bit + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } else { + //save interval + instance->decoder.te_last = duration; + instance->decoder.parser_step = NiceFlorSDecoderStepCheckDuration; + } + } + break; + case NiceFlorSDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nice_flor_s_const.te_short) < + subghz_protocol_nice_flor_s_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_long) < + subghz_protocol_nice_flor_s_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = NiceFlorSDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nice_flor_s_const.te_long) < + subghz_protocol_nice_flor_s_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nice_flor_s_const.te_short) < + subghz_protocol_nice_flor_s_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = NiceFlorSDecoderStepSaveDuration; + } else + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } else { + instance->decoder.parser_step = NiceFlorSDecoderStepReset; + } + break; + } +} + +/** Decrypt protocol Nice Flor S + * + * @param instance - SubGhzProtocolNiceFlorS* instance + */ +static void subghz_protocol_nice_flor_s_remote_controller( + SubGhzBlockGeneric* instance, + const char* file_name) { + /* + * Packet format Nice Flor-s: START-P0-P1-P2-P3-P4-P5-P6-P7-STOP + * P0 (4-bit) - button positional code - 1:0x1, 2:0x2, 3:0x4, 4:0x8; + * P1 (4-bit) - batch repetition number, calculated by the formula: + * P1 = 0xF ^ P0 ^ n; where n changes from 1 to 15, then 0, and then in a circle + * key 1: {0xE,0xF,0xC,0xD,0xA,0xB,0x8,0x9,0x6,0x7,0x4,0x5,0x2,0x3,0x0,0x1}; + * key 2: {0xD,0xC,0xF,0xE,0x9,0x8,0xB,0xA,0x5,0x4,0x7,0x6,0x1,0x0,0x3,0x2}; + * key 3: {0xB,0xA,0x9,0x8,0xF,0xE,0xD,0xC,0x3,0x2,0x1,0x0,0x7,0x6,0x5,0x4}; + * key 4: {0x7,0x6,0x5,0x4,0x3,0x2,0x1,0x0,0xF,0xE,0xD,0xC,0xB,0xA,0x9,0x8}; + * P2 (4-bit) - part of the serial number, P2 = (K ^ S3) & 0xF; + * P3 (byte) - the major part of the encrypted index + * P4 (byte) - the low-order part of the encrypted index + * P5 (byte) - part of the serial number, P5 = K ^ S2; + * P6 (byte) - part of the serial number, P6 = K ^ S1; + * P7 (byte) - part of the serial number, P7 = K ^ S0; + * K (byte) - depends on P3 and P4, K = Fk(P3, P4); + * S3,S2,S1,S0 - serial number of the console 28 bit. + * + * data => 0x1c5783607f7b3 key serial cnt + * decrypt => 0x10436c6820444 => 0x1 0436c682 0444 + * + */ + if(!file_name) { + instance->cnt = 0; + instance->serial = 0; + instance->btn = 0; + } else { + uint64_t decrypt = subghz_protocol_nice_flor_s_decrypt(instance, file_name); + instance->cnt = decrypt & 0xFFFF; + instance->serial = (decrypt >> 16) & 0xFFFFFFF; + instance->btn = (decrypt >> 48) & 0xF; + } +} + +uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_nice_flor_s_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_nice_flor_s_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderNiceFlorS* instance = context; + + subghz_protocol_nice_flor_s_remote_controller( + &instance->generic, instance->nice_flor_s_rainbow_table_file_name); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:%05lX\r\n" + "Cnt:%04X Btn:%02lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h new file mode 100644 index 00000000..57ff50bc --- /dev/null +++ b/lib/subghz/protocols/nice_flor_s.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME "Nice FloR-S" + +typedef struct SubGhzProtocolDecoderNiceFlorS SubGhzProtocolDecoderNiceFlorS; +typedef struct SubGhzProtocolEncoderNiceFlorS SubGhzProtocolEncoderNiceFlorS; + +extern const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nice_flor_s_encoder; +extern const SubGhzProtocol subghz_protocol_nice_flor_s; + +void* subghz_protocol_decoder_nice_flor_s_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_nice_flor_s_free(void* context); +void subghz_protocol_decoder_nice_flor_s_reset(void* context); +void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); +bool subghz_protocol_decoder_nice_flor_s_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_nice_flor_s_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c new file mode 100644 index 00000000..06d2a8d2 --- /dev/null +++ b/lib/subghz/protocols/princeton.c @@ -0,0 +1,351 @@ +#include "princeton.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define TAG "SubGhzProtocolCAME" + +static const SubGhzBlockConst subghz_protocol_princeton_const = { + .te_short = 400, + .te_long = 1200, + .te_delta = 250, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderPrinceton { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +struct SubGhzProtocolEncoderPrinceton { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + PrincetonDecoderStepReset = 0, + PrincetonDecoderStepSaveDuration, + PrincetonDecoderStepCheckDuration, +} PrincetonDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_princeton_decoder = { + .alloc = subghz_protocol_decoder_princeton_alloc, + .free = subghz_protocol_decoder_princeton_free, + + .feed = subghz_protocol_decoder_princeton_feed, + .reset = subghz_protocol_decoder_princeton_reset, + + .get_hash_data = subghz_protocol_decoder_princeton_get_hash_data, + .serialize = subghz_protocol_decoder_princeton_serialize, + .deserialize = subghz_protocol_decoder_princeton_deserialize, + .get_string = subghz_protocol_decoder_princeton_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_princeton_encoder = { + .alloc = subghz_protocol_encoder_princeton_alloc, + .free = subghz_protocol_encoder_princeton_free, + + .deserialize = subghz_protocol_encoder_princeton_deserialize, + .stop = subghz_protocol_encoder_princeton_stop, + .yield = subghz_protocol_encoder_princeton_yield, +}; + +const SubGhzProtocol subghz_protocol_princeton = { + .name = SUBGHZ_PROTOCOL_PRINCETON_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_princeton_decoder, + .encoder = &subghz_protocol_princeton_encoder, +}; + +void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderPrinceton* instance = malloc(sizeof(SubGhzProtocolEncoderPrinceton)); + + instance->base.protocol = &subghz_protocol_princeton; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_princeton_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderPrinceton* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool + subghz_protocol_encoder_princeton_get_upload(SubGhzProtocolEncoderPrinceton* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + } + } + + //Send Stop bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send PT_GUARD + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 30); + + return true; +} + +bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderPrinceton* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_princeton_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_princeton_stop(void* context) { + SubGhzProtocolEncoderPrinceton* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_princeton_yield(void* context) { + SubGhzProtocolEncoderPrinceton* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_princeton_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderPrinceton* instance = malloc(sizeof(SubGhzProtocolDecoderPrinceton)); + instance->base.protocol = &subghz_protocol_princeton; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_princeton_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + free(instance); +} + +void subghz_protocol_decoder_princeton_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + instance->decoder.parser_step = PrincetonDecoderStepReset; +} + +void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + + switch(instance->decoder.parser_step) { + case PrincetonDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_princeton_const.te_short * 36) < + subghz_protocol_princeton_const.te_delta * 36)) { + //Found Preambula + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case PrincetonDecoderStepSaveDuration: + //save duration + if(level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = PrincetonDecoderStepCheckDuration; + } + break; + case PrincetonDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_princeton_const.te_short * 10 + + subghz_protocol_princeton_const.te_delta)) { + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + if(instance->decoder.decode_count_bit == + subghz_protocol_princeton_const.min_count_bit_for_found) { + instance->te /= (instance->decoder.decode_count_bit * 4 + 1); + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->generic.serial = instance->decoder.decode_data >> 4; + instance->generic.btn = (uint8_t)instance->decoder.decode_data & 0x00000F; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_princeton_const.te_short) < + subghz_protocol_princeton_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_princeton_const.te_long) < + subghz_protocol_princeton_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_princeton_const.te_long) < + subghz_protocol_princeton_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_princeton_const.te_short) < + subghz_protocol_princeton_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = PrincetonDecoderStepReset; + } + } else { + instance->decoder.parser_step = PrincetonDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_princeton_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_princeton_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderPrinceton* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n" + "Sn:0x%05lX BTN:%02X\r\n" + "Te:%dus\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo, + instance->generic.serial, + instance->generic.btn, + instance->te); +} diff --git a/lib/subghz/protocols/princeton.h b/lib/subghz/protocols/princeton.h new file mode 100644 index 00000000..b5ac9849 --- /dev/null +++ b/lib/subghz/protocols/princeton.h @@ -0,0 +1,30 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_PRINCETON_NAME "Princeton" + +typedef struct SubGhzProtocolDecoderPrinceton SubGhzProtocolDecoderPrinceton; +typedef struct SubGhzProtocolEncoderPrinceton SubGhzProtocolEncoderPrinceton; + +extern const SubGhzProtocolDecoder subghz_protocol_princeton_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_princeton_encoder; +extern const SubGhzProtocol subghz_protocol_princeton; + +void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_princeton_free(void* context); +bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_princeton_stop(void* context); +LevelDuration subghz_protocol_encoder_princeton_yield(void* context); +void* subghz_protocol_decoder_princeton_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_princeton_free(void* context); +void subghz_protocol_decoder_princeton_reset(void* context); +void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context); +bool subghz_protocol_decoder_princeton_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_princeton_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/princeton_for_testing.c b/lib/subghz/protocols/princeton_for_testing.c new file mode 100644 index 00000000..0aca55e4 --- /dev/null +++ b/lib/subghz/protocols/princeton_for_testing.c @@ -0,0 +1,287 @@ +#include "princeton_for_testing.h" + +#include "furi_hal.h" +#include "../blocks/math.h" +/* + * Help + * https://phreakerclub.com/447 + * + */ + +#define SUBGHZ_PT_SHORT 300 +#define SUBGHZ_PT_LONG (SUBGHZ_PT_SHORT * 3) +#define SUBGHZ_PT_GUARD (SUBGHZ_PT_SHORT * 30) +#define SUBGHZ_PT_COUNT_KEY_433 9 +#define SUBGHZ_PT_TIMEOUT_433 900 +#define SUBGHZ_PT_COUNT_KEY_868 9 +#define SUBGHZ_PT_TIMEOUT_868 14000 + +#define TAG "SubGhzProtocolPrinceton" + +struct SubGhzEncoderPrinceton { + uint32_t key; + uint16_t te; + size_t repeat; + size_t front; + size_t count_key; + size_t count_key_package; + uint32_t time_high; + uint32_t time_low; + uint32_t timeout; + uint32_t time_stop; +}; + +typedef enum { + PrincetonDecoderStepReset = 0, + PrincetonDecoderStepSaveDuration, + PrincetonDecoderStepCheckDuration, +} PrincetonDecoderStep; + +SubGhzEncoderPrinceton* subghz_encoder_princeton_for_testing_alloc() { + SubGhzEncoderPrinceton* instance = malloc(sizeof(SubGhzEncoderPrinceton)); + return instance; +} + +void subghz_encoder_princeton_for_testing_free(SubGhzEncoderPrinceton* instance) { + furi_assert(instance); + free(instance); +} + +void subghz_encoder_princeton_for_testing_stop( + SubGhzEncoderPrinceton* instance, + uint32_t time_stop) { + instance->time_stop = time_stop; +} + +void subghz_encoder_princeton_for_testing_set( + SubGhzEncoderPrinceton* instance, + uint32_t key, + size_t repeat, + uint32_t frequency) { + furi_assert(instance); + instance->te = SUBGHZ_PT_SHORT; + instance->key = key; + instance->repeat = repeat + 1; + instance->front = 48; + instance->time_high = 0; + instance->time_low = 0; + if(frequency < 700000000) { + instance->count_key_package = SUBGHZ_PT_COUNT_KEY_433; + instance->timeout = SUBGHZ_PT_TIMEOUT_433; + } else { + instance->count_key_package = SUBGHZ_PT_COUNT_KEY_868; + instance->timeout = SUBGHZ_PT_TIMEOUT_868; + } + + instance->count_key = instance->count_key_package + 3; + + if((millis() - instance->time_stop) < instance->timeout) { + instance->time_stop = (instance->timeout - (millis() - instance->time_stop)) * 1000; + } else { + instance->time_stop = 0; + } +} + +size_t subghz_encoder_princeton_for_testing_get_repeat_left(SubGhzEncoderPrinceton* instance) { + furi_assert(instance); + return instance->repeat; +} + +void subghz_encoder_princeton_for_testing_print_log(void* context) { + SubGhzEncoderPrinceton* instance = context; + float duty_cycle = + ((float)instance->time_high / (instance->time_high + instance->time_low)) * 100; + FURI_LOG_I( + TAG "Encoder", + "Radio tx_time=%dus ON=%dus, OFF=%dus, DutyCycle=%d,%d%%", + instance->time_high + instance->time_low, + instance->time_high, + instance->time_low, + (uint32_t)duty_cycle, + (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100)); +} + +LevelDuration subghz_encoder_princeton_for_testing_yield(void* context) { + SubGhzEncoderPrinceton* instance = context; + if(instance->repeat == 0) { + subghz_encoder_princeton_for_testing_print_log(instance); + return level_duration_reset(); + } + + size_t bit = instance->front / 2; + bool level = !(instance->front % 2); + + LevelDuration ret; + if(bit < 24) { + uint8_t byte = bit / 8; + uint8_t bit_in_byte = bit % 8; + bool value = (((uint8_t*)&instance->key)[2 - byte] >> (7 - bit_in_byte)) & 1; + if(value) { + ret = level_duration_make(level, level ? instance->te * 3 : instance->te); + if(level) + instance->time_high += instance->te * 3; + else + instance->time_low += instance->te; + } else { + ret = level_duration_make(level, level ? instance->te : instance->te * 3); + if(level) + instance->time_high += instance->te; + else + instance->time_low += instance->te * 3; + } + } else { + if(instance->time_stop) { + ret = level_duration_make(level, level ? instance->te : instance->time_stop); + if(level) + instance->time_high += instance->te; + else { + instance->time_low += instance->time_stop; + instance->time_stop = 0; + instance->front = 47; + } + } else { + if(--instance->count_key != 0) { + ret = level_duration_make(level, level ? instance->te : instance->te * 30); + if(level) + instance->time_high += instance->te; + else + instance->time_low += instance->te * 30; + } else { + instance->count_key = instance->count_key_package + 2; + instance->front = 48; + ret = level_duration_make(level, level ? instance->te : instance->timeout * 1000); + if(level) + instance->time_high += instance->te; + else + instance->time_low += instance->timeout * 1000; + } + } + } + + instance->front++; + if(instance->front == 50) { + instance->repeat--; + instance->front = 0; + } + return ret; +} + +struct SubGhzDecoderPrinceton { + const char* name; + uint16_t te_long; + uint16_t te_short; + uint16_t te_delta; + uint8_t code_count_bit; + uint8_t code_last_count_bit; + uint64_t code_found; + uint64_t code_last_found; + uint8_t code_min_count_bit_for_found; + uint8_t btn; + uint32_t te_last; + uint32_t serial; + uint32_t parser_step; + uint16_t cnt; + uint32_t te; + + SubGhzDecoderPrincetonCallback callback; + void* context; +}; + +SubGhzDecoderPrinceton* subghz_decoder_princeton_for_testing_alloc(void) { + SubGhzDecoderPrinceton* instance = malloc(sizeof(SubGhzDecoderPrinceton)); + + instance->te = SUBGHZ_PT_SHORT; + instance->name = "Princeton"; + instance->code_min_count_bit_for_found = 24; + instance->te_short = 400; + instance->te_long = 1200; + instance->te_delta = 250; + return instance; +} + +void subghz_decoder_princeton_for_testing_free(SubGhzDecoderPrinceton* instance) { + furi_assert(instance); + free(instance); +} + +void subghz_decoder_princeton_for_testing_set_callback( + SubGhzDecoderPrinceton* instance, + SubGhzDecoderPrincetonCallback callback, + void* context) { + instance->callback = callback; + instance->context = context; +} + +void subghz_decoder_princeton_for_testing_reset(SubGhzDecoderPrinceton* instance) { + instance->parser_step = PrincetonDecoderStepReset; +} + +static void + subghz_decoder_princeton_for_testing_add_bit(SubGhzDecoderPrinceton* instance, uint8_t bit) { + instance->code_found = instance->code_found << 1 | bit; + instance->code_count_bit++; +} + +void subghz_decoder_princeton_for_testing_parse( + SubGhzDecoderPrinceton* instance, + bool level, + uint32_t duration) { + switch(instance->parser_step) { + case PrincetonDecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, instance->te_short * 36) < instance->te_delta * 36)) { + //Found Preambula + instance->parser_step = PrincetonDecoderStepSaveDuration; + instance->code_found = 0; + instance->code_count_bit = 0; + instance->te = 0; + } + break; + case PrincetonDecoderStepSaveDuration: + //save duration + if(level) { + instance->te_last = duration; + instance->te += duration; + instance->parser_step = PrincetonDecoderStepCheckDuration; + } + break; + case PrincetonDecoderStepCheckDuration: + if(!level) { + if(duration >= (instance->te_short * 10 + instance->te_delta)) { + instance->parser_step = PrincetonDecoderStepSaveDuration; + if(instance->code_count_bit == instance->code_min_count_bit_for_found) { + instance->te /= (instance->code_count_bit * 4 + 1); + + instance->code_last_found = instance->code_found; + instance->code_last_count_bit = instance->code_count_bit; + instance->serial = instance->code_found >> 4; + instance->btn = (uint8_t)instance->code_found & 0x00000F; + + if(instance->callback) instance->callback(instance, instance->context); + } + instance->code_found = 0; + instance->code_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->te_last, instance->te_short) < instance->te_delta) && + (DURATION_DIFF(duration, instance->te_long) < instance->te_delta * 3)) { + subghz_decoder_princeton_for_testing_add_bit(instance, 0); + instance->parser_step = PrincetonDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->te_last, instance->te_long) < instance->te_delta * 3) && + (DURATION_DIFF(duration, instance->te_short) < instance->te_delta)) { + subghz_decoder_princeton_for_testing_add_bit(instance, 1); + instance->parser_step = PrincetonDecoderStepSaveDuration; + } else { + instance->parser_step = PrincetonDecoderStepReset; + } + } else { + instance->parser_step = PrincetonDecoderStepReset; + } + break; + } +} diff --git a/lib/subghz/protocols/princeton_for_testing.h b/lib/subghz/protocols/princeton_for_testing.h new file mode 100644 index 00000000..307a73c7 --- /dev/null +++ b/lib/subghz/protocols/princeton_for_testing.h @@ -0,0 +1,85 @@ +#pragma once + +#include "base.h" + +/** SubGhzDecoderPrinceton anonymous type */ +typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton; +/** SubGhzEncoderPrinceton anonymous type */ +typedef struct SubGhzEncoderPrinceton SubGhzEncoderPrinceton; + +typedef void (*SubGhzDecoderPrincetonCallback)(SubGhzDecoderPrinceton* parser, void* context); + +/** Allocate SubGhzEncoderPrinceton + * @return pointer to SubGhzEncoderPrinceton instance + */ +SubGhzEncoderPrinceton* subghz_encoder_princeton_for_testing_alloc(); + +/** Free SubGhzEncoderPrinceton instance + * @param instance - SubGhzEncoderPrinceton instance + */ +void subghz_encoder_princeton_for_testing_free(SubGhzEncoderPrinceton* instance); + +void subghz_encoder_princeton_for_testing_stop( + SubGhzEncoderPrinceton* instance, + uint32_t time_stop); + +/** Set new encoder params + * @param instance - SubGhzEncoderPrinceton instance + * @param key - 24bit key + * @param repeat - how many times to repeat + * @param frequency - frequency + */ +void subghz_encoder_princeton_for_testing_set( + SubGhzEncoderPrinceton* instance, + uint32_t key, + size_t repeat, + uint32_t frequency); + +/** Get repeat count left + * @param instance - SubGhzEncoderPrinceton instance + * @return repeat count left + */ +size_t subghz_encoder_princeton_for_testing_get_repeat_left(SubGhzEncoderPrinceton* instance); + +/** Print encoder log + * @param instance - SubGhzEncoderPrinceton instance + */ +void subghz_encoder_princeton_for_testing_print_log(void* context); + +/** Get level duration + * @param instance - SubGhzEncoderPrinceton instance + * @return level duration + */ +LevelDuration subghz_encoder_princeton_for_testing_yield(void* context); + +/** Allocate SubGhzDecoderPrinceton + * + * @return SubGhzDecoderPrinceton* + */ +SubGhzDecoderPrinceton* subghz_decoder_princeton_for_testing_alloc(); + +/** Free SubGhzDecoderPrinceton + * + * @param instance + */ +void subghz_decoder_princeton_for_testing_free(SubGhzDecoderPrinceton* instance); + +void subghz_decoder_princeton_for_testing_set_callback( + SubGhzDecoderPrinceton* instance, + SubGhzDecoderPrincetonCallback callback, + void* context); + +/** Reset internal state + * @param instance - SubGhzDecoderPrinceton instance + */ +void subghz_decoder_princeton_for_testing_reset(SubGhzDecoderPrinceton* instance); + +/** Parse accepted duration + * + * @param instance - SubGhzDecoderPrinceton instance + * @param data - LevelDuration level_duration + */ +void subghz_decoder_princeton_for_testing_parse( + SubGhzDecoderPrinceton* instance, + bool level, + uint32_t duration); diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c new file mode 100644 index 00000000..6c6675fe --- /dev/null +++ b/lib/subghz/protocols/raw.c @@ -0,0 +1,347 @@ +#include "raw.h" +#include +#include "../subghz_file_encoder_worker.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#include +#include + +#define TAG "SubGhzProtocolRAW" +#define SUBGHZ_DOWNLOAD_MAX_SIZE 512 + +static const SubGhzBlockConst subghz_protocol_raw_const = { + .te_short = 80, + .te_long = 32700, + .te_delta = 0, + .min_count_bit_for_found = 0, +}; + +struct SubGhzProtocolDecoderRAW { + SubGhzProtocolDecoderBase base; + + int32_t* upload_raw; + uint16_t ind_write; + Storage* storage; + FlipperFormat* flipper_file; + uint32_t file_is_open; + string_t file_name; + size_t sample_write; + bool last_level; +}; + +struct SubGhzProtocolEncoderRAW { + SubGhzProtocolEncoderBase base; + + bool is_runing; + string_t file_name; + SubGhzFileEncoderWorker* file_worker_encoder; +}; + +typedef enum { + RAWFileIsOpenClose = 0, + RAWFileIsOpenWrite, + RAWFileIsOpenRead, +} RAWFilIsOpen; + +const SubGhzProtocolDecoder subghz_protocol_raw_decoder = { + .alloc = subghz_protocol_decoder_raw_alloc, + .free = subghz_protocol_decoder_raw_free, + + .feed = subghz_protocol_decoder_raw_feed, + .reset = subghz_protocol_decoder_raw_reset, + + .get_hash_data = NULL, + .serialize = NULL, + .get_string = subghz_protocol_decoder_raw_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_raw_encoder = { + .alloc = subghz_protocol_encoder_raw_alloc, + .free = subghz_protocol_encoder_raw_free, + + .deserialize = subghz_protocol_encoder_raw_deserialize, + .stop = subghz_protocol_encoder_raw_stop, + .yield = subghz_protocol_encoder_raw_yield, +}; + +const SubGhzProtocol subghz_protocol_raw = { + .name = SUBGHZ_PROTOCOL_RAW_NAME, + .type = SubGhzProtocolTypeRAW, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_RAW | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_raw_decoder, + .encoder = &subghz_protocol_raw_encoder, +}; + +bool subghz_protocol_raw_save_to_file_init( + SubGhzProtocolDecoderRAW* instance, + const char* dev_name, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(instance); + + instance->storage = furi_record_open("storage"); + instance->flipper_file = flipper_format_file_alloc(instance->storage); + + string_t temp_str; + string_init(temp_str); + bool init = false; + + do { + // Create subghz folder directory if necessary + if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { + break; + } + // Create saved directory if necessary + if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { + break; + } + + string_set(instance->file_name, dev_name); + // First remove subghz device file if it was saved + string_printf(temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); + + if(!storage_simply_remove(instance->storage, string_get_cstr(temp_str))) { + break; + } + + // Open file + if(!flipper_format_file_open_always(instance->flipper_file, string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", temp_str); + break; + } + + if(!flipper_format_write_header_cstr( + instance->flipper_file, SUBGHZ_RAW_FILE_TYPE, SUBGHZ_RAW_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(instance->flipper_file, "Frequency", &frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + if(!subghz_block_generic_get_preset_name(preset, temp_str)) { + break; + } + if(!flipper_format_write_string_cstr( + instance->flipper_file, "Preset", string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + + if(!flipper_format_write_string_cstr( + instance->flipper_file, "Protocol", instance->base.protocol->name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + instance->upload_raw = malloc(SUBGHZ_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); + instance->file_is_open = RAWFileIsOpenWrite; + instance->sample_write = 0; + init = true; + } while(0); + + string_clear(temp_str); + + return init; +} + +static bool subghz_protocol_raw_save_to_file_write(SubGhzProtocolDecoderRAW* instance) { + furi_assert(instance); + + bool is_write = false; + if(instance->file_is_open == RAWFileIsOpenWrite) { + if(!flipper_format_write_int32( + instance->flipper_file, "RAW_Data", instance->upload_raw, instance->ind_write)) { + FURI_LOG_E(TAG, "Unable to add RAW_Data"); + } else { + instance->sample_write += instance->ind_write; + instance->ind_write = 0; + is_write = true; + } + } + return is_write; +} + +void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance) { + furi_assert(instance); + + if(instance->file_is_open == RAWFileIsOpenWrite && instance->ind_write) + subghz_protocol_raw_save_to_file_write(instance); + if(instance->file_is_open != RAWFileIsOpenClose) { + free(instance->upload_raw); + instance->upload_raw = NULL; + flipper_format_file_close(instance->flipper_file); + flipper_format_free(instance->flipper_file); + furi_record_close("storage"); + } + + instance->file_is_open = RAWFileIsOpenClose; +} + +size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance) { + return instance->sample_write + instance->ind_write; +} + +void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderRAW* instance = malloc(sizeof(SubGhzProtocolDecoderRAW)); + instance->base.protocol = &subghz_protocol_raw; + instance->upload_raw = NULL; + instance->ind_write = 0; + instance->last_level = false; + instance->file_is_open = RAWFileIsOpenClose; + string_init(instance->file_name); + + return instance; +} + +void subghz_protocol_decoder_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRAW* instance = context; + string_clear(instance->file_name); + free(instance); +} + +void subghz_protocol_decoder_raw_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRAW* instance = context; + instance->ind_write = 0; + instance->last_level = false; +} + +void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRAW* instance = context; + + if(instance->upload_raw != NULL) { + if(duration > subghz_protocol_raw_const.te_short) { + if(duration > subghz_protocol_raw_const.te_long) + duration = subghz_protocol_raw_const.te_long; + if(instance->last_level != level) { + instance->last_level = (level ? true : false); + instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); + } + } + + if(instance->ind_write == SUBGHZ_DOWNLOAD_MAX_SIZE) { + subghz_protocol_raw_save_to_file_write(instance); + } + } +} + +void subghz_protocol_decoder_raw_get_string(void* context, string_t output) { + furi_assert(context); + //SubGhzProtocolDecoderRAW* instance = context; + //ToDo no use + string_cat_printf(output, "RAW Date"); +} + +void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderRAW* instance = malloc(sizeof(SubGhzProtocolEncoderRAW)); + + instance->base.protocol = &subghz_protocol_raw; + string_init(instance->file_name); + instance->is_runing = false; + return instance; +} + +void subghz_protocol_encoder_raw_stop(void* context) { + SubGhzProtocolEncoderRAW* instance = context; + instance->is_runing = false; + if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { + subghz_file_encoder_worker_stop(instance->file_worker_encoder); + subghz_file_encoder_worker_free(instance->file_worker_encoder); + } +} + +void subghz_protocol_encoder_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRAW* instance = context; + subghz_protocol_encoder_raw_stop(instance); + string_clear(instance->file_name); + free(instance); +} + +void subghz_protocol_raw_file_encoder_worker_set_callback_end( + SubGhzProtocolEncoderRAW* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback_end, + void* context_end) { + furi_assert(instance); + furi_assert(callback_end); + subghz_file_encoder_worker_callback_end( + instance->file_worker_encoder, callback_end, context_end); +} + +static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* instance) { + furi_assert(instance); + + instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); + if(subghz_file_encoder_worker_start( + instance->file_worker_encoder, string_get_cstr(instance->file_name))) { + //the worker needs a file in order to open and read part of the file + osDelay(100); + instance->is_runing = true; + } else { + subghz_protocol_encoder_raw_stop(instance); + } + return instance->is_runing; +} + +void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_name) { + string_t temp_str; + string_init(temp_str); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", "RAW")) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + string_printf(temp_str, "%s/%s%s", SUBGHZ_APP_FOLDER, file_name, SUBGHZ_APP_EXTENSION); + + if(!flipper_format_write_string_cstr( + flipper_format, "File_name", string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add File_name"); + break; + } + } while(false); + string_clear(temp_str); +} + +bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRAW* instance = context; + bool res = false; + string_t temp_str; + string_init(temp_str); + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(flipper_format, "File_name", temp_str)) { + FURI_LOG_E(TAG, "Missing File_name"); + break; + } + string_set(instance->file_name, temp_str); + + res = subghz_protocol_encoder_raw_worker_init(instance); + } while(false); + string_clear(temp_str); + return res; +} + +LevelDuration subghz_protocol_encoder_raw_yield(void* context) { + SubGhzProtocolEncoderRAW* instance = context; + + if(!instance->is_runing) return level_duration_reset(); + return subghz_file_encoder_worker_get_level_duration(instance->file_worker_encoder); +} diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h new file mode 100644 index 00000000..cb8dc237 --- /dev/null +++ b/lib/subghz/protocols/raw.h @@ -0,0 +1,40 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_RAW_NAME "RAW" + +typedef void (*SubGhzProtocolEncoderRAWCallbackEnd)(void* context); + +typedef struct SubGhzProtocolDecoderRAW SubGhzProtocolDecoderRAW; +typedef struct SubGhzProtocolEncoderRAW SubGhzProtocolEncoderRAW; + +extern const SubGhzProtocolDecoder subghz_protocol_raw_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_raw_encoder; +extern const SubGhzProtocol subghz_protocol_raw; + +bool subghz_protocol_raw_save_to_file_init( + SubGhzProtocolDecoderRAW* instance, + const char* dev_name, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance); +size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance); +void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_raw_free(void* context); +void subghz_protocol_decoder_raw_reset(void* context); +void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration); +void subghz_protocol_decoder_raw_get_string(void* context, string_t output); + +void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_raw_free(void* context); +void subghz_protocol_encoder_raw_stop(void* context); +void subghz_protocol_raw_file_encoder_worker_callback_end(void* context); +void subghz_protocol_raw_file_encoder_worker_set_callback_end( + SubGhzProtocolEncoderRAW* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback_end, + void* context_end); +void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_name); +bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format); +LevelDuration subghz_protocol_encoder_raw_yield(void* context); diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c new file mode 100644 index 00000000..a1482210 --- /dev/null +++ b/lib/subghz/protocols/registry.c @@ -0,0 +1,53 @@ +#include "registry.h" + +#include "princeton.h" +#include "keeloq.h" +#include "star_line.h" +#include "nice_flo.h" +#include "came.h" +#include "faac_slh.h" +#include "nice_flor_s.h" +#include "came_twee.h" +#include "came_atomo.h" +#include "nero_sketch.h" +#include "ido.h" +#include "kia.h" +#include "hormann.h" +#include "nero_radio.h" +#include "somfy_telis.h" +#include "somfy_keytis.h" +#include "scher_khan.h" +#include "gate_tx.h" +#include "raw.h" + +const SubGhzProtocol* subghz_protocol_registry[] = { + &subghz_protocol_princeton, &subghz_protocol_keeloq, &subghz_protocol_star_line, + &subghz_protocol_nice_flo, &subghz_protocol_came, &subghz_protocol_faac_slh, + &subghz_protocol_nice_flor_s, &subghz_protocol_came_twee, &subghz_protocol_came_atomo, + &subghz_protocol_nero_sketch, &subghz_protocol_ido, &subghz_protocol_kia, + &subghz_protocol_hormann, &subghz_protocol_nero_radio, &subghz_protocol_somfy_telis, + &subghz_protocol_somfy_keytis, &subghz_protocol_scher_khan, &subghz_protocol_gate_tx, + &subghz_protocol_raw, + +}; + +const SubGhzProtocol* subghz_protocol_registry_get_by_name(const char* name) { + for(size_t i = 0; i < subghz_protocol_registry_count(); i++) { + if(strcmp(name, subghz_protocol_registry[i]->name) == 0) { + return subghz_protocol_registry[i]; + } + } + return NULL; +} + +const SubGhzProtocol* subghz_protocol_registry_get_by_index(size_t index) { + if(index < subghz_protocol_registry_count()) { + return subghz_protocol_registry[index]; + } else { + return NULL; + } +} + +size_t subghz_protocol_registry_count() { + return COUNT_OF(subghz_protocol_registry); +} diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h new file mode 100644 index 00000000..30d2e336 --- /dev/null +++ b/lib/subghz/protocols/registry.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../types.h" + +const SubGhzProtocol* subghz_protocol_registry_get_by_name(const char* name); + +const SubGhzProtocol* subghz_protocol_registry_get_by_index(size_t index); + +size_t subghz_protocol_registry_count(); diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c new file mode 100644 index 00000000..9d6dc6dd --- /dev/null +++ b/lib/subghz/protocols/scher_khan.c @@ -0,0 +1,286 @@ +#include "scher_khan.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +//https://phreakerclub.com/72 +//https://phreakerclub.com/forum/showthread.php?t=7&page=2 +//https://phreakerclub.com/forum/showthread.php?t=274&highlight=magicar +//!!! https://phreakerclub.com/forum/showthread.php?t=489&highlight=magicar&page=5 + +#define TAG "SubGhzProtocolScherKhan" + +static const SubGhzBlockConst subghz_protocol_scher_khan_const = { + .te_short = 750, + .te_long = 1100, + .te_delta = 150, + .min_count_bit_for_found = 35, +}; + +struct SubGhzProtocolDecoderScherKhan { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + const char* protocol_name; +}; + +struct SubGhzProtocolEncoderScherKhan { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ScherKhanDecoderStepReset = 0, + ScherKhanDecoderStepCheckPreambula, + ScherKhanDecoderStepSaveDuration, + ScherKhanDecoderStepCheckDuration, +} ScherKhanDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder = { + .alloc = subghz_protocol_decoder_scher_khan_alloc, + .free = subghz_protocol_decoder_scher_khan_free, + + .feed = subghz_protocol_decoder_scher_khan_feed, + .reset = subghz_protocol_decoder_scher_khan_reset, + + .get_hash_data = subghz_protocol_decoder_scher_khan_get_hash_data, + .serialize = subghz_protocol_decoder_scher_khan_serialize, + .deserialize = subghz_protocol_decoder_scher_khan_deserialize, + .get_string = subghz_protocol_decoder_scher_khan_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_scher_khan = { + .name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_scher_khan_decoder, + .encoder = &subghz_protocol_scher_khan_encoder, +}; + +void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderScherKhan* instance = malloc(sizeof(SubGhzProtocolDecoderScherKhan)); + instance->base.protocol = &subghz_protocol_scher_khan; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_scher_khan_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + free(instance); +} + +void subghz_protocol_decoder_scher_khan_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + instance->decoder.parser_step = ScherKhanDecoderStepReset; +} + +void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + + switch(instance->decoder.parser_step) { + case ScherKhanDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta)) { + instance->decoder.parser_step = ScherKhanDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case ScherKhanDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else if( + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta) { + // Found start bit + if(instance->header_count >= 2) { + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + case ScherKhanDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_scher_khan_const.te_long + + subghz_protocol_scher_khan_const.te_delta * 2)) { + //Found stop bit + instance->decoder.parser_step = ScherKhanDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_scher_khan_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ScherKhanDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + case ScherKhanDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_long) < + subghz_protocol_scher_khan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_long) < + subghz_protocol_scher_khan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + } +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolScherKhan instance + */ +static void subghz_protocol_scher_khan_check_remote_controller( + SubGhzBlockGeneric* instance, + const char* protocol_name) { + /* + * MAGICAR 51 bit 00000001A99121DE83C3 MAGIC CODE, Dinamic + * 0E8C1619E830C -> 000011101000110000010110 0001 1001 1110 1000001100001100 + * 0E8C1629D830D -> 000011101000110000010110 0010 1001 1101 1000001100001101 + * 0E8C1649B830E -> 000011101000110000010110 0100 1001 1011 1000001100001110 + * 0E8C16897830F -> 000011101000110000010110 1000 1001 0111 1000001100001111 + * Serial Key Ser ~Key CNT + */ + + switch(instance->data_count_bit) { + // case 35: //MAGIC CODE, Static + // instance->protocol_name = "MAGIC CODE, Static"; + // break; + case 51: //MAGIC CODE, Dinamic + protocol_name = "MAGIC CODE, Dinamic"; + instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F); + instance->btn = (instance->data >> 24) & 0x0F; + instance->cnt = instance->data & 0xFFFF; + break; + // case 57: //MAGIC CODE PRO / PRO2 + // instance->protocol_name = "MAGIC CODE PRO / PRO2"; + // break; + + default: + instance->protocol_name = "Unknown"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + } +} + +uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_scher_khan_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_scher_khan_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + + subghz_protocol_scher_khan_check_remote_controller( + &instance->generic, instance->protocol_name); + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:%07lX Btn:%lX Cnt:%04X\r\n" + "Pt: %s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt, + instance->protocol_name); +} diff --git a/lib/subghz/protocols/scher_khan.h b/lib/subghz/protocols/scher_khan.h new file mode 100644 index 00000000..cf54344e --- /dev/null +++ b/lib/subghz/protocols/scher_khan.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SCHER_KHAN_NAME "Scher-Khan" + +typedef struct SubGhzProtocolDecoderScherKhan SubGhzProtocolDecoderScherKhan; +typedef struct SubGhzProtocolEncoderScherKhan SubGhzProtocolEncoderScherKhan; + +extern const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder; +extern const SubGhzProtocol subghz_protocol_scher_khan; + +void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_scher_khan_free(void* context); +void subghz_protocol_decoder_scher_khan_reset(void* context); +void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); +bool subghz_protocol_decoder_scher_khan_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_scher_khan_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c new file mode 100644 index 00000000..3ec0bbe0 --- /dev/null +++ b/lib/subghz/protocols/somfy_keytis.c @@ -0,0 +1,438 @@ +#include "somfy_keytis.h" +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolSomfyKeytis" + +static const SubGhzBlockConst subghz_protocol_somfy_keytis_const = { + .te_short = 640, + .te_long = 1280, + .te_delta = 250, + .min_count_bit_for_found = 80, +}; + +struct SubGhzProtocolDecoderSomfyKeytis { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + ManchesterState manchester_saved_state; + uint32_t press_duration_counter; +}; + +struct SubGhzProtocolEncoderSomfyKeytis { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + SomfyKeytisDecoderStepReset = 0, + SomfyKeytisDecoderStepCheckPreambula, + SomfyKeytisDecoderStepFoundPreambula, + SomfyKeytisDecoderStepStartDecode, + SomfyKeytisDecoderStepDecoderData, +} SomfyKeytisDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_somfy_keytis_decoder = { + .alloc = subghz_protocol_decoder_somfy_keytis_alloc, + .free = subghz_protocol_decoder_somfy_keytis_free, + + .feed = subghz_protocol_decoder_somfy_keytis_feed, + .reset = subghz_protocol_decoder_somfy_keytis_reset, + + .get_hash_data = subghz_protocol_decoder_somfy_keytis_get_hash_data, + .serialize = subghz_protocol_decoder_somfy_keytis_serialize, + .deserialize = subghz_protocol_decoder_somfy_keytis_deserialize, + .get_string = subghz_protocol_decoder_somfy_keytis_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_somfy_keytis_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_somfy_keytis = { + .name = SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_somfy_keytis_decoder, + .encoder = &subghz_protocol_somfy_keytis_encoder, +}; + +void* subghz_protocol_decoder_somfy_keytis_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderSomfyKeytis* instance = malloc(sizeof(SubGhzProtocolDecoderSomfyKeytis)); + instance->base.protocol = &subghz_protocol_somfy_keytis; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_somfy_keytis_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + free(instance); +} + +void subghz_protocol_decoder_somfy_keytis_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +static uint8_t subghz_protocol_somfy_keytis_crc(uint64_t data) { + uint8_t crc = 0; + data &= 0xFFF0FFFFFFFFFF; + for(uint8_t i = 0; i < 56; i += 8) { + crc = crc ^ data >> i ^ (data >> (i + 4)); + } + return crc & 0xf; +} + +void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case SomfyKeytisDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 4) < + subghz_protocol_somfy_keytis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyKeytisDecoderStepFoundPreambula; + instance->header_count++; + } + break; + case SomfyKeytisDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 4) < + subghz_protocol_somfy_keytis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyKeytisDecoderStepCheckPreambula; + } else { + instance->header_count = 0; + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } + break; + case SomfyKeytisDecoderStepCheckPreambula: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 4) < + subghz_protocol_somfy_keytis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyKeytisDecoderStepFoundPreambula; + instance->header_count++; + } else if( + (instance->header_count > 1) && + (DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short * 7) < + subghz_protocol_somfy_keytis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyKeytisDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->press_duration_counter = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + } + } + + break; + + case SomfyKeytisDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_long) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_somfy_keytis_const.te_long + + subghz_protocol_somfy_keytis_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_somfy_keytis_const.min_count_bit_for_found) { + //check crc + uint64_t data_tmp = instance->generic.data ^ (instance->generic.data >> 8); + if(((data_tmp >> 40) & 0xF) == subghz_protocol_somfy_keytis_crc(data_tmp)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } else { + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_short) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_keytis_const.te_long) < + subghz_protocol_somfy_keytis_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = SomfyKeytisDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + if(instance->decoder.decode_count_bit < 56) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + } else { + instance->press_duration_counter = (instance->press_duration_counter << 1) | + data; + } + + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolSomfyKeytis instance + */ +static void subghz_protocol_somfy_keytis_check_remote_controller(SubGhzBlockGeneric* instance) { + //https://pushstack.wordpress.com/somfy-rts-protocol/ + /* + * 604 us + * / + * | 2416us | 2416us | 2416us | 2416us | 4550 us | | + * + * +--------+ +--------+ +---...---+ + * + +--------+ +--------+ +--+XXXX...XXX+ + * + * | hw. sync. | soft. | | + * | | sync. | data | + * + * + * encrypt | decrypt + * + * package 80 bit pdc key btn crc cnt serial + * + * 0xA453537C4B9855 C40019 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 C80026 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 CC0033 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 D00049 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 D4005C => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 D80063 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 DC0076 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 E00086 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 E40093 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 E800AC => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 EC00B9 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 F000C3 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 F400D6 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 F800E9 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC00FC => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC0102 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC0113 => 0xA 4 F 7 002F 37D3CD + * 0xA453537C4B9855 FC0120 => 0xA 4 F 7 002F 37D3CD + * .......... + * 0xA453537C4B9855 FC048F => 0xA 4 F 7 002F 37D3CD + * + * Pdc: "Press Duration Counter" the total delay of the button is sent 72 parcels, + * pdc cnt4b cnt8b pdc_crc + * C40019 => 11 0001 00 0000 00000001 1001 + * C80026 => 11 0010 00 0000 00000010 0110 + * CC0033 => 11 0011 00 0000 00000011 0011 + * D00049 => 11 0100 00 0000 00000100 1001 + * D4005C => 11 0101 00 0000 00000101 1100 + * D80063 => 11 0110 00 0000 00000110 0011 + * DC0076 => 11 0111 00 0000 00000111 0110 + * E00086 => 11 1000 00 0000 00001000 0110 + * E40093 => 11 1001 00 0000 00001001 0011 + * E800AC => 11 1010 00 0000 00001010 1100 + * EC00B9 => 11 1011 00 0000 00001011 1001 + * F000C3 => 11 1100 00 0000 00001100 0011 + * F400D6 => 11 1101 00 0000 00001101 0110 + * F800E9 => 11 1110 00 0000 00001110 1001 + * FC00FC => 11 1111 00 0000 00001111 1100 + * FC0102 => 11 1111 00 0000 00010000 0010 + * FC0113 => 11 1111 00 0000 00010001 0011 + * FC0120 => 11 1111 00 0000 00010010 0000 + * + * Cnt4b: 4-bit counter changes from 1 to 15 then always equals 15 + * Cnt8b: 8-bit counter changes from 1 to 72 (0x48) + * Ppdc_crc: + * uint8_t crc=0; + * for(i=4; i<24; i+=4){ + * crc ^=(pdc>>i); + * } + * return crc; + * example: crc = 1^0^0^4^C = 9 + * 11, 00, 0000: const + * + * Key: “Encryption Key”, Most significant 4-bit are always 0xA, Least Significant bits is + * a linear counter. In the Smoove Origin this counter is increased together with the + * rolling code. But leaving this on a constant value also works. Gerardwr notes that + * for some other types of remotes the MSB is not constant. + * Btn: 4-bit Control codes, this indicates the button that is pressed + * CRC: 4-bit Checksum. + * Ctn: 16-bit rolling code (big-endian) increased with every button press. + * Serial: 24-bit identifier of sending device (little-endian) + * + * + * Decrypt + * + * uint8_t frame[7]; + * for (i=1; i < 7; i++) { + * frame[i] = frame[i] ^ frame[i-1]; + * } + * or + * uint64 Decrypt = frame ^ (frame>>8); + * + * CRC + * + * uint8_t frame[7]; + * for (i=0; i < 7; i++) { + * crc = crc ^ frame[i] ^ (frame[i] >> 4); + * } + * crc = crc & 0xf; + * + */ + + uint64_t data = instance->data ^ (instance->data >> 8); + instance->btn = (data >> 48) & 0xF; + instance->cnt = (data >> 24) & 0xFFFF; + instance->serial = data & 0xFFFFFF; +} + +static const char* subghz_protocol_somfy_keytis_get_name_button(uint8_t btn) { + const char* name_btn[0x10] = { + "Unknown", + "0x01", + "0x02", + "Prog", + "Key_1", + "0x05", + "0x06", + "0x07", + "0x08", + "0x09", + "0x0A", + "0x0B", + "0x0C", + "0x0D", + "0x0E", + "0x0F"}; + return btn <= 0xf ? name_btn[btn] : name_btn[0]; +} + +uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_somfy_keytis_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + if(res && !flipper_format_write_uint32( + flipper_format, "Duration_Counter", &instance->press_duration_counter, 1)) { + FURI_LOG_E(TAG, "Unable to add Duration_Counter"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32( + flipper_format, + "Duration_Counter", + (uint32_t*)&instance->press_duration_counter, + 1)) { + FURI_LOG_E(TAG, "Missing Duration_Counter"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_somfy_keytis_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderSomfyKeytis* instance = context; + + subghz_protocol_somfy_keytis_check_remote_controller(&instance->generic); + + string_cat_printf( + output, + "%s %db\r\n" + "%lX%08lX%06lX\r\n" + "Sn:0x%06lX \r\n" + "Cnt:0x%04X\r\n" + "Btn:%s\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->press_duration_counter, + instance->generic.serial, + instance->generic.cnt, + subghz_protocol_somfy_keytis_get_name_button(instance->generic.btn)); +} diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h new file mode 100644 index 00000000..1bc32210 --- /dev/null +++ b/lib/subghz/protocols/somfy_keytis.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME "Somfy Keytis" + +typedef struct SubGhzProtocolDecoderSomfyKeytis SubGhzProtocolDecoderSomfyKeytis; +typedef struct SubGhzProtocolEncoderSomfyKeytis SubGhzProtocolEncoderSomfyKeytis; + +extern const SubGhzProtocolDecoder subghz_protocol_somfy_keytis_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_somfy_keytis_encoder; +extern const SubGhzProtocol subghz_protocol_somfy_keytis; + +void* subghz_protocol_decoder_somfy_keytis_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_somfy_keytis_free(void* context); +void subghz_protocol_decoder_somfy_keytis_reset(void* context); +void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); +bool subghz_protocol_decoder_somfy_keytis_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_somfy_keytis_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c new file mode 100644 index 00000000..73815f2b --- /dev/null +++ b/lib/subghz/protocols/somfy_telis.c @@ -0,0 +1,366 @@ +#include "somfy_telis.h" +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolSomfyTelis" + +static const SubGhzBlockConst subghz_protocol_somfy_telis_const = { + .te_short = 640, + .te_long = 1280, + .te_delta = 250, + .min_count_bit_for_found = 56, +}; + +struct SubGhzProtocolDecoderSomfyTelis { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderSomfyTelis { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + SomfyTelisDecoderStepReset = 0, + SomfyTelisDecoderStepCheckPreambula, + SomfyTelisDecoderStepFoundPreambula, + SomfyTelisDecoderStepStartDecode, + SomfyTelisDecoderStepDecoderData, +} SomfyTelisDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_somfy_telis_decoder = { + .alloc = subghz_protocol_decoder_somfy_telis_alloc, + .free = subghz_protocol_decoder_somfy_telis_free, + + .feed = subghz_protocol_decoder_somfy_telis_feed, + .reset = subghz_protocol_decoder_somfy_telis_reset, + + .get_hash_data = subghz_protocol_decoder_somfy_telis_get_hash_data, + .serialize = subghz_protocol_decoder_somfy_telis_serialize, + .deserialize = subghz_protocol_decoder_somfy_telis_deserialize, + .get_string = subghz_protocol_decoder_somfy_telis_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_somfy_telis_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_somfy_telis = { + .name = SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_somfy_telis_decoder, + .encoder = &subghz_protocol_somfy_telis_encoder, +}; + +void* subghz_protocol_decoder_somfy_telis_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderSomfyTelis* instance = malloc(sizeof(SubGhzProtocolDecoderSomfyTelis)); + instance->base.protocol = &subghz_protocol_somfy_telis; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_somfy_telis_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + free(instance); +} + +void subghz_protocol_decoder_somfy_telis_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +static uint8_t subghz_protocol_somfy_telis_crc(uint64_t data) { + uint8_t crc = 0; + data &= 0xFFF0FFFFFFFFFF; + for(uint8_t i = 0; i < 56; i += 8) { + crc = crc ^ data >> i ^ (data >> (i + 4)); + } + return crc & 0xf; +} + +void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case SomfyTelisDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 4) < + subghz_protocol_somfy_telis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyTelisDecoderStepFoundPreambula; + instance->header_count++; + } + break; + case SomfyTelisDecoderStepFoundPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 4) < + subghz_protocol_somfy_telis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyTelisDecoderStepCheckPreambula; + } else { + instance->header_count = 0; + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } + break; + case SomfyTelisDecoderStepCheckPreambula: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 4) < + subghz_protocol_somfy_telis_const.te_delta * 4) { + instance->decoder.parser_step = SomfyTelisDecoderStepFoundPreambula; + instance->header_count++; + } else if( + (instance->header_count > 1) && + (DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short * 7) < + subghz_protocol_somfy_telis_const.te_delta * 4)) { + instance->decoder.parser_step = SomfyTelisDecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + } + } + + break; + + case SomfyTelisDecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_long) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_somfy_telis_const.te_long + + subghz_protocol_somfy_telis_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_somfy_telis_const.min_count_bit_for_found) { + //check crc + uint64_t data_tmp = instance->decoder.decode_data ^ + (instance->decoder.decode_data >> 8); + if(((data_tmp >> 40) & 0xF) == subghz_protocol_somfy_telis_crc(data_tmp)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } else { + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_short) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_somfy_telis_const.te_long) < + subghz_protocol_somfy_telis_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = SomfyTelisDecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolSomfyTelis instance + */ +static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { + //https://pushstack.wordpress.com/somfy-rts-protocol/ + /* + * 604 us + * / + * | 2416us | 2416us | 2416us | 2416us | 4550 us | | 67648 us | 30415 us | + * + * +--------+ +--------+ +---...---+ + * + +--------+ +--------+ +--+XXXX...XXX+-----...----- + * + * | hw. sync. | soft. | | Inter-frame + * | | sync. | data | gap + * + * + * encrypt | decrypt + * + * package 56 bit cnt key btn|crc cnt serial + * 0xA7232323312222 - 0 => A7 8 0 | 00 00 | 12 13 00 + * 0xA7222223312222 - 1 => A7 8 5 | 00 01 | 12 13 00 + * 0xA7212123312222 - 2 => A7 8 6 | 00 02 | 12 13 00 + * + * Key: “Encryption Key”, Most significant 4-bit are always 0xA, Least Significant bits is + * a linear counter. In the Smoove Origin this counter is increased together with the + * rolling code. But leaving this on a constant value also works. Gerardwr notes that + * for some other types of remotes the MSB is not constant. + * Btn: 4-bit Control codes, this indicates the button that is pressed + * CRC: 4-bit Checksum. + * Ctn: 16-bit rolling code (big-endian) increased with every button press. + * Serial: 24-bit identifier of sending device (little-endian) + * + * + * Decrypt + * + * uint8_t frame[7]; + * for (i=1; i < 7; i++) { + * frame[i] = frame[i] ^ frame[i-1]; + * } + * or + * uint64 Decrypt = frame ^ (frame>>8); + * + * Btn + * + * Value Button(s) Description + * 0x1 My Stop or move to favourite position + * 0x2 Up Move up + * 0x3 My + Up Set upper motor limit in initial programming mode + * 0x4 Down Move down + * 0x5 My + Down Set lower motor limit in initial programming mode + * 0x6 Up + Down Change motor limit and initial programming mode + * 0x8 Prog Used for (de-)registering remotes, see below + * 0x9 Sun + Flag Enable sun and wind detector (SUN and FLAG symbol on the Telis Soliris RC) + * 0xA Flag Disable sun detector (FLAG symbol on the Telis Soliris RC) + * + * CRC + * + * uint8_t frame[7]; + * for (i=0; i < 7; i++) { + * cksum = cksum ^ frame[i] ^ (frame[i] >> 4); + * } + * cksum = cksum & 0xf; + * + */ + + uint64_t data = instance->data ^ (instance->data >> 8); + instance->btn = (data >> 44) & 0xF; + instance->cnt = (data >> 24) & 0xFFFF; + instance->serial = data & 0xFFFFFF; +} + +static const char* subghz_protocol_somfy_telis_get_name_button(uint8_t btn) { + const char* name_btn[0x10] = { + "Unknown", + "My", + "Up", + "My+Up", + "Down", + "My+Down", + "Up+Down", + "0x07", + "Prog", + "Sun+Flag", + "Flag", + "0x0B", + "0x0C", + "0x0D", + "0x0E", + "0x0F"}; + return btn <= 0xf ? name_btn[btn] : name_btn[0]; +} + +uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_somfy_telis_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_somfy_telis_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderSomfyTelis* instance = context; + + subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); + + string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%06lX \r\n" + "Cnt:0x%04X\r\n" + "Btn:%s\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.serial, + instance->generic.cnt, + subghz_protocol_somfy_telis_get_name_button(instance->generic.btn)); +} diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h new file mode 100644 index 00000000..e28c2b51 --- /dev/null +++ b/lib/subghz/protocols/somfy_telis.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME "Somfy Telis" + +typedef struct SubGhzProtocolDecoderSomfyTelis SubGhzProtocolDecoderSomfyTelis; +typedef struct SubGhzProtocolEncoderSomfyTelis SubGhzProtocolEncoderSomfyTelis; + +extern const SubGhzProtocolDecoder subghz_protocol_somfy_telis_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_somfy_telis_encoder; +extern const SubGhzProtocol subghz_protocol_somfy_telis; + +void* subghz_protocol_decoder_somfy_telis_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_somfy_telis_free(void* context); +void subghz_protocol_decoder_somfy_telis_reset(void* context); +void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); +bool subghz_protocol_decoder_somfy_telis_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_somfy_telis_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c new file mode 100644 index 00000000..e253fae7 --- /dev/null +++ b/lib/subghz/protocols/star_line.c @@ -0,0 +1,376 @@ +#include "star_line.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include +#include + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolStarLine" + +static const SubGhzBlockConst subghz_protocol_star_line_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 120, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderStarLine { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; + const char* manufacture_name; +}; + +struct SubGhzProtocolEncoderStarLine { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + StarLineDecoderStepReset = 0, + StarLineDecoderStepCheckPreambula, + StarLineDecoderStepSaveDuration, + StarLineDecoderStepCheckDuration, +} StarLineDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_star_line_decoder = { + .alloc = subghz_protocol_decoder_star_line_alloc, + .free = subghz_protocol_decoder_star_line_free, + + .feed = subghz_protocol_decoder_star_line_feed, + .reset = subghz_protocol_decoder_star_line_reset, + + .get_hash_data = subghz_protocol_decoder_star_line_get_hash_data, + .serialize = subghz_protocol_decoder_star_line_serialize, + .deserialize = subghz_protocol_decoder_star_line_deserialize, + .get_string = subghz_protocol_decoder_star_line_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_star_line_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_star_line = { + .name = SUBGHZ_PROTOCOL_STAR_LINE_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_star_line_decoder, + .encoder = &subghz_protocol_star_line_encoder, +}; + +void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderStarLine* instance = malloc(sizeof(SubGhzProtocolDecoderStarLine)); + instance->base.protocol = &subghz_protocol_star_line; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + return instance; +} + +void subghz_protocol_decoder_star_line_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + free(instance); +} + +void subghz_protocol_decoder_star_line_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + instance->decoder.parser_step = StarLineDecoderStepReset; +} + +void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + switch(instance->decoder.parser_step) { + case StarLineDecoderStepReset: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) < + subghz_protocol_star_line_const.te_delta * 2) { + instance->decoder.parser_step = StarLineDecoderStepCheckPreambula; + instance->header_count++; + } else if(instance->header_count > 4) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.te_last = duration; + instance->decoder.parser_step = StarLineDecoderStepCheckDuration; + } + } else { + instance->header_count = 0; + } + break; + case StarLineDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) < + subghz_protocol_star_line_const.te_delta * 2)) { + //Found Preambula + instance->decoder.parser_step = StarLineDecoderStepReset; + } else { + instance->header_count = 0; + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + case StarLineDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_star_line_const.te_long + + subghz_protocol_star_line_const.te_delta)) { + instance->decoder.parser_step = StarLineDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_star_line_const.min_count_bit_for_found) { + if(instance->generic.data != instance->decoder.decode_data) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = StarLineDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + case StarLineDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_short) < + subghz_protocol_star_line_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_short) < + subghz_protocol_star_line_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = StarLineDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_long) < + subghz_protocol_star_line_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long) < + subghz_protocol_star_line_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = StarLineDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + } +} + +static inline bool subghz_protocol_star_line_check_decrypt( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn, + uint32_t end_serial) { + furi_assert(instance); + if((decrypt >> 24 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { + instance->cnt = decrypt & 0x0000FFFF; + return true; + } + return false; +} + +/** Checking the accepted code against the database manafacture key + * + * @param instance SubGhzProtocolStarLine instance + * @param fix fix part of the parcel + * @param hop hop encrypted part of the parcel + * @return true on successful search + */ +static uint8_t subghz_protocol_star_line_check_remote_controller_selector( + SubGhzBlockGeneric* instance, + uint32_t fix, + uint32_t hop, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint16_t end_serial = (uint16_t)(fix & 0xFF); + uint8_t btn = (uint8_t)(fix >> 24); + uint32_t decrypt = 0; + uint64_t man_normal_learning; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal_Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + //########################### + // Normal_Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + man_normal_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; + } + } + + *manufacture_name = "Unknown"; + instance->cnt = 0; + + return 0; +} + +/** Analysis of received data + * + * @param instance SubGhzProtocolStarLine instance + */ +static void subghz_protocol_star_line_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t key_fix = key >> 32; + uint32_t key_hop = key & 0x00000000ffffffff; + + subghz_protocol_star_line_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + + instance->serial = key_fix & 0x00FFFFFF; + instance->btn = key_fix >> 24; +} + +uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_star_line_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + + if(res && !flipper_format_write_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { + FURI_LOG_E(TAG, "Unable to add manufacture name"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_star_line_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04X\r\n" + "Hop:0x%08lX Btn:%02lX\r\n" + "MF:%s\r\n" + "Sn:0x%07lX \r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name, + instance->generic.serial); +} diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h new file mode 100644 index 00000000..1e382c7d --- /dev/null +++ b/lib/subghz/protocols/star_line.h @@ -0,0 +1,25 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_STAR_LINE_NAME "Star Line" + +typedef struct SubGhzProtocolDecoderStarLine SubGhzProtocolDecoderStarLine; +typedef struct SubGhzProtocolEncoderStarLine SubGhzProtocolEncoderStarLine; + +extern const SubGhzProtocolDecoder subghz_protocol_star_line_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_star_line_encoder; +extern const SubGhzProtocol subghz_protocol_star_line; + +void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_star_line_free(void* context); +void subghz_protocol_decoder_star_line_reset(void* context); +void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); +bool subghz_protocol_decoder_star_line_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_star_line_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/subghz_protocol_came.c b/lib/subghz/protocols/subghz_protocol_came.c deleted file mode 100644 index a84f8248..00000000 --- a/lib/subghz/protocols/subghz_protocol_came.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "subghz_protocol_came.h" -#include "subghz_protocol_common.h" - -/* - * Help - * https://phreakerclub.com/447 - * - */ - -struct SubGhzProtocolCame { - SubGhzProtocolCommon common; -}; - -typedef enum { - CameDecoderStepReset = 0, - CameDecoderStepFoundStartBit, - CameDecoderStepSaveDuration, - CameDecoderStepCheckDuration, -} CameDecoderStep; - -SubGhzProtocolCame* subghz_protocol_came_alloc() { - SubGhzProtocolCame* instance = malloc(sizeof(SubGhzProtocolCame)); - - instance->common.name = "CAME"; - instance->common.code_min_count_bit_for_found = 12; - instance->common.te_short = 320; - instance->common.te_long = 640; - instance->common.te_delta = 150; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_came_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_came_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_came_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_came_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_came_send_key; - - return instance; -} - -void subghz_protocol_came_free(SubGhzProtocolCame* instance) { - furi_assert(instance); - free(instance); -} - -bool subghz_protocol_came_send_key( - SubGhzProtocolCame* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - size_t index = 0; - encoder->size_upload = (instance->common.code_last_count_bit * 2) + 2; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - //Send header - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 36); - //Send start bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - } - } - return true; -} - -void subghz_protocol_came_reset(SubGhzProtocolCame* instance) { - instance->common.parser_step = CameDecoderStepReset; -} - -void subghz_protocol_came_parse(SubGhzProtocolCame* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case CameDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 51) < - instance->common.te_delta * 51)) { //Need protocol 36 te_short - //Found header CAME - instance->common.parser_step = CameDecoderStepFoundStartBit; - } - break; - case CameDecoderStepFoundStartBit: - if(!level) { - break; - } else if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - //Found start bit CAME - instance->common.parser_step = CameDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = CameDecoderStepReset; - } - break; - case CameDecoderStepSaveDuration: - if(!level) { //save interval - if(duration >= (instance->common.te_short * 4)) { - instance->common.parser_step = CameDecoderStepFoundStartBit; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.serial = 0x0; - instance->common.btn = 0x0; - - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - break; - } - instance->common.te_last = duration; - instance->common.parser_step = CameDecoderStepCheckDuration; - } else { - instance->common.parser_step = CameDecoderStepReset; - } - break; - case CameDecoderStepCheckDuration: - if(level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = CameDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = CameDecoderStepSaveDuration; - } else - instance->common.parser_step = CameDecoderStepReset; - } else { - instance->common.parser_step = CameDecoderStepReset; - } - break; - } -} - -void subghz_protocol_came_to_str(SubGhzProtocolCame* instance, string_t output) { - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_lo, - code_found_reverse_lo); -} - -bool subghz_protocol_came_to_save_file(SubGhzProtocolCame* instance, FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_came_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolCame* instance, - const char* file_path) { - return subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); -} - -void subghz_decoder_came_to_load_protocol(SubGhzProtocolCame* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; -} diff --git a/lib/subghz/protocols/subghz_protocol_came.h b/lib/subghz/protocols/subghz_protocol_came.h deleted file mode 100644 index 5d6790da..00000000 --- a/lib/subghz/protocols/subghz_protocol_came.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolCame SubGhzProtocolCame; - -/** Allocate SubGhzProtocolCame - * - * @return SubGhzProtocolCame* - */ -SubGhzProtocolCame* subghz_protocol_came_alloc(); - -/** Free SubGhzProtocolCame - * - * @param instance - */ -void subghz_protocol_came_free(SubGhzProtocolCame* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolCame instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_came_send_key( - SubGhzProtocolCame* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolCame instance - */ -void subghz_protocol_came_reset(SubGhzProtocolCame* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolCame instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_came_parse(SubGhzProtocolCame* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolCame* instance - * @param output - output string - */ -void subghz_protocol_came_to_str(SubGhzProtocolCame* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolCame instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_came_to_save_file(SubGhzProtocolCame* instance, FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolCame instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_came_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolCame* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolCame instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_came_to_load_protocol(SubGhzProtocolCame* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_came_atomo.c b/lib/subghz/protocols/subghz_protocol_came_atomo.c deleted file mode 100644 index 6bcb336b..00000000 --- a/lib/subghz/protocols/subghz_protocol_came_atomo.c +++ /dev/null @@ -1,264 +0,0 @@ -#include "subghz_protocol_came_atomo.h" -#include "subghz_protocol_common.h" -#include -#include "../subghz_keystore.h" - -#define TAG "SubGhzCameAtomo" - -#define SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE 0xFFFFFFFFFFFFFFFF - -struct SubGhzProtocolCameAtomo { - SubGhzProtocolCommon common; - ManchesterState manchester_saved_state; - const char* rainbow_table_file_name; -}; - -typedef enum { - CameAtomoDecoderStepReset = 0, - CameAtomoDecoderStepDecoderData, -} CameAtomoDecoderStep; - -SubGhzProtocolCameAtomo* subghz_protocol_came_atomo_alloc() { - SubGhzProtocolCameAtomo* instance = malloc(sizeof(SubGhzProtocolCameAtomo)); - - instance->common.name = "CAME Atomo"; - instance->common.code_min_count_bit_for_found = 62; - instance->common.te_short = 600; - instance->common.te_long = 1200; - instance->common.te_delta = 250; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_came_atomo_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_came_atomo_to_load_protocol; - - return instance; -} - -void subghz_protocol_came_atomo_free(SubGhzProtocolCameAtomo* instance) { - furi_assert(instance); - free(instance); -} - -void subghz_protocol_came_atomo_name_file(SubGhzProtocolCameAtomo* instance, const char* name) { - instance->rainbow_table_file_name = name; - FURI_LOG_I(TAG, "Loading rainbow table from %s", name); -} - -/** Read bytes from rainbow table - * - * @param instance - SubGhzProtocolCameAtomo* instance - * @param number_atomo_magic_xor - * @return atomo_magic_xor - */ -uint64_t subghz_came_atomo_get_atomo_magic_xor_in_file( - SubGhzProtocolCameAtomo* instance, - uint8_t number_atomo_magic_xor) { - if(!strcmp(instance->rainbow_table_file_name, "")) return SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE; - - uint8_t buffer[sizeof(uint64_t)] = {0}; - uint32_t address = number_atomo_magic_xor * sizeof(uint64_t); - uint64_t atomo_magic_xor = 0; - - if(subghz_keystore_raw_get_data( - instance->rainbow_table_file_name, address, buffer, sizeof(uint64_t))) { - for(size_t i = 0; i < sizeof(uint64_t); i++) { - atomo_magic_xor = (atomo_magic_xor << 8) | buffer[i]; - } - } else { - atomo_magic_xor = SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE; - } - return atomo_magic_xor; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolCameAtomo instance - */ -void subghz_protocol_came_atomo_remote_controller(SubGhzProtocolCameAtomo* instance) { - /* - * 0x1fafef3ed0f7d9ef - * 0x185fcc1531ee86e7 - * 0x184fa96912c567ff - * 0x187f8a42f3dc38f7 - * 0x186f63915492a5cd - * 0x181f40bab58bfac5 - * 0x180f25c696a01bdd - * 0x183f06ed77b944d5 - * 0x182ef661d83d21a9 - * 0x18ded54a39247ea1 - * 0x18ceb0361a0f9fb9 - * 0x18fe931dfb16c0b1 - * 0x18ee7ace5c585d8b - * ........ - * transmission consists of 99 parcels with increasing counter while holding down the button - * with each new press, the counter in the encrypted part increases - * - * 0x1FAFF13ED0F7D9EF - * 0x1FAFF11ED0F7D9EF - * 0x1FAFF10ED0F7D9EF - * 0x1FAFF0FED0F7D9EF - * 0x1FAFF0EED0F7D9EF - * 0x1FAFF0DED0F7D9EF - * 0x1FAFF0CED0F7D9EF - * 0x1FAFF0BED0F7D9EF - * 0x1FAFF0AED0F7D9EF - * - * where 0x1FAF - parcel counter, 0хF0A - button press counter, - * 0xED0F7D9E - serial number, 0хF - key - * 0x1FAF parcel counter - 1 in the parcel queue ^ 0x185F = 0x07F0 - * 0x185f ^ 0x185F = 0x0000 - * 0x184f ^ 0x185F = 0x0010 - * 0x187f ^ 0x185F = 0x0020 - * ..... - * 0x182e ^ 0x185F = 0x0071 - * 0x18de ^ 0x185F = 0x0081 - * ..... - * 0x1e43 ^ 0x185F = 0x061C - * where the last nibble is incremented every 8 samples - * - * Decode - * - * 0x1cf6931dfb16c0b1 => 0x1cf6 - * 0x1cf6 ^ 0x185F = 0x04A9 - * 0x04A9 => 0x04A = 74 (dec) - * 74+1 % 32(atomo_magic_xor) = 11 - * GET atomo_magic_xor[11] = 0xXXXXXXXXXXXXXXXX - * 0x931dfb16c0b1 ^ 0xXXXXXXXXXXXXXXXX = 0xEF3ED0F7D9EF - * 0xEF3 ED0F7D9E F => 0xEF3 - CNT, 0xED0F7D9E - SN, 0xF - key - * - * */ - - uint16_t parcel_counter = instance->common.code_last_found >> 48; - parcel_counter = parcel_counter ^ 0x185F; - parcel_counter >>= 4; - uint8_t ind = (parcel_counter + 1) % 32; - uint64_t temp_data = instance->common.code_last_found & 0x0000FFFFFFFFFFFF; - uint64_t atomo_magic_xor = subghz_came_atomo_get_atomo_magic_xor_in_file(instance, ind); - - if(atomo_magic_xor != SUBGHZ_NO_CAME_ATOMO_RAINBOW_TABLE) { - temp_data = temp_data ^ atomo_magic_xor; - instance->common.cnt = temp_data >> 36; - instance->common.serial = (temp_data >> 4) & 0x000FFFFFFFF; - instance->common.btn = temp_data & 0xF; - } else { - instance->common.cnt = 0; - instance->common.serial = 0; - instance->common.btn = 0; - } -} - -void subghz_protocol_came_atomo_reset(SubGhzProtocolCameAtomo* instance) { - instance->common.parser_step = CameAtomoDecoderStepReset; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); -} - -void subghz_protocol_came_atomo_parse( - SubGhzProtocolCameAtomo* instance, - bool level, - uint32_t duration) { - ManchesterEvent event = ManchesterEventReset; - switch(instance->common.parser_step) { - case CameAtomoDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_long * 65) < - instance->common.te_delta * 20)) { - //Found header CAME - instance->common.parser_step = CameAtomoDecoderStepDecoderData; - instance->common.code_found = 0; - instance->common.code_count_bit = 1; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventShortLow, - &instance->manchester_saved_state, - NULL); - } - break; - case CameAtomoDecoderStepDecoderData: - if(!level) { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortLow; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongLow; - } else if(duration >= (instance->common.te_long * 2 + instance->common.te_delta)) { - if(instance->common.code_count_bit == - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 1; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventShortLow, - &instance->manchester_saved_state, - NULL); - } else { - instance->common.parser_step = CameAtomoDecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortHigh; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongHigh; - } else { - instance->common.parser_step = CameAtomoDecoderStepReset; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - - if(data_ok) { - instance->common.code_found = (instance->common.code_found << 1) | !data; - instance->common.code_count_bit++; - } - } - break; - } -} -void subghz_protocol_came_atomo_to_str(SubGhzProtocolCameAtomo* instance, string_t output) { - subghz_protocol_came_atomo_remote_controller(instance); - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %db\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%08lX Btn:0x%01X\r\n" - "Cnt:0x%03X\r\n", - - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->common.serial, - instance->common.btn, - instance->common.cnt); -} - -void subghz_decoder_came_atomo_to_load_protocol(SubGhzProtocolCameAtomo* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_came_atomo_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_came_atomo.h b/lib/subghz/protocols/subghz_protocol_came_atomo.h deleted file mode 100644 index cc841441..00000000 --- a/lib/subghz/protocols/subghz_protocol_came_atomo.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolCameAtomo SubGhzProtocolCameAtomo; - -/** Allocate SubGhzProtocolCameAtomo - * - * @return SubGhzProtocolCameAtomo* - */ -SubGhzProtocolCameAtomo* subghz_protocol_came_atomo_alloc(); - -/** Free SubGhzProtocolCameAtomo - * - * @param instance - */ -void subghz_protocol_came_atomo_free(SubGhzProtocolCameAtomo* instance); - -/** File name rainbow table CAME Atomo - * - * @param instance - SubGhzProtocolCameAtomo instance - * @param file_name - "path/file_name" - */ -void subghz_protocol_came_atomo_name_file(SubGhzProtocolCameAtomo* instance, const char* name); - -/** Reset internal state - * @param instance - SubGhzProtocolCameAtomo instance - */ -void subghz_protocol_came_atomo_reset(SubGhzProtocolCameAtomo* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolCameAtomo instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_came_atomo_parse( - SubGhzProtocolCameAtomo* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolCameAtomo* instance - * @param output - output string - */ -void subghz_protocol_came_atomo_to_str(SubGhzProtocolCameAtomo* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolCameAtomo instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_came_atomo_to_load_protocol(SubGhzProtocolCameAtomo* instance, void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_came_twee.c b/lib/subghz/protocols/subghz_protocol_came_twee.c deleted file mode 100644 index 714cdf58..00000000 --- a/lib/subghz/protocols/subghz_protocol_came_twee.c +++ /dev/null @@ -1,353 +0,0 @@ -#include "subghz_protocol_came_twee.h" -#include "subghz_protocol_common.h" -#include -#include - -/* - * Help - * https://phreakerclub.com/forum/showthread.php?t=635&highlight=came+twin - * - */ - -#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" -#define CNT_TO_DIP(dip) \ - (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ - (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ - (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ - (dip & 0x0001 ? '1' : '0') - -struct SubGhzProtocolCameTwee { - SubGhzProtocolCommon common; - ManchesterState manchester_saved_state; -}; - -typedef enum { - CameTweeDecoderStepReset = 0, - CameTweeDecoderStepDecoderData, -} CameTweeDecoderStep; - -SubGhzProtocolCameTwee* subghz_protocol_came_twee_alloc() { - SubGhzProtocolCameTwee* instance = malloc(sizeof(SubGhzProtocolCameTwee)); - - instance->common.name = "CAME TWEE"; - instance->common.code_min_count_bit_for_found = 54; - instance->common.te_short = 500; - instance->common.te_long = 1000; - instance->common.te_delta = 250; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_came_twee_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_came_twee_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_came_twee_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_came_twee_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_came_twee_send_key; - - return instance; -} - -void subghz_protocol_came_twee_free(SubGhzProtocolCameTwee* instance) { - furi_assert(instance); - free(instance); -} - -LevelDuration subghz_protocol_came_twee_add_duration_to_upload( - SubGhzProtocolCameTwee* instance, - ManchesterEncoderResult result) { - LevelDuration data = {.duration = 0, .level = 0}; - switch(result) { - case ManchesterEncoderResultShortLow: - data.duration = instance->common.te_short; - data.level = false; - break; - case ManchesterEncoderResultLongLow: - data.duration = instance->common.te_long; - data.level = false; - break; - case ManchesterEncoderResultLongHigh: - data.duration = instance->common.te_long; - data.level = true; - break; - case ManchesterEncoderResultShortHigh: - data.duration = instance->common.te_short; - data.level = true; - break; - - default: - printf("DO CRASH HERE\r\n"); - // furi_crash - break; - } - return level_duration_make(data.level, data.duration); -} - -bool subghz_protocol_came_twee_send_key( - SubGhzProtocolCameTwee* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - const uint32_t magic_numbers_xor[15] = { - 0x0E0E0E00, - 0x1D1D1D11, - 0x2C2C2C22, - 0x3B3B3B33, - 0x4A4A4A44, - 0x59595955, - 0x68686866, - 0x77777777, - 0x86868688, - 0x95959599, - 0xA4A4A4AA, - 0xB3B3B3BB, - 0xC2C2C2CC, - 0xD1D1D1DD, - 0xE0E0E0EE, - }; - - size_t index = 0; - ManchesterEncoderState enc_state; - manchester_encoder_reset(&enc_state); - ManchesterEncoderResult result; - - // encoder->size_upload = (instance->common.code_last_count_bit * 2) + 2; - // if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - - uint64_t temp_parcel = 0x003FFF7200000000; //parcel mask - - for(int i = 14; i >= 0; i--) { - temp_parcel = (temp_parcel & 0xFFFFFFFF00000000) | - (instance->common.serial ^ magic_numbers_xor[i]); - - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(!manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result)) { - encoder->upload[index++] = - subghz_protocol_came_twee_add_duration_to_upload(instance, result); - manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result); - } - encoder->upload[index++] = - subghz_protocol_came_twee_add_duration_to_upload(instance, result); - } - encoder->upload[index] = subghz_protocol_came_twee_add_duration_to_upload( - instance, manchester_encoder_finish(&enc_state)); - if(level_duration_get_level(encoder->upload[index])) { - index++; - } - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long * 51); - } - encoder->size_upload = index; - return true; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolCameTwee instance - */ -void subghz_protocol_came_twee_remote_controller(SubGhzProtocolCameTwee* instance) { - /* Came Twee 54 bit, rolling code 15 parcels with - * a decreasing counter from 0xE to 0x0 - * with originally coded dip switches on the console 10 bit code - * - * 0x003FFF72E04A6FEE - * 0x003FFF72D17B5EDD - * 0x003FFF72C2684DCC - * 0x003FFF72B3193CBB - * 0x003FFF72A40E2BAA - * 0x003FFF72953F1A99 - * 0x003FFF72862C0988 - * 0x003FFF7277DDF877 - * 0x003FFF7268C2E766 - * 0x003FFF7259F3D655 - * 0x003FFF724AE0C544 - * 0x003FFF723B91B433 - * 0x003FFF722C86A322 - * 0x003FFF721DB79211 - * 0x003FFF720EA48100 - * - * decryption - * the last 32 bits, do XOR by the desired number, divide the result by 4, - * convert the first 16 bits of the resulting 32-bit number to bin and do - * bit-by-bit mirroring, adding up to 10 bits - * - * Example - * Step 1. 0x003FFF721DB79211 => 0x1DB79211 - * Step 4. 0x1DB79211 xor 0x1D1D1D11 => 0x00AA8F00 - * Step 4. 0x00AA8F00 / 4 => 0x002AA3C0 - * Step 5. 0x002AA3C0 => 0x002A - * Step 6. 0x002A bin => b101010 - * Step 7. b101010 => b0101010000 - * Step 8. b0101010000 => (Dip) Off ON Off ON Off ON Off Off Off Off - */ - - const uint32_t magic_numbers_xor[15] = { - 0x0E0E0E00, - 0x1D1D1D11, - 0x2C2C2C22, - 0x3B3B3B33, - 0x4A4A4A44, - 0x59595955, - 0x68686866, - 0x77777777, - 0x86868688, - 0x95959599, - 0xA4A4A4AA, - 0xB3B3B3BB, - 0xC2C2C2CC, - 0xD1D1D1DD, - 0xE0E0E0EE, - }; - uint8_t cnt_parcel = (uint8_t)(instance->common.code_last_found & 0xF); - uint32_t data = (uint32_t)(instance->common.code_last_found & 0x0FFFFFFFF); - - data = (data ^ magic_numbers_xor[cnt_parcel]); - instance->common.serial = data; - data /= 4; - instance->common.btn = (data >> 4) & 0x0F; - data >>= 16; - data = (uint16_t)subghz_protocol_common_reverse_key(data, 16); - instance->common.cnt = data >> 6; -} - -void subghz_protocol_came_twee_reset(SubGhzProtocolCameTwee* instance) { - instance->common.parser_step = CameTweeDecoderStepReset; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); -} - -void subghz_protocol_came_twee_parse( - SubGhzProtocolCameTwee* instance, - bool level, - uint32_t duration) { - ManchesterEvent event = ManchesterEventReset; - switch(instance->common.parser_step) { - case CameTweeDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_long * 51) < - instance->common.te_delta * 20)) { - //Found header CAME - instance->common.parser_step = CameTweeDecoderStepDecoderData; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongLow, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongHigh, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventShortLow, - &instance->manchester_saved_state, - NULL); - } - break; - case CameTweeDecoderStepDecoderData: - if(!level) { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortLow; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongLow; - } else if(duration >= (instance->common.te_long * 2 + instance->common.te_delta)) { - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongLow, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongHigh, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventShortLow, - &instance->manchester_saved_state, - NULL); - } else { - instance->common.parser_step = CameTweeDecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortHigh; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongHigh; - } else { - instance->common.parser_step = CameTweeDecoderStepReset; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - - if(data_ok) { - instance->common.code_found = (instance->common.code_found << 1) | !data; - instance->common.code_count_bit++; - } - } - break; - } -} -void subghz_protocol_came_twee_to_str(SubGhzProtocolCameTwee* instance, string_t output) { - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Btn:%lX\r\n" - "DIP:" DIP_PATTERN, - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->common.btn, - CNT_TO_DIP(instance->common.cnt)); -} - -bool subghz_protocol_came_twee_to_save_file( - SubGhzProtocolCameTwee* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_came_twee_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolCameTwee* instance, - const char* file_path) { - if(subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format)) { - subghz_protocol_came_twee_remote_controller(instance); - return true; - } - return false; -} - -void subghz_decoder_came_twee_to_load_protocol(SubGhzProtocolCameTwee* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_came_twee_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_came_twee.h b/lib/subghz/protocols/subghz_protocol_came_twee.h deleted file mode 100644 index decb723c..00000000 --- a/lib/subghz/protocols/subghz_protocol_came_twee.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolCameTwee SubGhzProtocolCameTwee; - -/** Allocate SubGhzProtocolCameTwee - * - * @return SubGhzProtocolCameTwee* - */ -SubGhzProtocolCameTwee* subghz_protocol_came_twee_alloc(); - -/** Free SubGhzProtocolCameTwee - * - * @param instance - */ -void subghz_protocol_came_twee_free(SubGhzProtocolCameTwee* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolCameTwee instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_came_twee_send_key( - SubGhzProtocolCameTwee* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolCameTwee instance - */ -void subghz_protocol_came_twee_reset(SubGhzProtocolCameTwee* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolCameTwee instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_came_twee_parse( - SubGhzProtocolCameTwee* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolCameTwee* instance - * @param output - output string - */ -void subghz_protocol_came_twee_to_str(SubGhzProtocolCameTwee* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolCameTwee instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_came_twee_to_save_file( - SubGhzProtocolCameTwee* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolCameTwee instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_came_twee_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolCameTwee* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolCameTwee instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_came_twee_to_load_protocol(SubGhzProtocolCameTwee* instance, void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_cfm.c b/lib/subghz/protocols/subghz_protocol_cfm.c deleted file mode 100644 index 44d89f39..00000000 --- a/lib/subghz/protocols/subghz_protocol_cfm.c +++ /dev/null @@ -1,4 +0,0 @@ - -/* - * https://phreakerclub.com/616 - */ diff --git a/lib/subghz/protocols/subghz_protocol_cfm.h b/lib/subghz/protocols/subghz_protocol_cfm.h deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/subghz/protocols/subghz_protocol_common.c b/lib/subghz/protocols/subghz_protocol_common.c deleted file mode 100644 index b1119a39..00000000 --- a/lib/subghz/protocols/subghz_protocol_common.c +++ /dev/null @@ -1,236 +0,0 @@ -#include "subghz_protocol_common.h" -#include -#include - -SubGhzProtocolCommonEncoder* subghz_protocol_encoder_common_alloc() { - SubGhzProtocolCommonEncoder* instance = malloc(sizeof(SubGhzProtocolCommonEncoder)); - instance->upload = malloc(SUBGHZ_ENCODER_UPLOAD_MAX_SIZE * sizeof(LevelDuration)); - instance->start = true; - instance->repeat = 10; //default number of repeat - return instance; -} - -void subghz_protocol_encoder_common_free(SubGhzProtocolCommonEncoder* instance) { - furi_assert(instance); - if(instance->callback_end) { - instance->callback_end((SubGhzProtocolCommon*)instance->context_end); - } - free(instance->upload); - free(instance); -} - -size_t subghz_encoder_common_get_repeat_left(SubGhzProtocolCommonEncoder* instance) { - furi_assert(instance); - return instance->repeat; -} - -void subghz_protocol_encoder_common_set_callback( - SubGhzProtocolCommonEncoder* instance, - SubGhzProtocolCommonEncoderCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - instance->callback = callback; - instance->context = context; -} - -void subghz_protocol_encoder_common_set_callback_end( - SubGhzProtocolCommonEncoder* instance, - SubGhzProtocolCommonEncoderCallbackEnd callback_end, - void* context_end) { - furi_assert(instance); - furi_assert(callback_end); - instance->callback_end = callback_end; - instance->context_end = context_end; -} - -LevelDuration subghz_protocol_encoder_common_yield(void* context) { - SubGhzProtocolCommonEncoder* instance = context; - - if(instance->callback) { - return instance->callback((SubGhzProtocolCommon*)instance->context); - } - - if(instance->repeat == 0) { - return level_duration_reset(); - } - - LevelDuration ret = instance->upload[instance->front]; - - if(++instance->front == instance->size_upload) { - instance->repeat--; - instance->front = 0; - } - - return ret; -} - -void subghz_protocol_common_add_bit(SubGhzProtocolCommon* common, uint8_t bit) { - common->code_found = common->code_found << 1 | bit; - common->code_count_bit++; -} - -bool subghz_protocol_common_check_interval( - SubGhzProtocolCommon* common, - uint32_t duration, - uint16_t duration_check) { - if((duration_check >= (duration - common->te_delta)) && - (duration_check <= (duration + common->te_delta))) { - return true; - } else { - return false; - } -} - -uint64_t subghz_protocol_common_reverse_key(uint64_t key, uint8_t count_bit) { - uint64_t key_reverse = 0; - for(uint8_t i = 0; i < count_bit; i++) { - key_reverse = key_reverse << 1 | bit_read(key, i); - } - return key_reverse; -} - -void subghz_protocol_common_set_callback( - SubGhzProtocolCommon* common, - SubGhzProtocolCommonCallback callback, - void* context) { - common->callback = callback; - common->context = context; -} - -void subghz_protocol_common_to_str(SubGhzProtocolCommon* instance, string_t output) { - if(instance->to_string) { - instance->to_string(instance, output); - } else { - uint32_t code_found_hi = instance->code_found >> 32; - uint32_t code_found_lo = instance->code_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = - subghz_protocol_common_reverse_key(instance->code_found, instance->code_count_bit); - - uint32_t code_found_reverse_hi = code_found_reverse >> 32; - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - - if(code_found_hi > 0) { - string_cat_printf( - output, - "Protocol %s, %d Bit\r\n" - " KEY:0x%lX%08lX\r\n" - " YEK:0x%lX%08lX\r\n" - " SN:0x%05lX BTN:%02X\r\n", - instance->name, - instance->code_count_bit, - code_found_hi, - code_found_lo, - code_found_reverse_hi, - code_found_reverse_lo, - instance->serial, - instance->btn); - } else { - string_cat_printf( - output, - "Protocol %s, %d Bit\r\n" - " KEY:0x%lX%lX\r\n" - " YEK:0x%lX%lX\r\n" - " SN:0x%05lX BTN:%02X\r\n", - instance->name, - instance->code_count_bit, - code_found_hi, - code_found_lo, - code_found_reverse_hi, - code_found_reverse_lo, - instance->serial, - instance->btn); - } - } -} - -bool subghz_protocol_common_read_hex(string_t str, uint8_t* buff, uint16_t len) { - string_strim(str); - uint8_t nibble_high = 0; - uint8_t nibble_low = 0; - bool parsed = true; - - for(uint16_t i = 0; i < len; i++) { - if(hex_char_to_hex_nibble(string_get_char(str, 0), &nibble_high) && - hex_char_to_hex_nibble(string_get_char(str, 1), &nibble_low)) { - buff[i] = (nibble_high << 4) | nibble_low; - if(string_size(str) > 2) { - string_right(str, 2); - } else if(i < len - 1) { - parsed = false; - break; - }; - } else { - parsed = false; - break; - } - } - return parsed; -} - -bool subghz_protocol_common_to_save_file( - SubGhzProtocolCommon* instance, - FlipperFormat* flipper_format) { - furi_assert(instance); - furi_assert(flipper_format); - bool res = false; - do { - if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->name)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Protocol"); - break; - } - uint32_t temp = instance->code_last_count_bit; - if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Bit"); - break; - } - - uint8_t key_data[sizeof(uint64_t)] = {0}; - for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (instance->code_last_found >> i * 8) & 0xFF; - } - - if(!flipper_format_write_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Key"); - break; - } - res = true; - } while(false); - - return res; -} - -bool subghz_protocol_common_to_load_protocol_from_file( - SubGhzProtocolCommon* instance, - FlipperFormat* flipper_format) { - furi_assert(instance); - furi_assert(flipper_format); - bool loaded = false; - string_t temp_str; - string_init(temp_str); - uint32_t temp_data = 0; - - do { - if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Bit"); - break; - } - instance->code_last_count_bit = (uint8_t)temp_data; - - uint8_t key_data[sizeof(uint64_t)] = {0}; - if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { - FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Key"); - break; - } - for(uint8_t i = 0; i < sizeof(uint64_t); i++) { - instance->code_last_found = instance->code_last_found << 8 | key_data[i]; - } - - loaded = true; - } while(0); - - string_clear(temp_str); - - return loaded; -} diff --git a/lib/subghz/protocols/subghz_protocol_common.h b/lib/subghz/protocols/subghz_protocol_common.h deleted file mode 100644 index 4abae60b..00000000 --- a/lib/subghz/protocols/subghz_protocol_common.h +++ /dev/null @@ -1,226 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#define bit_read(value, bit) (((value) >> (bit)) & 0x01) -#define bit_set(value, bit) ((value) |= (1UL << (bit))) -#define bit_clear(value, bit) ((value) &= ~(1UL << (bit))) -#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) - -#define SUBGHZ_TX_PIN_HIGH() -#define SUBGHZ_TX_PIN_LOW() -#define DURATION_DIFF(x, y) ((x < y) ? (y - x) : (x - y)) - -#define SUBGHZ_APP_FOLDER "/any/subghz" -#define SUBGHZ_RAW_FOLDER "/ext/subghz" -#define SUBGHZ_APP_EXTENSION ".sub" -#define SUBGHZ_ENCODER_UPLOAD_MAX_SIZE 2048 - -#define SUBGHZ_PARSER_TAG "SubGhzParser" -#define SUBGHZ_KEY_FILE_VERSION 1 -#define SUBGHZ_KEY_FILE_TYPE "Flipper SubGhz Key File" - -#define SUBGHZ_RAW_FILE_VERSION 1 -#define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" - -typedef enum { - SubGhzProtocolCommonTypeUnknown, - SubGhzProtocolCommonTypeStatic, - SubGhzProtocolCommonTypeDynamic, - SubGhzProtocolCommonTypeRAW, -} SubGhzProtocolCommonType; - -typedef struct SubGhzProtocolCommon SubGhzProtocolCommon; -typedef struct SubGhzProtocolCommonEncoder SubGhzProtocolCommonEncoder; -typedef struct SubGhzProtocolCommonLoad SubGhzProtocolCommonLoad; - -typedef void (*SubGhzProtocolCommonCallback)(SubGhzProtocolCommon* parser, void* context); - -typedef void (*SubGhzProtocolCommonToStr)(SubGhzProtocolCommon* instance, string_t output); - -//Get string to save -typedef bool ( - *SubGhzProtocolCommonSaveFile)(SubGhzProtocolCommon* instance, FlipperFormat* flipper_format); - -//Load protocol from file -typedef bool (*SubGhzProtocolCommonLoadFromFile)( - FlipperFormat* flipper_format, - SubGhzProtocolCommon* instance, - const char* file_path); -//Load protocol -typedef void (*SubGhzProtocolCommonLoadFromRAW)(SubGhzProtocolCommon* instance, void* context); -//Get upload encoder protocol -typedef bool (*SubGhzProtocolCommonEncoderGetUpLoad)( - SubGhzProtocolCommon* instance, - SubGhzProtocolCommonEncoder* encoder); - -typedef LevelDuration (*SubGhzProtocolCommonEncoderCallback)(void* context); -typedef void (*SubGhzProtocolCommonEncoderCallbackEnd)(void* context); - -struct SubGhzProtocolCommon { - const char* name; - uint16_t te_long; - uint16_t te_short; - uint16_t te_delta; - uint8_t code_count_bit; - uint8_t code_last_count_bit; - uint64_t code_found; - uint64_t code_last_found; - uint8_t code_min_count_bit_for_found; - uint8_t btn; - uint8_t header_count; - SubGhzProtocolCommonType type_protocol; - uint32_t te_last; - uint32_t serial; - uint32_t parser_step; - uint16_t cnt; - - /* Standard Callback for on rx complete event */ - SubGhzProtocolCommonCallback callback; - void* context; - - /* Dump To String */ - SubGhzProtocolCommonToStr to_string; - /* Get string to save */ - SubGhzProtocolCommonSaveFile to_save_file; - /* Load protocol from file */ - SubGhzProtocolCommonLoadFromFile to_load_protocol_from_file; - /* Load protocol from RAW data */ - SubGhzProtocolCommonLoadFromRAW to_load_protocol; - /* Get upload encoder protocol */ - SubGhzProtocolCommonEncoderGetUpLoad get_upload_protocol; -}; - -struct SubGhzProtocolCommonEncoder { - bool start; - size_t repeat; - size_t front; - size_t size_upload; - LevelDuration* upload; - - SubGhzProtocolCommonEncoderCallback callback; - SubGhzProtocolCommonEncoderCallbackEnd callback_end; - void* context; - void* context_end; -}; - -struct SubGhzProtocolCommonLoad { - uint64_t code_found; - uint8_t code_count_bit; - uint32_t param1; - uint32_t param2; - uint32_t param3; -}; - -/** Allocate SubGhzProtocolCommonEncoder - * - * @return SubGhzProtocolCommonEncoder* - */ -SubGhzProtocolCommonEncoder* subghz_protocol_encoder_common_alloc(); - -/** Free SubGhzProtocolCommonEncoder - * - * @param instance - */ -void subghz_protocol_encoder_common_free(SubGhzProtocolCommonEncoder* instance); - -void subghz_protocol_encoder_common_set_callback( - SubGhzProtocolCommonEncoder* instance, - SubGhzProtocolCommonEncoderCallback callback, - void* context); - -void subghz_protocol_encoder_common_set_callback_end( - SubGhzProtocolCommonEncoder* instance, - SubGhzProtocolCommonEncoderCallbackEnd callback_end, - void* context_end); - -/** Get count repeat left - * - * @param instance - SubGhzProtocolCommonEncoder instance - * @return count repeat left - */ -size_t subghz_encoder_common_get_repeat_left(SubGhzProtocolCommonEncoder* instance); - -/** Get LevelDuration this encoder step - * - * @param context - SubGhzProtocolCommonEncoder context - * @return LevelDuration this step - */ -LevelDuration subghz_protocol_encoder_common_yield(void* context); - -/** Add data bit to code_found - * - * @param common - SubGhzProtocolCommon common - * @param bit - add bit - */ -void subghz_protocol_common_add_bit(SubGhzProtocolCommon* common, uint8_t bit); - -/** Checking that the duration is included in the interval - * - * @param common - SubGhzProtocolCommon common - * @param duration duration reference - * @param duration_check duration checked - * @return true on success - */ -bool subghz_protocol_common_check_interval( - SubGhzProtocolCommon* common, - uint32_t duration, - uint16_t duration_check); - -/** Bit-by-bit data mirroring - * - * @param key - data to mirror - * @param count_bit number of data bits - * @return mirrored data - */ -uint64_t subghz_protocol_common_reverse_key(uint64_t key, uint8_t count_bit); - -/** Callback protocol - * - * @param instance - SubGhzProtocolCommon* instance - * @param callback - * @param context - */ -void subghz_protocol_common_set_callback( - SubGhzProtocolCommon* instance, - SubGhzProtocolCommonCallback callback, - void* context); - -/** outputting information from the parser - * - * @param instance - SubGhzProtocolCommon* instance - * @param output - output string - */ -void subghz_protocol_common_to_str(SubGhzProtocolCommon* instance, string_t output); - -/** Converting a string to a HEX array - * - * @param str - string data - * @param buff - uint8_t* buff - * @param len - size buff - * @return bool - */ -bool subghz_protocol_common_read_hex(string_t str, uint8_t* buff, uint16_t len); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolCommon instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_common_to_save_file( - SubGhzProtocolCommon* instance, - FlipperFormat* flipper_format); - -/** Loading data to a file - * - * @param instance - SubGhzProtocolCommon instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_common_to_load_protocol_from_file( - SubGhzProtocolCommon* instance, - FlipperFormat* flipper_format); diff --git a/lib/subghz/protocols/subghz_protocol_faac_slh.c b/lib/subghz/protocols/subghz_protocol_faac_slh.c deleted file mode 100644 index 310295d6..00000000 --- a/lib/subghz/protocols/subghz_protocol_faac_slh.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "subghz_protocol_faac_slh.h" - -struct SubGhzProtocolFaacSLH { - SubGhzProtocolCommon common; -}; - -typedef enum { - FaacSLHDecoderStepReset = 0, - FaacSLHDecoderStepFoundPreambula, - FaacSLHDecoderStepSaveDuration, - FaacSLHDecoderStepCheckDuration, -} FaacSLHDecoderStep; - -SubGhzProtocolFaacSLH* subghz_protocol_faac_slh_alloc(void) { - SubGhzProtocolFaacSLH* instance = malloc(sizeof(SubGhzProtocolFaacSLH)); - - instance->common.name = "Faac SLH"; - instance->common.code_min_count_bit_for_found = 64; - instance->common.te_short = 255; - instance->common.te_long = 595; - instance->common.te_delta = 100; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_faac_slh_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_faac_slh_to_load_protocol; - - return instance; -} - -void subghz_protocol_faac_slh_free(SubGhzProtocolFaacSLH* instance) { - furi_assert(instance); - free(instance); -} - -/** Send bit - * - * @param instance - SubGhzProtocolFaacSLH instance - * @param bit - bit - */ -void subghz_protocol_faac_slh_send_bit(SubGhzProtocolFaacSLH* instance, uint8_t bit) { - if(bit) { - //send bit 1 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_long); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short); - } else { - //send bit 0 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_long); - } -} - -void subghz_protocol_faac_slh_send_key( - SubGhzProtocolFaacSLH* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat) { - while(repeat--) { - SUBGHZ_TX_PIN_HIGH(); - //Send header - delay_us(instance->common.te_long * 2); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_long * 2); - //Send key data - for(uint8_t i = bit; i > 0; i--) { - subghz_protocol_faac_slh_send_bit(instance, bit_read(key, i - 1)); - } - } -} - -void subghz_protocol_faac_slh_reset(SubGhzProtocolFaacSLH* instance) { - instance->common.parser_step = FaacSLHDecoderStepReset; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolFaacSLH instance - */ -void subghz_protocol_faac_slh_check_remote_controller(SubGhzProtocolFaacSLH* instance) { - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - uint32_t code_fix = code_found_reverse & 0xFFFFFFFF; - //uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFF; - - instance->common.serial = code_fix & 0xFFFFFFF; - instance->common.btn = (code_fix >> 28) & 0x0F; -} - -void subghz_protocol_faac_slh_parse(SubGhzProtocolFaacSLH* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case FaacSLHDecoderStepReset: - if((level) && (DURATION_DIFF(duration, instance->common.te_long * 2) < - instance->common.te_delta * 3)) { - instance->common.parser_step = FaacSLHDecoderStepFoundPreambula; - } - break; - case FaacSLHDecoderStepFoundPreambula: - if((!level) && (DURATION_DIFF(duration, instance->common.te_long * 2) < - instance->common.te_delta * 3)) { - //Found Preambula - instance->common.parser_step = FaacSLHDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = FaacSLHDecoderStepReset; - } - break; - case FaacSLHDecoderStepSaveDuration: - if(level) { - if(duration >= (instance->common.te_short * 3 + instance->common.te_delta)) { - instance->common.parser_step = FaacSLHDecoderStepFoundPreambula; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = FaacSLHDecoderStepCheckDuration; - } - - } else { - instance->common.parser_step = FaacSLHDecoderStepReset; - } - break; - case FaacSLHDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = FaacSLHDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = FaacSLHDecoderStepSaveDuration; - } else { - instance->common.parser_step = FaacSLHDecoderStepReset; - } - } else { - instance->common.parser_step = FaacSLHDecoderStepReset; - } - break; - } -} - -void subghz_protocol_faac_slh_to_str(SubGhzProtocolFaacSLH* instance, string_t output) { - subghz_protocol_faac_slh_check_remote_controller(instance); - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - uint32_t code_fix = code_found_reverse & 0xFFFFFFFF; - uint32_t code_hop = (code_found_reverse >> 32) & 0xFFFFFFFF; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:%lX%08lX\r\n" - "Fix:%08lX \r\n" - "Hop:%08lX \r\n" - "Sn:%07lX Btn:%lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found >> 32), - (uint32_t)instance->common.code_last_found, - code_fix, - code_hop, - instance->common.serial, - instance->common.btn); -} - -void subghz_decoder_faac_slh_to_load_protocol(SubGhzProtocolFaacSLH* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_faac_slh_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_faac_slh.h b/lib/subghz/protocols/subghz_protocol_faac_slh.h deleted file mode 100644 index 80528711..00000000 --- a/lib/subghz/protocols/subghz_protocol_faac_slh.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolFaacSLH SubGhzProtocolFaacSLH; - -/** Allocate SubGhzProtocolFaacSLH - * - * @return SubGhzProtocolFaacSLH* - */ -SubGhzProtocolFaacSLH* subghz_protocol_faac_slh_alloc(); - -/** Free SubGhzProtocolFaacSLH - * - * @param instance - */ -void subghz_protocol_faac_slh_free(SubGhzProtocolFaacSLH* instance); - -/** Sends the key on the air - * - * @param instance - SubGhzProtocolFaacSLH instance - * @param key - key send - * @param bit - count bit key - * @param repeat - repeat send key - */ -void subghz_protocol_faac_slh_send_key( - SubGhzProtocolFaacSLH* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat); - -/** Reset internal state - * @param instance - SubGhzProtocolFaacSLH instance - */ -void subghz_protocol_faac_slh_reset(SubGhzProtocolFaacSLH* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolFaacSLH instance - */ -void subghz_protocol_faac_slh_check_remote_controller(SubGhzProtocolFaacSLH* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolFaacSLH instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_faac_slh_parse(SubGhzProtocolFaacSLH* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolFaacSLH* instance - * @param output - output string - */ -void subghz_protocol_faac_slh_to_str(SubGhzProtocolFaacSLH* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolFaacSLH instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_faac_slh_to_load_protocol(SubGhzProtocolFaacSLH* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_gate_tx.c b/lib/subghz/protocols/subghz_protocol_gate_tx.c deleted file mode 100644 index 58736817..00000000 --- a/lib/subghz/protocols/subghz_protocol_gate_tx.c +++ /dev/null @@ -1,195 +0,0 @@ -#include "subghz_protocol_gate_tx.h" - -struct SubGhzProtocolGateTX { - SubGhzProtocolCommon common; -}; - -typedef enum { - GateTXDecoderStepReset = 0, - GateTXDecoderStepFoundStartBit, - GateTXDecoderStepSaveDuration, - GateTXDecoderStepCheckDuration, -} GateTXDecoderStep; - -SubGhzProtocolGateTX* subghz_protocol_gate_tx_alloc(void) { - SubGhzProtocolGateTX* instance = malloc(sizeof(SubGhzProtocolGateTX)); - - instance->common.name = "GateTX"; - instance->common.code_min_count_bit_for_found = 24; - instance->common.te_short = 350; - instance->common.te_long = 700; - instance->common.te_delta = 100; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_gate_tx_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_gate_tx_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_gate_tx_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_gate_tx_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_gate_tx_send_key; - return instance; -} - -void subghz_protocol_gate_tx_free(SubGhzProtocolGateTX* instance) { - furi_assert(instance); - free(instance); -} - -bool subghz_protocol_gate_tx_send_key( - SubGhzProtocolGateTX* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - size_t index = 0; - encoder->size_upload = (instance->common.code_last_count_bit * 2) + 2; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - //Send header - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 49); - //Send start bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_long); - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - } - } - return true; -} - -void subghz_protocol_gate_tx_reset(SubGhzProtocolGateTX* instance) { - instance->common.parser_step = GateTXDecoderStepReset; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolFaacSLH instance - */ -void subghz_protocol_gate_tx_check_remote_controller(SubGhzProtocolGateTX* instance) { - uint32_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - instance->common.serial = (code_found_reverse & 0xFF) << 12 | - ((code_found_reverse >> 8) & 0xFF) << 4 | - ((code_found_reverse >> 20) & 0x0F); - instance->common.btn = ((code_found_reverse >> 16) & 0x0F); -} - -void subghz_protocol_gate_tx_parse(SubGhzProtocolGateTX* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case GateTXDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 47) < - instance->common.te_delta * 47)) { - //Found Preambula - instance->common.parser_step = GateTXDecoderStepFoundStartBit; - } - break; - case GateTXDecoderStepFoundStartBit: - if(level && - ((DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta * 3))) { - //Found start bit - instance->common.parser_step = GateTXDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = GateTXDecoderStepReset; - } - break; - case GateTXDecoderStepSaveDuration: - if(!level) { - if(duration >= (instance->common.te_short * 10 + instance->common.te_delta)) { - instance->common.parser_step = GateTXDecoderStepFoundStartBit; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = GateTXDecoderStepCheckDuration; - } - } - break; - case GateTXDecoderStepCheckDuration: - if(level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < - instance->common.te_delta * 3)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = GateTXDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta * 3) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = GateTXDecoderStepSaveDuration; - } else { - instance->common.parser_step = GateTXDecoderStepReset; - } - } else { - instance->common.parser_step = GateTXDecoderStepReset; - } - break; - } -} - -void subghz_protocol_gate_tx_to_str(SubGhzProtocolGateTX* instance, string_t output) { - subghz_protocol_gate_tx_check_remote_controller(instance); - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:%06lX\r\n" - "Sn:%05lX Btn:%lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found & 0xFFFFFF), - instance->common.serial, - instance->common.btn); -} - -bool subghz_protocol_gate_tx_to_save_file( - SubGhzProtocolGateTX* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_gate_tx_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolGateTX* instance, - const char* file_path) { - if(subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format)) { - subghz_protocol_gate_tx_check_remote_controller(instance); - return true; - } - return false; -} - -void subghz_decoder_gate_tx_to_load_protocol(SubGhzProtocolGateTX* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_gate_tx_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_gate_tx.h b/lib/subghz/protocols/subghz_protocol_gate_tx.h deleted file mode 100644 index a72346c8..00000000 --- a/lib/subghz/protocols/subghz_protocol_gate_tx.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolGateTX SubGhzProtocolGateTX; - -/** Allocate SubGhzProtocolGateTX - * - * @return SubGhzProtocolGateTX* - */ -SubGhzProtocolGateTX* subghz_protocol_gate_tx_alloc(); - -/** Free SubGhzProtocolGateTX - * - * @param instance - */ -void subghz_protocol_gate_tx_free(SubGhzProtocolGateTX* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolGateTX instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_gate_tx_send_key( - SubGhzProtocolGateTX* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolGateTX instance - */ -void subghz_protocol_gate_tx_reset(SubGhzProtocolGateTX* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolGateTX instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_gate_tx_parse(SubGhzProtocolGateTX* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolFaacSLH* instance - * @param output - output string - */ -void subghz_protocol_gate_tx_to_str(SubGhzProtocolGateTX* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolGateTX instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_gate_tx_to_save_file( - SubGhzProtocolGateTX* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolGateTX instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_gate_tx_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolGateTX* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolGateTX instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_gate_tx_to_load_protocol(SubGhzProtocolGateTX* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_hormann.c b/lib/subghz/protocols/subghz_protocol_hormann.c deleted file mode 100644 index 97363002..00000000 --- a/lib/subghz/protocols/subghz_protocol_hormann.c +++ /dev/null @@ -1,209 +0,0 @@ -#include "subghz_protocol_hormann.h" -#include "subghz_protocol_common.h" - -struct SubGhzProtocolHormann { - SubGhzProtocolCommon common; -}; - -typedef enum { - HormannDecoderStepReset = 0, - HormannDecoderStepFoundStartHeader, - HormannDecoderStepFoundHeader, - HormannDecoderStepFoundStartBit, - HormannDecoderStepSaveDuration, - HormannDecoderStepCheckDuration, -} HormannDecoderStep; - -SubGhzProtocolHormann* subghz_protocol_hormann_alloc() { - SubGhzProtocolHormann* instance = malloc(sizeof(SubGhzProtocolHormann)); - - instance->common.name = "Hormann HSM"; - instance->common.code_min_count_bit_for_found = 44; - instance->common.te_short = 511; - instance->common.te_long = 1022; - instance->common.te_delta = 200; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_hormann_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_hormann_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_hormann_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_hormann_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_hormann_send_key; - - return instance; -} - -void subghz_protocol_hormann_free(SubGhzProtocolHormann* instance) { - furi_assert(instance); - free(instance); -} - -bool subghz_protocol_hormann_send_key( - SubGhzProtocolHormann* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - - size_t index = 0; - encoder->size_upload = 3 + (instance->common.code_last_count_bit * 2 + 2) * 20 + 1; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - //Send header - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 64); - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short * 64); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 64); - encoder->repeat = 10; - - for(size_t repeat = 0; repeat < 20; repeat++) { - //Send start bit - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short * 24); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - } - } - } - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short * 24); - return true; -} - -void subghz_protocol_hormann_reset(SubGhzProtocolHormann* instance) { - instance->common.parser_step = HormannDecoderStepReset; -} - -void subghz_protocol_hormann_parse(SubGhzProtocolHormann* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case HormannDecoderStepReset: - if((level) && (DURATION_DIFF(duration, instance->common.te_short * 64) < - instance->common.te_delta * 64)) { - instance->common.parser_step = HormannDecoderStepFoundStartHeader; - } - break; - case HormannDecoderStepFoundStartHeader: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 64) < - instance->common.te_delta * 64)) { - instance->common.parser_step = HormannDecoderStepFoundHeader; - } else { - instance->common.parser_step = HormannDecoderStepReset; - } - break; - case HormannDecoderStepFoundHeader: - if((level) && (DURATION_DIFF(duration, instance->common.te_short * 24) < - instance->common.te_delta * 24)) { - instance->common.parser_step = HormannDecoderStepFoundStartBit; - } else { - instance->common.parser_step = HormannDecoderStepReset; - } - break; - case HormannDecoderStepFoundStartBit: - if((!level) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - instance->common.parser_step = HormannDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = HormannDecoderStepReset; - } - break; - case HormannDecoderStepSaveDuration: - if(level) { //save interval - if(duration >= (instance->common.te_short * 5)) { - instance->common.parser_step = HormannDecoderStepFoundStartBit; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.serial = 0x0; - instance->common.btn = 0x0; - - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - break; - } - instance->common.te_last = duration; - instance->common.parser_step = HormannDecoderStepCheckDuration; - } else { - instance->common.parser_step = HormannDecoderStepReset; - } - break; - case HormannDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = HormannDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = HormannDecoderStepSaveDuration; - } else - instance->common.parser_step = HormannDecoderStepReset; - } else { - instance->common.parser_step = HormannDecoderStepReset; - } - break; - } -} - -void subghz_protocol_hormann_to_str(SubGhzProtocolHormann* instance, string_t output) { - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - instance->common.btn = (instance->common.code_last_found >> 4) & 0xF; - - string_cat_printf( - output, - "%s\r\n" - "%dbit\r\n" - "Key:0x%03lX%08lX\r\n" - "Btn:0x%01X", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->common.btn); -} - -bool subghz_protocol_hormann_to_save_file( - SubGhzProtocolHormann* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_hormann_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolHormann* instance, - const char* file_path) { - return subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); -} - -void subghz_decoder_hormann_to_load_protocol(SubGhzProtocolHormann* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; -} diff --git a/lib/subghz/protocols/subghz_protocol_hormann.h b/lib/subghz/protocols/subghz_protocol_hormann.h deleted file mode 100644 index 65b80e08..00000000 --- a/lib/subghz/protocols/subghz_protocol_hormann.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolHormann SubGhzProtocolHormann; - -/** Allocate SubGhzProtocolHormann - * - * @return SubGhzProtocolHormann* - */ -SubGhzProtocolHormann* subghz_protocol_hormann_alloc(); - -/** Free SubGhzProtocolHormann - * - * @param instance - */ -void subghz_protocol_hormann_free(SubGhzProtocolHormann* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolHormann instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_hormann_send_key( - SubGhzProtocolHormann* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolHormann instance - */ -void subghz_protocol_hormann_reset(SubGhzProtocolHormann* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolHormann instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_hormann_parse(SubGhzProtocolHormann* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolHormann* instance - * @param output - output string - */ -void subghz_protocol_hormann_to_str(SubGhzProtocolHormann* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolHormann instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_hormann_to_save_file( - SubGhzProtocolHormann* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolHormann instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_hormann_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolHormann* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolHormann instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_hormann_to_load_protocol(SubGhzProtocolHormann* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_ido.c b/lib/subghz/protocols/subghz_protocol_ido.c deleted file mode 100644 index 5db8d085..00000000 --- a/lib/subghz/protocols/subghz_protocol_ido.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "subghz_protocol_ido.h" - -struct SubGhzProtocolIDo { - SubGhzProtocolCommon common; -}; - -typedef enum { - IDoDecoderStepReset = 0, - IDoDecoderStepFoundPreambula, - IDoDecoderStepSaveDuration, - IDoDecoderStepCheckDuration, -} IDoDecoderStep; - -SubGhzProtocolIDo* subghz_protocol_ido_alloc(void) { - SubGhzProtocolIDo* instance = malloc(sizeof(SubGhzProtocolIDo)); - - instance->common.name = "iDo 117/111"; // PT4301-X"; - instance->common.code_min_count_bit_for_found = 48; - instance->common.te_short = 450; - instance->common.te_long = 1450; - instance->common.te_delta = 150; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_ido_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_ido_to_load_protocol; - - return instance; -} - -void subghz_protocol_ido_free(SubGhzProtocolIDo* instance) { - furi_assert(instance); - free(instance); -} - -/** Send bit - * - * @param instance - SubGhzProtocolIDo instance - * @param bit - bit - */ -void subghz_protocol_ido_send_bit(SubGhzProtocolIDo* instance, uint8_t bit) { - if(bit) { - //send bit 1 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short); - } else { - //send bit 0 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_long); - } -} - -void subghz_protocol_ido_send_key( - SubGhzProtocolIDo* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat) { - while(repeat--) { - SUBGHZ_TX_PIN_HIGH(); - //Send header - delay_us(instance->common.te_short * 10); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short * 10); - //Send key data - for(uint8_t i = bit; i > 0; i--) { - subghz_protocol_ido_send_bit(instance, bit_read(key, i - 1)); - } - } -} - -void subghz_protocol_ido_reset(SubGhzProtocolIDo* instance) { - instance->common.parser_step = IDoDecoderStepReset; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolIDo instance - */ -void subghz_protocol_ido_check_remote_controller(SubGhzProtocolIDo* instance) { - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - uint32_t code_fix = code_found_reverse & 0xFFFFFF; - - instance->common.serial = code_fix & 0xFFFFF; - instance->common.btn = (code_fix >> 20) & 0x0F; -} - -void subghz_protocol_ido_parse(SubGhzProtocolIDo* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case IDoDecoderStepReset: - if((level) && (DURATION_DIFF(duration, instance->common.te_short * 10) < - instance->common.te_delta * 5)) { - instance->common.parser_step = IDoDecoderStepFoundPreambula; - } - break; - case IDoDecoderStepFoundPreambula: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 10) < - instance->common.te_delta * 5)) { - //Found Preambula - instance->common.parser_step = IDoDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = IDoDecoderStepReset; - } - break; - case IDoDecoderStepSaveDuration: - if(level) { - if(duration >= (instance->common.te_short * 5 + instance->common.te_delta)) { - instance->common.parser_step = IDoDecoderStepFoundPreambula; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = IDoDecoderStepCheckDuration; - } - - } else { - instance->common.parser_step = IDoDecoderStepReset; - } - break; - case IDoDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < - instance->common.te_delta * 3)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = IDoDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta * 3) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = IDoDecoderStepSaveDuration; - } else { - instance->common.parser_step = IDoDecoderStepReset; - } - } else { - instance->common.parser_step = IDoDecoderStepReset; - } - break; - } -} - -void subghz_protocol_ido_to_str(SubGhzProtocolIDo* instance, string_t output) { - subghz_protocol_ido_check_remote_controller(instance); - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - uint32_t code_fix = code_found_reverse & 0xFFFFFF; - uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFFF; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Fix:%06lX \r\n" - "Hop:%06lX \r\n" - "Sn:%05lX Btn:%lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found >> 32), - (uint32_t)instance->common.code_last_found, - code_fix, - code_hop, - instance->common.serial, - instance->common.btn); -} - -void subghz_decoder_ido_to_load_protocol(SubGhzProtocolIDo* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_ido_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_ido.h b/lib/subghz/protocols/subghz_protocol_ido.h deleted file mode 100644 index 298c398c..00000000 --- a/lib/subghz/protocols/subghz_protocol_ido.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolIDo SubGhzProtocolIDo; - -/** Allocate SubGhzProtocolIDo - * - * @return SubGhzProtocolIDo* - */ -SubGhzProtocolIDo* subghz_protocol_ido_alloc(); - -/** Free SubGhzProtocolIDo - * - * @param instance - */ -void subghz_protocol_ido_free(SubGhzProtocolIDo* instance); - -/** Sends the key on the air - * - * @param instance - SubGhzProtocolIDo instance - * @param key - key send - * @param bit - count bit key - * @param repeat - repeat send key - */ -void subghz_protocol_ido_send_key( - SubGhzProtocolIDo* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat); - -/** Reset internal state - * @param instance - SubGhzProtocolIDo instance - */ -void subghz_protocol_ido_reset(SubGhzProtocolIDo* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolIDo instance - */ -void subghz_protocol_ido_check_remote_controller(SubGhzProtocolIDo* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolIDo instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_ido_parse(SubGhzProtocolIDo* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolIDo* instance - * @param output - output string - */ -void subghz_protocol_ido_to_str(SubGhzProtocolIDo* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolIDo instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_ido_to_load_protocol(SubGhzProtocolIDo* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_keeloq.c b/lib/subghz/protocols/subghz_protocol_keeloq.c deleted file mode 100644 index a5f19c95..00000000 --- a/lib/subghz/protocols/subghz_protocol_keeloq.c +++ /dev/null @@ -1,493 +0,0 @@ -#include "subghz_protocol_keeloq.h" -#include "subghz_protocol_keeloq_common.h" - -#include "../subghz_keystore.h" - -#include - -#include - -struct SubGhzProtocolKeeloq { - SubGhzProtocolCommon common; - SubGhzKeystore* keystore; - const char* manufacture_name; -}; - -typedef enum { - KeeloqDecoderStepReset = 0, - KeeloqDecoderStepCheckPreambula, - KeeloqDecoderStepSaveDuration, - KeeloqDecoderStepCheckDuration, -} KeeloqDecoderStep; - -SubGhzProtocolKeeloq* subghz_protocol_keeloq_alloc(SubGhzKeystore* keystore) { - SubGhzProtocolKeeloq* instance = malloc(sizeof(SubGhzProtocolKeeloq)); - - instance->keystore = keystore; - - instance->common.name = "KeeLoq"; - instance->common.code_min_count_bit_for_found = 64; - instance->common.te_short = 400; - instance->common.te_long = 800; - instance->common.te_delta = 140; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_keeloq_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_keeloq_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_keeloq_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_keeloq_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_keeloq_send_key; - - return instance; -} - -void subghz_protocol_keeloq_free(SubGhzProtocolKeeloq* instance) { - furi_assert(instance); - free(instance); -} - -static inline bool subghz_protocol_keeloq_check_decrypt( - SubGhzProtocolKeeloq* instance, - uint32_t decrypt, - uint8_t btn, - uint32_t end_serial) { - furi_assert(instance); - if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) || - ((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) { - instance->common.cnt = decrypt & 0x0000FFFF; - return true; - } - return false; -} - -/** Checking the accepted code against the database manafacture key - * - * @param instance SubGhzProtocolKeeloq instance - * @param fix fix part of the parcel - * @param hop hop encrypted part of the parcel - * @return true on successful search - */ -uint8_t subghz_protocol_keeloq_check_remote_controller_selector( - SubGhzProtocolKeeloq* instance, - uint32_t fix, - uint32_t hop) { - // protocol HCS300 uses 10 bits in discriminator, HCS200 uses 8 bits, for backward compatibility, we are looking for the 8-bit pattern - // HCS300 -> uint16_t end_serial = (uint16_t)(fix & 0x3FF); - // HCS200 -> uint16_t end_serial = (uint16_t)(fix & 0xFF); - - uint16_t end_serial = (uint16_t)(fix & 0xFF); - uint8_t btn = (uint8_t)(fix >> 28); - uint32_t decrypt = 0; - uint64_t man_learning; - uint32_t seed = 0; - - for - M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - break; - case KEELOQ_LEARNING_NORMAL: - // Normal Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - break; - case KEELOQ_LEARNING_SECURE: - man_learning = subghz_protocol_keeloq_common_secure_learning( - fix, seed, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - break; - case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: - man_learning = subghz_protocol_keeloq_common_magic_xor_type1_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - break; - case KEELOQ_LEARNING_UNKNOWN: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - // Check for mirrored man - uint64_t man_rev = 0; - uint64_t man_rev_byte = 0; - for(uint8_t i = 0; i < 64; i += 8) { - man_rev_byte = (uint8_t)(manufacture_code->key >> i); - man_rev = man_rev | man_rev_byte << (56 - i); - } - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - //########################### - // Normal Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - - // Check for mirrored man - man_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - - // Secure Learning - man_learning = subghz_protocol_keeloq_common_secure_learning( - fix, seed, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - - // Check for mirrored man - man_learning = subghz_protocol_keeloq_common_secure_learning(fix, seed, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - - // Magic xor type1 learning - man_learning = subghz_protocol_keeloq_common_magic_xor_type1_learning( - fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - - // Check for mirrored man - man_learning = - subghz_protocol_keeloq_common_magic_xor_type1_learning(fix, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_learning); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - return 1; - } - break; - } - } - - instance->manufacture_name = "Unknown"; - instance->common.cnt = 0; - - return 0; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolKeeloq instance - */ -void subghz_protocol_keeloq_check_remote_controller(SubGhzProtocolKeeloq* instance) { - uint64_t key = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - uint32_t key_fix = key >> 32; - uint32_t key_hop = key & 0x00000000ffffffff; - // Check key AN-Motors - if((key_hop >> 24) == ((key_hop >> 16) & 0x00ff) && - (key_fix >> 28) == ((key_hop >> 12) & 0x0f) && (key_hop & 0xFFF) == 0x404) { - instance->manufacture_name = "AN-Motors"; - instance->common.cnt = key_hop >> 16; - } else if((key_hop & 0xFFF) == (0x000) && (key_fix >> 28) == ((key_hop >> 12) & 0x0f)) { - instance->manufacture_name = "HCS101"; - instance->common.cnt = key_hop >> 16; - } else { - subghz_protocol_keeloq_check_remote_controller_selector(instance, key_fix, key_hop); - } - - instance->common.serial = key_fix & 0x0FFFFFFF; - instance->common.btn = key_fix >> 28; -} - -const char* subghz_protocol_keeloq_find_and_get_manufacture_name(void* context) { - SubGhzProtocolKeeloq* instance = context; - subghz_protocol_keeloq_check_remote_controller(instance); - return instance->manufacture_name; -} - -const char* subghz_protocol_keeloq_get_manufacture_name(void* context) { - SubGhzProtocolKeeloq* instance = context; - return instance->manufacture_name; -} - -bool subghz_protocol_keeloq_set_manufacture_name(void* context, const char* manufacture_name) { - SubGhzProtocolKeeloq* instance = context; - instance->manufacture_name = manufacture_name; - int res = 0; - for - M_EACH( - manufacture_code, - *subghz_keystore_get_data(instance->keystore), - SubGhzKeyArray_t) { - res = strcmp(string_get_cstr(manufacture_code->name), instance->manufacture_name); - if(res == 0) return true; - } - instance->manufacture_name = "Unknown"; - return false; -} - -uint64_t subghz_protocol_keeloq_gen_key(void* context) { - SubGhzProtocolKeeloq* instance = context; - uint32_t fix = instance->common.btn << 28 | instance->common.serial; - uint32_t decrypt = instance->common.btn << 28 | (instance->common.serial & 0x3FF) << 16 | - instance->common.cnt; - uint32_t hop = 0; - uint64_t man_learning = 0; - int res = 0; - - for - M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - res = strcmp(string_get_cstr(manufacture_code->name), instance->manufacture_name); - if(res == 0) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - //Simple Learning - hop = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); - break; - case KEELOQ_LEARNING_NORMAL: - //Simple Learning - man_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man_learning); - break; - case KEELOQ_LEARNING_UNKNOWN: - hop = 0; //todo - break; - } - break; - } - } - uint64_t yek = (uint64_t)fix << 32 | hop; - return subghz_protocol_common_reverse_key(yek, instance->common.code_last_count_bit); -} - -bool subghz_protocol_keeloq_send_key( - SubGhzProtocolKeeloq* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - - //gen new key - instance->common.cnt++; - instance->common.code_last_found = subghz_protocol_keeloq_gen_key(instance); - if(instance->common.callback) - instance->common.callback((SubGhzProtocolCommon*)instance, instance->common.context); - - if(!strcmp(instance->manufacture_name, "Unknown")) { - return false; - } - - size_t index = 0; - encoder->size_upload = 11 * 2 + 2 + (instance->common.code_last_count_bit * 2) + 4; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - - //Send header - for(uint8_t i = 11; i > 0; i--) { - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - } - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 10); - - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - } - } - // +send 2 status bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_long); - - //encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_long); - //encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - - // send end - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 40); - - return true; -} - -void subghz_protocol_keeloq_reset(SubGhzProtocolKeeloq* instance) { - instance->common.parser_step = KeeloqDecoderStepReset; -} - -void subghz_protocol_keeloq_parse(SubGhzProtocolKeeloq* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case KeeloqDecoderStepReset: - if((level) && - DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - instance->common.parser_step = KeeloqDecoderStepCheckPreambula; - instance->common.header_count++; - } - break; - case KeeloqDecoderStepCheckPreambula: - if((!level) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - instance->common.parser_step = KeeloqDecoderStepReset; - break; - } - if((instance->common.header_count > 2) && - (DURATION_DIFF(duration, instance->common.te_short * 10) < - instance->common.te_delta * 10)) { - // Found header - instance->common.parser_step = KeeloqDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = KeeloqDecoderStepReset; - instance->common.header_count = 0; - } - break; - case KeeloqDecoderStepSaveDuration: - if(level) { - instance->common.te_last = duration; - instance->common.parser_step = KeeloqDecoderStepCheckDuration; - } - break; - case KeeloqDecoderStepCheckDuration: - if(!level) { - if(duration >= (instance->common.te_short * 2 + instance->common.te_delta)) { - // Found end TX - instance->common.parser_step = KeeloqDecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - if(instance->common.code_last_found != instance->common.code_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->common.header_count = 0; - } - break; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - if(instance->common.code_count_bit < - instance->common.code_min_count_bit_for_found) { - subghz_protocol_common_add_bit(&instance->common, 1); - } - instance->common.parser_step = KeeloqDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - if(instance->common.code_count_bit < - instance->common.code_min_count_bit_for_found) { - subghz_protocol_common_add_bit(&instance->common, 0); - } - instance->common.parser_step = KeeloqDecoderStepSaveDuration; - } else { - instance->common.parser_step = KeeloqDecoderStepReset; - instance->common.header_count = 0; - } - } else { - instance->common.parser_step = KeeloqDecoderStepReset; - instance->common.header_count = 0; - } - break; - } -} - -void subghz_protocol_keeloq_to_str(SubGhzProtocolKeeloq* instance, string_t output) { - subghz_protocol_keeloq_check_remote_controller(instance); - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_hi = code_found_reverse >> 32; - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:%08lX%08lX\r\n" - "Fix:0x%08lX Cnt:%04X\r\n" - "Hop:0x%08lX Btn:%02lX\r\n" - "MF:%s\r\n" - "Sn:0x%07lX \r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - code_found_reverse_hi, - instance->common.cnt, - code_found_reverse_lo, - instance->common.btn, - instance->manufacture_name, - instance->common.serial); -} - -bool subghz_protocol_keeloq_to_save_file( - SubGhzProtocolKeeloq* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_keeloq_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolKeeloq* instance, - const char* file_path) { - return subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); -} - -void subghz_decoder_keeloq_to_load_protocol(SubGhzProtocolKeeloq* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_keeloq_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_keeloq.h b/lib/subghz/protocols/subghz_protocol_keeloq.h deleted file mode 100644 index 195f0b47..00000000 --- a/lib/subghz/protocols/subghz_protocol_keeloq.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzKeystore SubGhzKeystore; - -typedef struct SubGhzProtocolKeeloq SubGhzProtocolKeeloq; - -/** Allocate SubGhzProtocolKeeloq - * - * @return SubGhzProtocolKeeloq* - */ -SubGhzProtocolKeeloq* subghz_protocol_keeloq_alloc(SubGhzKeystore* keystore); - -/** Free SubGhzProtocolKeeloq - * - * @param instance - */ -void subghz_protocol_keeloq_free(SubGhzProtocolKeeloq* instance); - -/** Find and get manufacture name - * - * @param context - SubGhzProtocolKeeloq context - * @return name - char* manufacture name - */ -const char* subghz_protocol_keeloq_find_and_get_manufacture_name(void* context); - -/** Get manufacture name - * - * @param context - SubGhzProtocolKeeloq context - * @return name - char* manufacture name - */ -const char* subghz_protocol_keeloq_get_manufacture_name(void* context); - -/** Set manufacture name - * - * @param manufacture_name - manufacture name - * @param context - SubGhzProtocolKeeloq context - * @return bool - */ -bool subghz_protocol_keeloq_set_manufacture_name(void* context, const char* manufacture_name); - -/** Get key keeloq - * - * @param context - SubGhzProtocolKeeloq context - * @return key - */ -uint64_t subghz_protocol_keeloq_gen_key(void* context); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolKeeloq instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_keeloq_send_key( - SubGhzProtocolKeeloq* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolKeeloq instance - */ -void subghz_protocol_keeloq_reset(SubGhzProtocolKeeloq* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolKeeloq instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_keeloq_parse(SubGhzProtocolKeeloq* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolKeeloq* instance - * @param output - output string - */ -void subghz_protocol_keeloq_to_str(SubGhzProtocolKeeloq* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolKeeloq instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_keeloq_to_save_file( - SubGhzProtocolKeeloq* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolKeeloq instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_keeloq_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolKeeloq* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolKeeloq instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_keeloq_to_load_protocol(SubGhzProtocolKeeloq* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_kia.c b/lib/subghz/protocols/subghz_protocol_kia.c deleted file mode 100644 index 3eecb770..00000000 --- a/lib/subghz/protocols/subghz_protocol_kia.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "subghz_protocol_kia.h" - -struct SubGhzProtocolKIA { - SubGhzProtocolCommon common; -}; - -typedef enum { - KIADecoderStepReset = 0, - KIADecoderStepCheckPreambula, - KIADecoderStepSaveDuration, - KIADecoderStepCheckDuration, -} KIADecoderStep; - -SubGhzProtocolKIA* subghz_protocol_kia_alloc(void) { - SubGhzProtocolKIA* instance = malloc(sizeof(SubGhzProtocolKIA)); - - instance->common.name = "KIA"; - instance->common.code_min_count_bit_for_found = 60; - instance->common.te_short = 250; - instance->common.te_long = 500; - instance->common.te_delta = 100; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_kia_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_kia_to_load_protocol; - - return instance; -} - -void subghz_protocol_kia_free(SubGhzProtocolKIA* instance) { - furi_assert(instance); - free(instance); -} - -void subghz_protocol_kia_reset(SubGhzProtocolKIA* instance) { - instance->common.parser_step = KIADecoderStepReset; -} - -uint8_t subghz_protocol_kia_crc8(uint8_t* data, size_t len) { - uint8_t crc = 0x08; - size_t i, j; - for(i = 0; i < len; i++) { - crc ^= data[i]; - for(j = 0; j < 8; j++) { - if((crc & 0x80) != 0) - crc = (uint8_t)((crc << 1) ^ 0x7F); - else - crc <<= 1; - } - } - return crc; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolKIA instance - */ -void subghz_protocol_kia_check_remote_controller(SubGhzProtocolKIA* instance) { - /* - * 0x0F 0112 43B04EC 1 7D - * 0x0F 0113 43B04EC 1 DF - * 0x0F 0114 43B04EC 1 30 - * 0x0F 0115 43B04EC 2 13 - * 0x0F 0116 43B04EC 3 F5 - * CNT Serial K CRC8 Kia (CRC8, poly 0x7f, start_crc 0x08) - */ - - instance->common.serial = (uint32_t)((instance->common.code_last_found >> 12) & 0x0FFFFFFF); - instance->common.btn = (instance->common.code_last_found >> 8) & 0x0F; - instance->common.cnt = (instance->common.code_last_found >> 40) & 0xFFFF; -} - -void subghz_protocol_kia_parse(SubGhzProtocolKIA* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case KIADecoderStepReset: - if((!level) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - instance->common.parser_step = KIADecoderStepCheckPreambula; - instance->common.te_last = duration; - instance->common.header_count = 0; - } - break; - case KIADecoderStepCheckPreambula: - if(!level) { - if((DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) || - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - instance->common.te_last = duration; - } else { - instance->common.parser_step = KIADecoderStepReset; - } - } else if( - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) && - (DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta)) { - // Found header - instance->common.header_count++; - break; - } else if( - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) && - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta)) { - // Found start bit - if(instance->common.header_count > 15) { - instance->common.parser_step = KIADecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 1; - subghz_protocol_common_add_bit(&instance->common, 1); - } else { - instance->common.parser_step = KIADecoderStepReset; - } - } else { - instance->common.parser_step = KIADecoderStepReset; - } - break; - case KIADecoderStepSaveDuration: - if(!level) { - if(duration >= (instance->common.te_long + instance->common.te_delta * 2)) { - //Found stop bit - instance->common.parser_step = KIADecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = KIADecoderStepCheckDuration; - } - - } else { - instance->common.parser_step = KIADecoderStepReset; - } - break; - case KIADecoderStepCheckDuration: - if(level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = KIADecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = KIADecoderStepSaveDuration; - } else { - instance->common.parser_step = KIADecoderStepReset; - } - } else { - instance->common.parser_step = KIADecoderStepReset; - } - break; - } -} - -void subghz_protocol_kia_to_str(SubGhzProtocolKIA* instance, string_t output) { - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:%08lX%08lX\r\n" - "Sn:%07lX Btn:%lX Cnt:%04X\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->common.serial, - instance->common.btn, - instance->common.cnt); -} - -void subghz_decoder_kia_to_load_protocol(SubGhzProtocolKIA* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_kia_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_kia.h b/lib/subghz/protocols/subghz_protocol_kia.h deleted file mode 100644 index bc672537..00000000 --- a/lib/subghz/protocols/subghz_protocol_kia.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolKIA SubGhzProtocolKIA; - -/** Allocate SubGhzProtocolKIA - * - * @return SubGhzProtocolKIA* - */ -SubGhzProtocolKIA* subghz_protocol_kia_alloc(); - -/** Free SubGhzProtocolKIA - * - * @param instance - */ -void subghz_protocol_kia_free(SubGhzProtocolKIA* instance); - -/** Reset internal state - * @param instance - SubGhzProtocolKIA instance - */ -void subghz_protocol_kia_reset(SubGhzProtocolKIA* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolKIA instance - */ -void subghz_protocol_kia_check_remote_controller(SubGhzProtocolKIA* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolKIA instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_kia_parse(SubGhzProtocolKIA* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolKIA* instance - * @param output - output string - */ -void subghz_protocol_kia_to_str(SubGhzProtocolKIA* instance, string_t output); - -/** Get a string to save the protocol - * - * @param instance - SubGhzProtocolKIA instance - * @param output - the resulting string - */ -void subghz_protocol_kia_to_save_str(SubGhzProtocolKIA* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolKIA instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_kia_to_load_protocol(SubGhzProtocolKIA* instance, void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_nero_radio.c b/lib/subghz/protocols/subghz_protocol_nero_radio.c deleted file mode 100644 index 3b2fb1d7..00000000 --- a/lib/subghz/protocols/subghz_protocol_nero_radio.c +++ /dev/null @@ -1,232 +0,0 @@ -#include "subghz_protocol_nero_radio.h" - -struct SubGhzProtocolNeroRadio { - SubGhzProtocolCommon common; -}; - -typedef enum { - NeroRadioDecoderStepReset = 0, - NeroRadioDecoderStepCheckPreambula, - NeroRadioDecoderStepSaveDuration, - NeroRadioDecoderStepCheckDuration, -} NeroRadioDecoderStep; - -SubGhzProtocolNeroRadio* subghz_protocol_nero_radio_alloc(void) { - SubGhzProtocolNeroRadio* instance = malloc(sizeof(SubGhzProtocolNeroRadio)); - - instance->common.name = "Nero Radio"; - instance->common.code_min_count_bit_for_found = 55; - instance->common.te_short = 200; - instance->common.te_long = 400; - instance->common.te_delta = 80; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_nero_radio_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_nero_radio_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_nero_radio_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_nero_radio_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_nero_radio_send_key; - - return instance; -} - -void subghz_protocol_nero_radio_free(SubGhzProtocolNeroRadio* instance) { - furi_assert(instance); - free(instance); -} - -bool subghz_protocol_nero_radio_send_key( - SubGhzProtocolNeroRadio* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - size_t index = 0; - encoder->size_upload = 2 + 47 * 2 + 2 + (instance->common.code_last_count_bit * 2); - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - - //Send header - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 37); - for(uint8_t i = 0; i < 47; i++) { - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - } - - //Send start bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short * 4); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - } - } - - return true; -} - -void subghz_protocol_nero_radio_reset(SubGhzProtocolNeroRadio* instance) { - instance->common.parser_step = NeroRadioDecoderStepReset; -} - -void subghz_protocol_nero_radio_parse( - SubGhzProtocolNeroRadio* instance, - bool level, - uint32_t duration) { - switch(instance->common.parser_step) { - case NeroRadioDecoderStepReset: - if((level) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - instance->common.parser_step = NeroRadioDecoderStepCheckPreambula; - instance->common.te_last = duration; - instance->common.header_count = 0; - } - break; - case NeroRadioDecoderStepCheckPreambula: - if(level) { - if((DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) || - (DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta)) { - instance->common.te_last = duration; - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - } else if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - if(DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) { - // Found header - instance->common.header_count++; - break; - } else if( - DURATION_DIFF(instance->common.te_last, instance->common.te_short * 4) < - instance->common.te_delta) { - // Found start bit - if(instance->common.header_count > 40) { - instance->common.parser_step = NeroRadioDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - break; - case NeroRadioDecoderStepSaveDuration: - if(level) { - instance->common.te_last = duration; - instance->common.parser_step = NeroRadioDecoderStepCheckDuration; - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - break; - case NeroRadioDecoderStepCheckDuration: - if(!level) { - if(duration >= (instance->common.te_short * 10 + instance->common.te_delta * 2)) { - //Found stop bit - if(DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) { - subghz_protocol_common_add_bit(&instance->common, 0); - } else if( - DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) { - subghz_protocol_common_add_bit(&instance->common, 1); - } - instance->common.parser_step = NeroRadioDecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->common.parser_step = NeroRadioDecoderStepReset; - break; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = NeroRadioDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = NeroRadioDecoderStepSaveDuration; - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; - } - break; - } -} - -void subghz_protocol_nero_radio_to_str(SubGhzProtocolNeroRadio* instance, string_t output) { - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_hi = code_found_reverse >> 32; - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Yek:0x%lX%08lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - code_found_reverse_hi, - code_found_reverse_lo); -} - -bool subghz_protocol_nero_radio_to_save_file( - SubGhzProtocolNeroRadio* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_nero_radio_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolNeroRadio* instance, - const char* file_path) { - return subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); -} - -void subghz_decoder_nero_radio_to_load_protocol(SubGhzProtocolNeroRadio* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; -} diff --git a/lib/subghz/protocols/subghz_protocol_nero_radio.h b/lib/subghz/protocols/subghz_protocol_nero_radio.h deleted file mode 100644 index 437e95f6..00000000 --- a/lib/subghz/protocols/subghz_protocol_nero_radio.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolNeroRadio SubGhzProtocolNeroRadio; - -/** Allocate SubGhzProtocolNeroRadio - * - * @return SubGhzProtocolNeroRadio* - */ -SubGhzProtocolNeroRadio* subghz_protocol_nero_radio_alloc(); - -/** Free SubGhzProtocolNeroRadio - * - * @param instance - */ -void subghz_protocol_nero_radio_free(SubGhzProtocolNeroRadio* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolNeroRadio instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_nero_radio_send_key( - SubGhzProtocolNeroRadio* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolNeroRadio instance - */ -void subghz_protocol_nero_radio_reset(SubGhzProtocolNeroRadio* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolNeroRadio instance - */ -void subghz_protocol_nero_radio_check_remote_controller(SubGhzProtocolNeroRadio* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolNeroRadio instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_nero_radio_parse( - SubGhzProtocolNeroRadio* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolNeroRadio* instance - * @param output - output string - */ -void subghz_protocol_nero_radio_to_str(SubGhzProtocolNeroRadio* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolNeroRadio instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_nero_radio_to_save_file( - SubGhzProtocolNeroRadio* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolNeroRadio instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_nero_radio_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolNeroRadio* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolNeroRadio instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_nero_radio_to_load_protocol(SubGhzProtocolNeroRadio* instance, void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_nero_sketch.c b/lib/subghz/protocols/subghz_protocol_nero_sketch.c deleted file mode 100644 index db87e0a1..00000000 --- a/lib/subghz/protocols/subghz_protocol_nero_sketch.c +++ /dev/null @@ -1,225 +0,0 @@ -#include "subghz_protocol_nero_sketch.h" - -struct SubGhzProtocolNeroSketch { - SubGhzProtocolCommon common; -}; - -typedef enum { - NeroSketchDecoderStepReset = 0, - NeroSketchDecoderStepCheckPreambula, - NeroSketchDecoderStepSaveDuration, - NeroSketchDecoderStepCheckDuration, -} NeroSketchDecoderStep; - -SubGhzProtocolNeroSketch* subghz_protocol_nero_sketch_alloc(void) { - SubGhzProtocolNeroSketch* instance = malloc(sizeof(SubGhzProtocolNeroSketch)); - - instance->common.name = "Nero Sketch"; - instance->common.code_min_count_bit_for_found = 40; - instance->common.te_short = 330; - instance->common.te_long = 660; - instance->common.te_delta = 150; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_nero_sketch_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_nero_sketch_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_nero_sketch_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_nero_sketch_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_nero_sketch_send_key; - - return instance; -} - -void subghz_protocol_nero_sketch_free(SubGhzProtocolNeroSketch* instance) { - furi_assert(instance); - free(instance); -} - -bool subghz_protocol_nero_sketch_send_key( - SubGhzProtocolNeroSketch* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - size_t index = 0; - encoder->size_upload = 47 * 2 + 2 + (instance->common.code_last_count_bit * 2) + 2; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - - //Send header - for(uint8_t i = 0; i < 47; i++) { - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - } - - //Send start bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short * 4); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - } - } - - //Send stop bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short * 3); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->common.te_short); - - return true; -} - -void subghz_protocol_nero_sketch_reset(SubGhzProtocolNeroSketch* instance) { - instance->common.parser_step = NeroSketchDecoderStepReset; -} - -void subghz_protocol_nero_sketch_parse( - SubGhzProtocolNeroSketch* instance, - bool level, - uint32_t duration) { - switch(instance->common.parser_step) { - case NeroSketchDecoderStepReset: - if((level) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - instance->common.parser_step = NeroSketchDecoderStepCheckPreambula; - instance->common.te_last = duration; - instance->common.header_count = 0; - } - break; - case NeroSketchDecoderStepCheckPreambula: - if(level) { - if((DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) || - (DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta)) { - instance->common.te_last = duration; - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - } else if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - if(DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) { - // Found header - instance->common.header_count++; - break; - } else if( - DURATION_DIFF(instance->common.te_last, instance->common.te_short * 4) < - instance->common.te_delta) { - // Found start bit - if(instance->common.header_count > 40) { - instance->common.parser_step = NeroSketchDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - break; - case NeroSketchDecoderStepSaveDuration: - if(level) { - if(duration >= (instance->common.te_short * 2 + instance->common.te_delta * 2)) { - //Found stop bit - instance->common.parser_step = NeroSketchDecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = NeroSketchDecoderStepCheckDuration; - } - - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - break; - case NeroSketchDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = NeroSketchDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = NeroSketchDecoderStepSaveDuration; - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; - } - break; - } -} - -void subghz_protocol_nero_sketch_to_str(SubGhzProtocolNeroSketch* instance, string_t output) { - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_hi = code_found_reverse >> 32; - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Yek:0x%lX%08lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - code_found_reverse_hi, - code_found_reverse_lo); -} - -bool subghz_protocol_nero_sketch_to_save_file( - SubGhzProtocolNeroSketch* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_nero_sketch_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolNeroSketch* instance, - const char* file_path) { - return subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); -} - -void subghz_decoder_nero_sketch_to_load_protocol(SubGhzProtocolNeroSketch* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; -} diff --git a/lib/subghz/protocols/subghz_protocol_nero_sketch.h b/lib/subghz/protocols/subghz_protocol_nero_sketch.h deleted file mode 100644 index db95ab76..00000000 --- a/lib/subghz/protocols/subghz_protocol_nero_sketch.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolNeroSketch SubGhzProtocolNeroSketch; - -/** Allocate SubGhzProtocolNeroSketch - * - * @return SubGhzProtocolNeroSketch* - */ -SubGhzProtocolNeroSketch* subghz_protocol_nero_sketch_alloc(); - -/** Free SubGhzProtocolNeroSketch - * - * @param instance - */ -void subghz_protocol_nero_sketch_free(SubGhzProtocolNeroSketch* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolNeroSketch instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_nero_sketch_send_key( - SubGhzProtocolNeroSketch* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolNeroSketch instance - */ -void subghz_protocol_nero_sketch_reset(SubGhzProtocolNeroSketch* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolNeroSketch instance - */ -void subghz_protocol_nero_sketch_check_remote_controller(SubGhzProtocolNeroSketch* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolNeroSketch instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_nero_sketch_parse( - SubGhzProtocolNeroSketch* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolNeroSketch* instance - * @param output - output string - */ -void subghz_protocol_nero_sketch_to_str(SubGhzProtocolNeroSketch* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolNeroSketch instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_nero_sketch_to_save_file( - SubGhzProtocolNeroSketch* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolNeroSketch instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_nero_sketch_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolNeroSketch* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolNeroSketch instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_nero_sketch_to_load_protocol(SubGhzProtocolNeroSketch* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_nice_flo.c b/lib/subghz/protocols/subghz_protocol_nice_flo.c deleted file mode 100644 index 2c2da378..00000000 --- a/lib/subghz/protocols/subghz_protocol_nice_flo.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "subghz_protocol_nice_flo.h" - -/* - * Help - * https://phreakerclub.com/447 - * - */ - -struct SubGhzProtocolNiceFlo { - SubGhzProtocolCommon common; -}; - -typedef enum { - NiceFloDecoderStepReset = 0, - NiceFloDecoderStepFoundStartBit, - NiceFloDecoderStepSaveDuration, - NiceFloDecoderStepCheckDuration, -} NiceFloDecoderStep; - -SubGhzProtocolNiceFlo* subghz_protocol_nice_flo_alloc() { - SubGhzProtocolNiceFlo* instance = malloc(sizeof(SubGhzProtocolNiceFlo)); - - instance->common.name = "Nice FLO"; - instance->common.code_min_count_bit_for_found = 12; - instance->common.te_short = 700; - instance->common.te_long = 1400; - instance->common.te_delta = 200; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_nice_flo_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_protocol_nice_flo_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_nice_flo_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_nice_flo_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_nice_flo_send_key; - return instance; -} - -void subghz_protocol_nice_flo_free(SubGhzProtocolNiceFlo* instance) { - furi_assert(instance); - free(instance); -} - -bool subghz_protocol_nice_flo_send_key( - SubGhzProtocolNiceFlo* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - size_t index = 0; - encoder->size_upload = (instance->common.code_last_count_bit * 2) + 2; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - //Send header - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short * 36); - //Send start bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->common.te_short); - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_long); - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_short); - } else { - //send bit 0 - encoder->upload[index++] = - level_duration_make(false, (uint32_t)instance->common.te_short); - encoder->upload[index++] = - level_duration_make(true, (uint32_t)instance->common.te_long); - } - } - return true; -} - -void subghz_protocol_nice_flo_reset(SubGhzProtocolNiceFlo* instance) { - instance->common.parser_step = NiceFloDecoderStepReset; -} - -void subghz_protocol_nice_flo_parse(SubGhzProtocolNiceFlo* instance, bool level, uint32_t duration) { - switch(instance->common.parser_step) { - case NiceFloDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 36) < - instance->common.te_delta * 36)) { - //Found header Nice Flo - instance->common.parser_step = NiceFloDecoderStepFoundStartBit; - } - break; - case NiceFloDecoderStepFoundStartBit: - if(!level) { - break; - } else if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - //Found start bit Nice Flo - instance->common.parser_step = NiceFloDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = NiceFloDecoderStepReset; - } - break; - case NiceFloDecoderStepSaveDuration: - if(!level) { //save interval - if(duration >= (instance->common.te_short * 4)) { - instance->common.parser_step = NiceFloDecoderStepFoundStartBit; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.serial = 0x0; - instance->common.btn = 0x0; - - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - break; - } - instance->common.te_last = duration; - instance->common.parser_step = NiceFloDecoderStepCheckDuration; - } else { - instance->common.parser_step = NiceFloDecoderStepReset; - } - break; - case NiceFloDecoderStepCheckDuration: - if(level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = NiceFloDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = NiceFloDecoderStepSaveDuration; - } else - instance->common.parser_step = NiceFloDecoderStepReset; - } else { - instance->common.parser_step = NiceFloDecoderStepReset; - } - break; - } -} - -void subghz_protocol_nice_flo_to_str(SubGhzProtocolNiceFlo* instance, string_t output) { - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_lo, - code_found_reverse_lo); -} - -bool subghz_protocol_nice_flo_to_save_file( - SubGhzProtocolNiceFlo* instance, - FlipperFormat* flipper_format) { - return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); -} - -bool subghz_protocol_nice_flo_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolNiceFlo* instance, - const char* file_path) { - return subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); -} - -void subghz_decoder_nice_flo_to_load_protocol(SubGhzProtocolNiceFlo* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - instance->common.serial = 0x0; - instance->common.btn = 0x0; -} diff --git a/lib/subghz/protocols/subghz_protocol_nice_flo.h b/lib/subghz/protocols/subghz_protocol_nice_flo.h deleted file mode 100644 index 5d395071..00000000 --- a/lib/subghz/protocols/subghz_protocol_nice_flo.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolNiceFlo SubGhzProtocolNiceFlo; - -/** Allocate SubGhzProtocolNiceFlo - * - * @return SubGhzProtocolNiceFlo* - */ -SubGhzProtocolNiceFlo* subghz_protocol_nice_flo_alloc(); - -/** Free SubGhzProtocolNiceFlo - * - * @param instance - */ -void subghz_protocol_nice_flo_free(SubGhzProtocolNiceFlo* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolNiceFlo instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_nice_flo_send_key( - SubGhzProtocolNiceFlo* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzProtocolNiceFlo instance - */ -void subghz_protocol_nice_flo_reset(SubGhzProtocolNiceFlo* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolNiceFlo instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_nice_flo_parse(SubGhzProtocolNiceFlo* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolNiceFlo* instance - * @param output - output string - */ -void subghz_protocol_nice_flo_to_str(SubGhzProtocolNiceFlo* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzProtocolNiceFlo instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_protocol_nice_flo_to_save_file( - SubGhzProtocolNiceFlo* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzProtocolNiceFlo instance - * @param file_path - file path - * @return bool - */ -bool subghz_protocol_nice_flo_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolNiceFlo* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolNiceFlo instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_nice_flo_to_load_protocol(SubGhzProtocolNiceFlo* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_nice_flor_s.c b/lib/subghz/protocols/subghz_protocol_nice_flor_s.c deleted file mode 100644 index f9c7db6a..00000000 --- a/lib/subghz/protocols/subghz_protocol_nice_flor_s.c +++ /dev/null @@ -1,264 +0,0 @@ -#include "subghz_protocol_nice_flor_s.h" - -#include -#include "file_worker.h" -#include "../subghz_keystore.h" -/* - * https://phreakerclub.com/1615 - * https://phreakerclub.com/forum/showthread.php?t=2360 - * https://vrtp.ru/index.php?showtopic=27867 - */ - -#define TAG "SubGhzNiceFlorS" - -struct SubGhzProtocolNiceFlorS { - SubGhzProtocolCommon common; - const char* rainbow_table_file_name; -}; - -typedef enum { - NiceFlorSDecoderStepReset = 0, - NiceFlorSDecoderStepCheckHeader, - NiceFlorSDecoderStepFoundHeader, - NiceFlorSDecoderStepSaveDuration, - NiceFlorSDecoderStepCheckDuration, -} NiceFlorSDecoderStep; - -SubGhzProtocolNiceFlorS* subghz_protocol_nice_flor_s_alloc() { - SubGhzProtocolNiceFlorS* instance = malloc(sizeof(SubGhzProtocolNiceFlorS)); - - instance->common.name = "Nice FloR-S"; - instance->common.code_min_count_bit_for_found = 52; - instance->common.te_short = 500; - instance->common.te_long = 1000; - instance->common.te_delta = 300; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_nice_flor_s_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_nice_flor_s_to_load_protocol; - - return instance; -} - -void subghz_protocol_nice_flor_s_free(SubGhzProtocolNiceFlorS* instance) { - furi_assert(instance); - free(instance); -} - -void subghz_protocol_nice_flor_s_name_file(SubGhzProtocolNiceFlorS* instance, const char* name) { - instance->rainbow_table_file_name = name; - FURI_LOG_I(TAG, "Loading rainbow table from %s", name); -} - -/** Send bit - * - * @param instance - SubGhzProtocolNiceFlorS instance - * @param bit - bit - */ -void subghz_protocol_nice_flor_s_send_bit(SubGhzProtocolNiceFlorS* instance, uint8_t bit) { - if(bit) { - //send bit 1 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_long); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short); - } else { - //send bit 0 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_long); - } -} - -void subghz_protocol_nice_flor_s_send_key( - SubGhzProtocolNiceFlorS* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat) { - while(repeat--) { - //Send header - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short * 34); - //Send Start Bit - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short * 3); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short * 3); - //Send key data - for(uint8_t i = bit; i > 0; i--) { - subghz_protocol_nice_flor_s_send_bit(instance, bit_read(key, i - 1)); - } - //Send Stop Bit - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short * 3); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short * 3); - } -} - -/** Read bytes from rainbow table - * - * @param instance - SubGhzProtocolNiceFlorS* instance - * @param address - address byte - * @return byte data - */ -uint8_t subghz_nice_flor_s_get_byte_in_file(SubGhzProtocolNiceFlorS* instance, uint32_t address) { - if(!instance->rainbow_table_file_name) return 0; - - uint8_t buffer[1] = {0}; - if(subghz_keystore_raw_get_data( - instance->rainbow_table_file_name, address, buffer, sizeof(uint8_t))) { - return buffer[0]; - } else { - return 0; - } -} - -/** Decrypt protocol Nice Flor S - * - * @param instance - SubGhzProtocolNiceFlorS* instance - */ -void subghz_nice_flor_s_decoder_decrypt(SubGhzProtocolNiceFlorS* instance) { - /* - * Packet format Nice Flor-s: START-P0-P1-P2-P3-P4-P5-P6-P7-STOP - * P0 (4-bit) - button positional code - 1:0x1, 2:0x2, 3:0x4, 4:0x8; - * P1 (4-bit) - batch repetition number, calculated by the formula: - * P1 = 0xF ^ P0 ^ n; where n changes from 1 to 15, then 0, and then in a circle - * key 1: {0xF,0xC,0xD,0xA,0xB,0x8,0x9,0x6,0x7,0x4,0x5,0x2,0x3,0x0,0x1,0xE}; - * key 2: {0xC,0xF,0xE,0x9,0x8,0xB,0xA,0x5,0x4,0x7,0x6,0x1,0x0,0x3,0x2,0xD}; - * key 3: {0xA,0x9,0x8,0xF,0xE,0xD,0xC,0x3,0x2,0x1,0x0,0x7,0x6,0x5,0x4,0xB}; - * P2 (4-bit) - part of the serial number, P2 = (K ^ S3) & 0xF; - * P3 (byte) - the major part of the encrypted index - * P4 (byte) - the low-order part of the encrypted index - * P5 (byte) - part of the serial number, P5 = K ^ S2; - * P6 (byte) - part of the serial number, P6 = K ^ S1; - * P7 (byte) - part of the serial number, P7 = K ^ S0; - * K (byte) - depends on P3 and P4, K = Fk(P3, P4); - * S3,S2,S1,S0 - serial number of the console 28 bit. - */ - - uint16_t p3p4 = (uint16_t)(instance->common.code_last_found >> 24); - instance->common.cnt = subghz_nice_flor_s_get_byte_in_file(instance, p3p4 * 2) << 8 | - subghz_nice_flor_s_get_byte_in_file(instance, p3p4 * 2 + 1); - uint8_t k = - (uint8_t)(p3p4 & 0x00FF) ^ - subghz_nice_flor_s_get_byte_in_file(instance, (0x20000 | (instance->common.cnt & 0x00ff))); - - uint8_t s3 = ((uint8_t)(instance->common.code_last_found >> 40) ^ k) & 0x0f; - uint8_t s2 = ((uint8_t)(instance->common.code_last_found >> 16) ^ k); - uint8_t s1 = ((uint8_t)(instance->common.code_last_found >> 8) ^ k); - uint8_t s0 = ((uint8_t)(instance->common.code_last_found) ^ k); - instance->common.serial = s3 << 24 | s2 << 16 | s1 << 8 | s0; - - instance->common.btn = (instance->common.code_last_found >> 48) & 0x0f; -} - -void subghz_protocol_nice_flor_s_reset(SubGhzProtocolNiceFlorS* instance) { - instance->common.parser_step = NiceFlorSDecoderStepReset; -} - -void subghz_protocol_nice_flor_s_parse( - SubGhzProtocolNiceFlorS* instance, - bool level, - uint32_t duration) { - switch(instance->common.parser_step) { - case NiceFlorSDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 38) < - instance->common.te_delta * 38)) { - //Found start header Nice Flor-S - instance->common.parser_step = NiceFlorSDecoderStepCheckHeader; - } - break; - case NiceFlorSDecoderStepCheckHeader: - if((level) && (DURATION_DIFF(duration, instance->common.te_short * 3) < - instance->common.te_delta * 3)) { - //Found next header Nice Flor-S - instance->common.parser_step = NiceFlorSDecoderStepFoundHeader; - } else { - instance->common.parser_step = NiceFlorSDecoderStepReset; - } - break; - case NiceFlorSDecoderStepFoundHeader: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 3) < - instance->common.te_delta * 3)) { - //Found header Nice Flor-S - instance->common.parser_step = NiceFlorSDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - } else { - instance->common.parser_step = NiceFlorSDecoderStepReset; - } - break; - case NiceFlorSDecoderStepSaveDuration: - if(level) { - if(DURATION_DIFF(duration, instance->common.te_short * 3) < - instance->common.te_delta) { - //Found STOP bit - instance->common.parser_step = NiceFlorSDecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - break; - } else { - //save interval - instance->common.te_last = duration; - instance->common.parser_step = NiceFlorSDecoderStepCheckDuration; - } - } - break; - case NiceFlorSDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = NiceFlorSDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = NiceFlorSDecoderStepSaveDuration; - } else - instance->common.parser_step = NiceFlorSDecoderStepReset; - } else { - instance->common.parser_step = NiceFlorSDecoderStepReset; - } - break; - } -} - -void subghz_protocol_nice_flor_s_to_str(SubGhzProtocolNiceFlorS* instance, string_t output) { - subghz_nice_flor_s_decoder_decrypt(instance); - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:%05lX\r\n" - "Cnt:%04X Btn:%02lX\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->common.serial, - instance->common.cnt, - instance->common.btn); -} - -void subghz_decoder_nice_flor_s_to_load_protocol(SubGhzProtocolNiceFlorS* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_nice_flor_s_decoder_decrypt(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_nice_flor_s.h b/lib/subghz/protocols/subghz_protocol_nice_flor_s.h deleted file mode 100644 index ac5a13a2..00000000 --- a/lib/subghz/protocols/subghz_protocol_nice_flor_s.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolNiceFlorS SubGhzProtocolNiceFlorS; - -/** Allocate SubGhzProtocolNiceFlorS - * - * @return SubGhzProtocolNiceFlorS* - */ -SubGhzProtocolNiceFlorS* subghz_protocol_nice_flor_s_alloc(); - -/** Free SubGhzProtocolNiceFlorS - * - * @param instance - */ -void subghz_protocol_nice_flor_s_free(SubGhzProtocolNiceFlorS* instance); - -/** File name rainbow table Nice Flor-S - * - * @param instance - SubGhzProtocolNiceFlorS instance - * @param file_name - "path/file_name" - */ -void subghz_protocol_nice_flor_s_name_file(SubGhzProtocolNiceFlorS* instance, const char* name); - -/** Sends the key on the air - * - * @param instance - SubGhzProtocolNiceFlorS instance - * @param key - key send - * @param bit - count bit key - * @param repeat - repeat send key - */ -void subghz_protocol_nice_flor_s_send_key( - SubGhzProtocolNiceFlorS* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat); - -/** Reset internal state - * @param instance - SubGhzProtocolNiceFlorS instance - */ -void subghz_protocol_nice_flor_s_reset(SubGhzProtocolNiceFlorS* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolNiceFlorS instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_nice_flor_s_parse( - SubGhzProtocolNiceFlorS* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolNiceFlorS* instance - * @param output - output string - */ -void subghz_protocol_nice_flor_s_to_str(SubGhzProtocolNiceFlorS* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolNiceFlorS instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_nice_flor_s_to_load_protocol(SubGhzProtocolNiceFlorS* instance, void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_princeton.c b/lib/subghz/protocols/subghz_protocol_princeton.c deleted file mode 100644 index 64f8a47c..00000000 --- a/lib/subghz/protocols/subghz_protocol_princeton.c +++ /dev/null @@ -1,368 +0,0 @@ -#include "subghz_protocol_princeton.h" -/* - * Help - * https://phreakerclub.com/447 - * - */ - -#define SUBGHZ_PT_SHORT 300 -#define SUBGHZ_PT_LONG (SUBGHZ_PT_SHORT * 3) -#define SUBGHZ_PT_GUARD (SUBGHZ_PT_SHORT * 30) -#define SUBGHZ_PT_COUNT_KEY_433 9 -#define SUBGHZ_PT_TIMEOUT_433 900 -#define SUBGHZ_PT_COUNT_KEY_868 9 -#define SUBGHZ_PT_TIMEOUT_868 14000 - -#define TAG "SubghzPrinceton" - -struct SubGhzEncoderPrinceton { - uint32_t key; - uint16_t te; - size_t repeat; - size_t front; - size_t count_key; - size_t count_key_package; - uint32_t time_high; - uint32_t time_low; - uint32_t timeout; - uint32_t time_stop; -}; - -typedef enum { - PrincetonDecoderStepReset = 0, - PrincetonDecoderStepSaveDuration, - PrincetonDecoderStepCheckDuration, -} PrincetonDecoderStep; - -SubGhzEncoderPrinceton* subghz_encoder_princeton_alloc() { - SubGhzEncoderPrinceton* instance = malloc(sizeof(SubGhzEncoderPrinceton)); - return instance; -} - -void subghz_encoder_princeton_free(SubGhzEncoderPrinceton* instance) { - furi_assert(instance); - free(instance); -} - -void subghz_encoder_princeton_set_te(SubGhzEncoderPrinceton* instance, void* decoder) { - SubGhzDecoderPrinceton* pricenton = decoder; - if((pricenton->te) != 0) { - instance->te = pricenton->te; - } else { - instance->te = SUBGHZ_PT_SHORT; - } -} - -void subghz_encoder_princeton_stop(SubGhzEncoderPrinceton* instance, uint32_t time_stop) { - instance->time_stop = time_stop; -} - -void subghz_encoder_princeton_set( - SubGhzEncoderPrinceton* instance, - uint32_t key, - size_t repeat, - uint32_t frequency) { - furi_assert(instance); - instance->te = SUBGHZ_PT_SHORT; - instance->key = key; - instance->repeat = repeat + 1; - instance->front = 48; - instance->time_high = 0; - instance->time_low = 0; - if(frequency < 700000000) { - instance->count_key_package = SUBGHZ_PT_COUNT_KEY_433; - instance->timeout = SUBGHZ_PT_TIMEOUT_433; - } else { - instance->count_key_package = SUBGHZ_PT_COUNT_KEY_868; - instance->timeout = SUBGHZ_PT_TIMEOUT_868; - } - - instance->count_key = instance->count_key_package + 3; - - if((millis() - instance->time_stop) < instance->timeout) { - instance->time_stop = (instance->timeout - (millis() - instance->time_stop)) * 1000; - } else { - instance->time_stop = 0; - } -} - -size_t subghz_encoder_princeton_get_repeat_left(SubGhzEncoderPrinceton* instance) { - furi_assert(instance); - return instance->repeat; -} - -void subghz_encoder_princeton_print_log(void* context) { - SubGhzEncoderPrinceton* instance = context; - float duty_cycle = - ((float)instance->time_high / (instance->time_high + instance->time_low)) * 100; - FURI_LOG_I( - TAG "Encoder", - "Radio tx_time=%dus ON=%dus, OFF=%dus, DutyCycle=%d,%d%%", - instance->time_high + instance->time_low, - instance->time_high, - instance->time_low, - (uint32_t)duty_cycle, - (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100)); -} - -LevelDuration subghz_encoder_princeton_yield(void* context) { - SubGhzEncoderPrinceton* instance = context; - if(instance->repeat == 0) { - subghz_encoder_princeton_print_log(instance); - return level_duration_reset(); - } - - size_t bit = instance->front / 2; - bool level = !(instance->front % 2); - - LevelDuration ret; - if(bit < 24) { - uint8_t byte = bit / 8; - uint8_t bit_in_byte = bit % 8; - bool value = (((uint8_t*)&instance->key)[2 - byte] >> (7 - bit_in_byte)) & 1; - if(value) { - ret = level_duration_make(level, level ? instance->te * 3 : instance->te); - if(level) - instance->time_high += instance->te * 3; - else - instance->time_low += instance->te; - } else { - ret = level_duration_make(level, level ? instance->te : instance->te * 3); - if(level) - instance->time_high += instance->te; - else - instance->time_low += instance->te * 3; - } - } else { - if(instance->time_stop) { - ret = level_duration_make(level, level ? instance->te : instance->time_stop); - if(level) - instance->time_high += instance->te; - else { - instance->time_low += instance->time_stop; - instance->time_stop = 0; - instance->front = 47; - } - } else { - if(--instance->count_key != 0) { - ret = level_duration_make(level, level ? instance->te : instance->te * 30); - if(level) - instance->time_high += instance->te; - else - instance->time_low += instance->te * 30; - } else { - instance->count_key = instance->count_key_package + 2; - instance->front = 48; - ret = level_duration_make(level, level ? instance->te : instance->timeout * 1000); - if(level) - instance->time_high += instance->te; - else - instance->time_low += instance->timeout * 1000; - } - } - } - - instance->front++; - if(instance->front == 50) { - instance->repeat--; - instance->front = 0; - } - return ret; -} - -SubGhzDecoderPrinceton* subghz_decoder_princeton_alloc(void) { - SubGhzDecoderPrinceton* instance = malloc(sizeof(SubGhzDecoderPrinceton)); - - instance->te = SUBGHZ_PT_SHORT; - instance->common.name = "Princeton"; - instance->common.code_min_count_bit_for_found = 24; - instance->common.te_short = 400; //150; - instance->common.te_long = 1200; //450; - instance->common.te_delta = 250; //50; - instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_decoder_princeton_to_str; - instance->common.to_save_file = - (SubGhzProtocolCommonSaveFile)subghz_decoder_princeton_to_save_file; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_decoder_princeton_to_load_protocol_from_file; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_princeton_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_princeton_send_key; - - return instance; -} - -void subghz_decoder_princeton_free(SubGhzDecoderPrinceton* instance) { - furi_assert(instance); - free(instance); -} - -uint16_t subghz_protocol_princeton_get_te(void* context) { - SubGhzDecoderPrinceton* instance = context; - return instance->te; -} - -bool subghz_protocol_princeton_send_key( - SubGhzDecoderPrinceton* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - size_t index = 0; - encoder->size_upload = (instance->common.code_last_count_bit * 2) + 2; - if(encoder->size_upload > SUBGHZ_ENCODER_UPLOAD_MAX_SIZE) return false; - - //Send key data - for(uint8_t i = instance->common.code_last_count_bit; i > 0; i--) { - if(bit_read(instance->common.code_last_found, i - 1)) { - //send bit 1 - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->te * 3); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->te); - } else { - //send bit 0 - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->te); - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->te * 3); - } - } - - //Send Stop bit - encoder->upload[index++] = level_duration_make(true, (uint32_t)instance->te); - //Send PT_GUARD - encoder->upload[index++] = level_duration_make(false, (uint32_t)instance->te * 30); - - return true; -} - -void subghz_decoder_princeton_reset(SubGhzDecoderPrinceton* instance) { - instance->common.parser_step = PrincetonDecoderStepReset; -} - -void subghz_decoder_princeton_parse( - SubGhzDecoderPrinceton* instance, - bool level, - uint32_t duration) { - switch(instance->common.parser_step) { - case PrincetonDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 36) < - instance->common.te_delta * 36)) { - //Found Preambula - instance->common.parser_step = PrincetonDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->te = 0; - } - break; - case PrincetonDecoderStepSaveDuration: - //save duration - if(level) { - instance->common.te_last = duration; - instance->te += duration; - instance->common.parser_step = PrincetonDecoderStepCheckDuration; - } - break; - case PrincetonDecoderStepCheckDuration: - if(!level) { - if(duration >= (instance->common.te_short * 10 + instance->common.te_delta)) { - instance->common.parser_step = PrincetonDecoderStepSaveDuration; - if(instance->common.code_count_bit == - instance->common.code_min_count_bit_for_found) { - instance->te /= (instance->common.code_count_bit * 4 + 1); - - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - instance->common.serial = instance->common.code_found >> 4; - instance->common.btn = (uint8_t)instance->common.code_found & 0x00000F; - - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->te = 0; - break; - } - - instance->te += duration; - - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < - instance->common.te_delta * 3)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = PrincetonDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta * 3) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = PrincetonDecoderStepSaveDuration; - } else { - instance->common.parser_step = PrincetonDecoderStepReset; - } - } else { - instance->common.parser_step = PrincetonDecoderStepReset; - } - break; - } -} - -void subghz_decoder_princeton_to_str(SubGhzDecoderPrinceton* instance, string_t output) { - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n" - "Sn:0x%05lX BTN:%02X\r\n" - "Te:%dus\r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_lo, - code_found_reverse_lo, - instance->common.serial, - instance->common.btn, - instance->te); -} - -bool subghz_decoder_princeton_to_save_file( - SubGhzDecoderPrinceton* instance, - FlipperFormat* flipper_format) { - bool res = - subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_format); - if(res) { - res = flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1); - if(!res) FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Te"); - } - return res; -} - -bool subghz_decoder_princeton_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzDecoderPrinceton* instance, - const char* file_path) { - bool loaded = subghz_protocol_common_to_load_protocol_from_file( - (SubGhzProtocolCommon*)instance, flipper_format); - if(loaded) { - loaded = flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1); - if(!loaded) FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing TE"); - } - return loaded; -} - -void subghz_decoder_princeton_to_load_protocol(SubGhzDecoderPrinceton* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - instance->te = data->param1; - instance->common.serial = instance->common.code_last_found >> 4; - instance->common.btn = (uint8_t)instance->common.code_last_found & 0x00000F; -} diff --git a/lib/subghz/protocols/subghz_protocol_princeton.h b/lib/subghz/protocols/subghz_protocol_princeton.h deleted file mode 100644 index 9eccb4ef..00000000 --- a/lib/subghz/protocols/subghz_protocol_princeton.h +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -struct SubGhzDecoderPrinceton { - SubGhzProtocolCommon common; - uint32_t te; -}; - -/** SubGhzEncoderPrinceton anonymous type */ -typedef struct SubGhzEncoderPrinceton SubGhzEncoderPrinceton; - -/** Allocate SubGhzEncoderPrinceton - * @return pointer to SubGhzEncoderPrinceton instance - */ -SubGhzEncoderPrinceton* subghz_encoder_princeton_alloc(); - -/** Free SubGhzEncoderPrinceton instance - * @param instance - SubGhzEncoderPrinceton instance - */ -void subghz_encoder_princeton_free(SubGhzEncoderPrinceton* instance); - -void subghz_encoder_princeton_stop(SubGhzEncoderPrinceton* instance, uint32_t time_stop); - -/** Set new encoder params - * @param instance - SubGhzEncoderPrinceton instance - * @param key - 24bit key - * @param repeat - how many times to repeat - * @param frequency - frequency - */ -void subghz_encoder_princeton_set( - SubGhzEncoderPrinceton* instance, - uint32_t key, - size_t repeat, - uint32_t frequency); - -/** Get repeat count left - * @param instance - SubGhzEncoderPrinceton instance - * @return repeat count left - */ -size_t subghz_encoder_princeton_get_repeat_left(SubGhzEncoderPrinceton* instance); - -/** Print encoder log - * @param instance - SubGhzEncoderPrinceton instance - */ -void subghz_encoder_princeton_print_log(void* context); - -/** Get level duration - * @param instance - SubGhzEncoderPrinceton instance - * @return level duration - */ -LevelDuration subghz_encoder_princeton_yield(void* context); - -/** SubGhzDecoderPrinceton anonymous type */ -typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton; - -void subghz_encoder_princeton_set_te(SubGhzEncoderPrinceton* instance, void* decoder); - -/** Allocate SubGhzDecoderPrinceton - * - * @return SubGhzDecoderPrinceton* - */ -SubGhzDecoderPrinceton* subghz_decoder_princeton_alloc(); - -/** Free SubGhzDecoderPrinceton - * - * @param instance - */ -void subghz_decoder_princeton_free(SubGhzDecoderPrinceton* instance); - -/** Get Te interval protocol - * - * @param context - SubGhzDecoderPrinceton context - * @return Te interval (us) - */ -uint16_t subghz_protocol_princeton_get_te(void* context); - -/** Get upload protocol - * - * @param instance - SubGhzDecoderPrinceton instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_princeton_send_key( - SubGhzDecoderPrinceton* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Reset internal state - * @param instance - SubGhzDecoderPrinceton instance - */ -void subghz_decoder_princeton_reset(SubGhzDecoderPrinceton* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzDecoderPrinceton instance - * @param data - LevelDuration level_duration - */ -void subghz_decoder_princeton_parse( - SubGhzDecoderPrinceton* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzDecoderPrinceton* instance - * @param output - output string - */ -void subghz_decoder_princeton_to_str(SubGhzDecoderPrinceton* instance, string_t output); - -/** Adding data to a file - * - * @param instance - SubGhzDecoderPrinceton instance - * @param flipper_format - FlipperFormat - * @return bool - */ -bool subghz_decoder_princeton_to_save_file( - SubGhzDecoderPrinceton* instance, - FlipperFormat* flipper_format); - -/** Loading protocol from file - * - * @param flipper_format - FlipperFormat - * @param instance - SubGhzDecoderPrinceton instance - * @param file_path - file path - * @return bool - */ -bool subghz_decoder_princeton_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzDecoderPrinceton* instance, - const char* file_path); - -/** Loading protocol from bin data - * - * @param instance - SubGhzDecoderPrinceton instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_princeton_to_load_protocol(SubGhzDecoderPrinceton* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_raw.c b/lib/subghz/protocols/subghz_protocol_raw.c deleted file mode 100755 index 72f540c6..00000000 --- a/lib/subghz/protocols/subghz_protocol_raw.c +++ /dev/null @@ -1,270 +0,0 @@ -#include "subghz_protocol_raw.h" -#include "../subghz_file_encoder_worker.h" - -#define TAG "SubGhzRaw" - -#define SUBGHZ_DOWNLOAD_MAX_SIZE 512 - -struct SubGhzProtocolRAW { - SubGhzProtocolCommon common; - - int32_t* upload_raw; - uint16_t ind_write; - Storage* storage; - FlipperFormat* flipper_format; - SubGhzFileEncoderWorker* file_worker_encoder; - uint32_t file_is_open; - string_t file_name; - size_t sample_write; - bool last_level; - SubGhzProtocolRAWCallbackEnd callback_end; - void* context_end; -}; - -typedef enum { - RAWFileIsOpenClose = 0, - RAWFileIsOpenWrite, - RAWFileIsOpenRead, -} RAWFilIsOpen; - -SubGhzProtocolRAW* subghz_protocol_raw_alloc(void) { - SubGhzProtocolRAW* instance = malloc(sizeof(SubGhzProtocolRAW)); - - instance->upload_raw = NULL; - instance->ind_write = 0; - - instance->last_level = false; - - instance->storage = furi_record_open("storage"); - instance->flipper_format = flipper_format_file_alloc(instance->storage); - instance->file_is_open = RAWFileIsOpenClose; - string_init(instance->file_name); - - instance->common.name = "RAW"; - instance->common.code_min_count_bit_for_found = 0; - instance->common.te_short = 80; - instance->common.te_long = 32700; - instance->common.te_delta = 0; - instance->common.type_protocol = SubGhzProtocolCommonTypeRAW; - instance->common.to_load_protocol_from_file = - (SubGhzProtocolCommonLoadFromFile)subghz_protocol_raw_to_load_protocol_from_file; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_raw_to_str; - //instance->common.to_load_protocol = - // (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_raw_to_load_protocol; - instance->common.get_upload_protocol = - (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_raw_send_key; - - return instance; -} - -void subghz_protocol_raw_free(SubGhzProtocolRAW* instance) { - furi_assert(instance); - string_clear(instance->file_name); - - flipper_format_free(instance->flipper_format); - furi_record_close("storage"); - - free(instance); -} - -void subghz_protocol_raw_file_encoder_worker_callback_end(void* context) { - furi_assert(context); - SubGhzProtocolRAW* instance = context; - if(instance->callback_end) instance->callback_end(instance->context_end); -} - -void subghz_protocol_raw_file_encoder_worker_set_callback_end( - SubGhzProtocolRAW* instance, - SubGhzProtocolRAWCallbackEnd callback_end, - void* context_end) { - furi_assert(instance); - furi_assert(callback_end); - instance->callback_end = callback_end; - instance->context_end = context_end; -} - -void subghz_protocol_raw_file_encoder_worker_stop(void* context) { - furi_assert(context); - SubGhzProtocolRAW* instance = context; - if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { - subghz_file_encoder_worker_stop(instance->file_worker_encoder); - subghz_file_encoder_worker_free(instance->file_worker_encoder); - instance->file_is_open = RAWFileIsOpenClose; - } -} - -bool subghz_protocol_raw_send_key( - SubGhzProtocolRAW* instance, - SubGhzProtocolCommonEncoder* encoder) { - furi_assert(instance); - furi_assert(encoder); - - bool loaded = false; - - instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); - - if(subghz_file_encoder_worker_start( - instance->file_worker_encoder, string_get_cstr(instance->file_name))) { - //the worker needs a file in order to open and read part of the file - osDelay(100); - instance->file_is_open = RAWFileIsOpenRead; - //Forwarding UPLOAD to common encoder - subghz_protocol_encoder_common_set_callback( - encoder, subghz_file_encoder_worker_get_level_duration, instance->file_worker_encoder); - //forced stop of transmission - subghz_protocol_encoder_common_set_callback_end( - encoder, subghz_protocol_raw_file_encoder_worker_stop, instance); - //file transfer complete callback - subghz_file_encoder_worker_callback_end( - instance->file_worker_encoder, - subghz_protocol_raw_file_encoder_worker_callback_end, - instance); - - loaded = true; - } else { - subghz_protocol_raw_file_encoder_worker_stop(instance); - } - return loaded; -} - -void subghz_protocol_raw_reset(SubGhzProtocolRAW* instance) { - instance->ind_write = 0; -} - -void subghz_protocol_raw_parse(SubGhzProtocolRAW* instance, bool level, uint32_t duration) { - if(instance->upload_raw != NULL) { - if(duration > instance->common.te_short) { - if(duration > instance->common.te_long) duration = instance->common.te_long; - if(instance->last_level != level) { - instance->last_level = (level ? true : false); - instance->upload_raw[instance->ind_write++] = (level ? duration : -duration); - } - } - - if(instance->ind_write == SUBGHZ_DOWNLOAD_MAX_SIZE) { - subghz_protocol_raw_save_to_file_write(instance); - } - } -} - -void subghz_protocol_raw_to_str(SubGhzProtocolRAW* instance, string_t output) { - string_cat_printf(output, "RAW Date"); -} - -const char* subghz_protocol_raw_get_last_file_name(SubGhzProtocolRAW* instance) { - return string_get_cstr(instance->file_name); -} - -void subghz_protocol_raw_set_last_file_name(SubGhzProtocolRAW* instance, const char* name) { - string_printf(instance->file_name, "%s", name); -} - -bool subghz_protocol_raw_save_to_file_init( - SubGhzProtocolRAW* instance, - const char* dev_name, - uint32_t frequency, - const char* preset) { - furi_assert(instance); - - //instance->flipper_format = flipper_format_file_alloc(instance->storage); - string_t dev_file_name; - string_init(dev_file_name); - bool init = false; - - do { - // Create subghz folder directory if necessary - if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { - break; - } - - string_set(instance->file_name, dev_name); - // First remove subghz device file if it was saved - string_printf(dev_file_name, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); - - if(!storage_simply_remove(instance->storage, string_get_cstr(dev_file_name))) { - break; - } - - // Open file - if(!flipper_format_file_open_always( - instance->flipper_format, string_get_cstr(dev_file_name))) { - FURI_LOG_E(TAG, "Unable to open file for write: %s", dev_file_name); - break; - } - - if(!flipper_format_write_header_cstr( - instance->flipper_format, SUBGHZ_RAW_FILE_TYPE, SUBGHZ_RAW_FILE_VERSION)) { - FURI_LOG_E(TAG, "Unable to add header"); - break; - } - - if(!flipper_format_write_uint32(instance->flipper_format, "Frequency", &frequency, 1)) { - FURI_LOG_E(TAG, "Unable to add Frequency"); - break; - } - - if(!flipper_format_write_string_cstr(instance->flipper_format, "Preset", preset)) { - FURI_LOG_E(TAG, "Unable to add Preset"); - break; - } - - if(!flipper_format_write_string_cstr( - instance->flipper_format, "Protocol", instance->common.name)) { - FURI_LOG_E(TAG, "Unable to add Protocol"); - break; - } - - instance->upload_raw = malloc(SUBGHZ_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); - instance->file_is_open = RAWFileIsOpenWrite; - instance->sample_write = 0; - init = true; - } while(0); - - string_clear(dev_file_name); - - return init; -} - -void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolRAW* instance) { - furi_assert(instance); - - if(instance->file_is_open == RAWFileIsOpenWrite && instance->ind_write) - subghz_protocol_raw_save_to_file_write(instance); - if(instance->file_is_open != RAWFileIsOpenClose) { - free(instance->upload_raw); - instance->upload_raw = NULL; - } - - flipper_format_file_close(instance->flipper_format); - instance->file_is_open = RAWFileIsOpenClose; -} - -bool subghz_protocol_raw_save_to_file_write(SubGhzProtocolRAW* instance) { - furi_assert(instance); - - bool is_write = false; - if(instance->file_is_open == RAWFileIsOpenWrite) { - if(!flipper_format_write_int32( - instance->flipper_format, "RAW_Data", instance->upload_raw, instance->ind_write)) { - FURI_LOG_E(TAG, "Unable to add RAW_Data"); - } else { - instance->sample_write += instance->ind_write; - instance->ind_write = 0; - is_write = true; - } - } - return is_write; -} - -size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolRAW* instance) { - return instance->sample_write + instance->ind_write; -} - -bool subghz_protocol_raw_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolRAW* instance, - const char* file_path) { - furi_assert(file_path); - subghz_protocol_raw_set_last_file_name(instance, file_path); - return true; -} diff --git a/lib/subghz/protocols/subghz_protocol_raw.h b/lib/subghz/protocols/subghz_protocol_raw.h deleted file mode 100644 index ca7fc99c..00000000 --- a/lib/subghz/protocols/subghz_protocol_raw.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef void (*SubGhzProtocolRAWCallbackEnd)(void* context); - -typedef struct SubGhzProtocolRAW SubGhzProtocolRAW; - -/** Allocate SubGhzProtocolRAW - * - * @return SubGhzProtocolRAW* - */ -SubGhzProtocolRAW* subghz_protocol_raw_alloc(); - -/** Free SubGhzProtocolRAW - * - * @param instance - */ -void subghz_protocol_raw_free(SubGhzProtocolRAW* instance); - -void subghz_protocol_raw_file_encoder_worker_set_callback_end( - SubGhzProtocolRAW* instance, - SubGhzProtocolRAWCallbackEnd callback_end, - void* context_end); - -/** Reset internal state - * @param instance - SubGhzProtocolRAW instance - */ -void subghz_protocol_raw_reset(SubGhzProtocolRAW* instance); - -/** Get upload protocol - * - * @param instance - SubGhzProtocolRAW instance - * @param encoder - SubGhzProtocolCommonEncoder encoder - * @return bool - */ -bool subghz_protocol_raw_send_key( - SubGhzProtocolRAW* instance, - SubGhzProtocolCommonEncoder* encoder); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolRAW instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_raw_parse(SubGhzProtocolRAW* instance, bool level, uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolRAW* instance - * @param output - output string - */ -void subghz_protocol_raw_to_str(SubGhzProtocolRAW* instance, string_t output); - -const char* subghz_protocol_raw_get_last_file_name(SubGhzProtocolRAW* instance); - -void subghz_protocol_raw_set_last_file_name(SubGhzProtocolRAW* instance, const char* name); - -bool subghz_protocol_raw_save_to_file_init( - SubGhzProtocolRAW* instance, - const char* dev_name, - uint32_t frequency, - const char* preset); -void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolRAW* instance); -bool subghz_protocol_raw_save_to_file_write(SubGhzProtocolRAW* instance); -size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolRAW* instance); - -bool subghz_protocol_raw_to_load_protocol_from_file( - FlipperFormat* flipper_format, - SubGhzProtocolRAW* instance, - const char* file_path); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_scher_khan.c b/lib/subghz/protocols/subghz_protocol_scher_khan.c deleted file mode 100644 index 5e5d0446..00000000 --- a/lib/subghz/protocols/subghz_protocol_scher_khan.c +++ /dev/null @@ -1,244 +0,0 @@ -#include "subghz_protocol_scher_khan.h" - -//https://phreakerclub.com/72 -//https://phreakerclub.com/forum/showthread.php?t=7&page=2 -//https://phreakerclub.com/forum/showthread.php?t=274&highlight=magicar -//!!! https://phreakerclub.com/forum/showthread.php?t=489&highlight=magicar&page=5 - -struct SubGhzProtocolScherKhan { - SubGhzProtocolCommon common; - const char* protocol_name; -}; - -typedef enum { - ScherKhanDecoderStepReset = 0, - ScherKhanDecoderStepCheckPreambula, - ScherKhanDecoderStepSaveDuration, - ScherKhanDecoderStepCheckDuration, -} ScherKhanDecoderStep; - -SubGhzProtocolScherKhan* subghz_protocol_scher_khan_alloc(void) { - SubGhzProtocolScherKhan* instance = malloc(sizeof(SubGhzProtocolScherKhan)); - - instance->common.name = "Scher-Khan"; - instance->common.code_min_count_bit_for_found = 35; - instance->common.te_short = 750; - instance->common.te_long = 1100; - instance->common.te_delta = 150; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_scher_khan_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_scher_khan_to_load_protocol; - - return instance; -} - -void subghz_protocol_scher_khan_free(SubGhzProtocolScherKhan* instance) { - furi_assert(instance); - free(instance); -} - -/** Send bit - * - * @param instance - SubGhzProtocolScherKhan instance - * @param bit - bit - */ -// void subghz_protocol_scher_khan_send_bit(SubGhzProtocolScherKhan* instance, uint8_t bit) { -// if(bit) { -// //send bit 1 -// SUBGHZ_TX_PIN_HIGH(); -// delay_us(instance->common.te_long); -// SUBGHZ_TX_PIN_LOW(); -// delay_us(instance->common.te_short); -// } else { -// //send bit 0 -// SUBGHZ_TX_PIN_HIGH(); -// delay_us(instance->common.te_short); -// SUBGHZ_TX_PIN_LOW(); -// delay_us(instance->common.te_long); -// } -// } - -// void subghz_protocol_scher_khan_send_key( -// SubGhzProtocolScherKhan* instance, -// uint64_t key, -// uint8_t bit, -// uint8_t repeat) { -// while(repeat--) { -// SUBGHZ_TX_PIN_HIGH(); -// //Send header -// delay_us(instance->common.te_long * 2); -// SUBGHZ_TX_PIN_LOW(); -// delay_us(instance->common.te_long * 2); -// //Send key data -// for(uint8_t i = bit; i > 0; i--) { -// subghz_protocol_scher_khan_send_bit(instance, bit_read(key, i - 1)); -// } -// } -// } - -void subghz_protocol_scher_khan_reset(SubGhzProtocolScherKhan* instance) { - instance->common.parser_step = ScherKhanDecoderStepReset; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolScherKhan instance - */ -void subghz_protocol_scher_khan_check_remote_controller(SubGhzProtocolScherKhan* instance) { - /* - * MAGICAR 51 bit 00000001A99121DE83C3 MAGIC CODE, Dinamic - * 0E8C1619E830C -> 000011101000110000010110 0001 1001 1110 1000001100001100 - * 0E8C1629D830D -> 000011101000110000010110 0010 1001 1101 1000001100001101 - * 0E8C1649B830E -> 000011101000110000010110 0100 1001 1011 1000001100001110 - * 0E8C16897830F -> 000011101000110000010110 1000 1001 0111 1000001100001111 - * Serial Key Ser ~Key CNT - */ - - switch(instance->common.code_last_count_bit) { - // case 35: //MAGIC CODE, Static - // instance->protocol_name = "MAGIC CODE, Static"; - // break; - case 51: //MAGIC CODE, Dinamic - instance->protocol_name = "MAGIC CODE, Dinamic"; - instance->common.serial = ((instance->common.code_last_found >> 24) & 0xFFFFFF0) | - ((instance->common.code_last_found >> 20) & 0x0F); - instance->common.btn = (instance->common.code_last_found >> 24) & 0x0F; - instance->common.cnt = instance->common.code_last_found & 0xFFFF; - break; - // case 57: //MAGIC CODE PRO / PRO2 - // instance->protocol_name = "MAGIC CODE PRO / PRO2"; - // break; - - default: - instance->protocol_name = "Unknown"; - instance->common.serial = 0; - instance->common.btn = 0; - instance->common.cnt = 0; - break; - } -} - -void subghz_protocol_scher_khan_parse( - SubGhzProtocolScherKhan* instance, - bool level, - uint32_t duration) { - switch(instance->common.parser_step) { - case ScherKhanDecoderStepReset: - if((level) && - (DURATION_DIFF(duration, instance->common.te_short * 2) < instance->common.te_delta)) { - instance->common.parser_step = ScherKhanDecoderStepCheckPreambula; - instance->common.te_last = duration; - instance->common.header_count = 0; - } - break; - case ScherKhanDecoderStepCheckPreambula: - if(level) { - if((DURATION_DIFF(duration, instance->common.te_short * 2) < - instance->common.te_delta) || - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - instance->common.te_last = duration; - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - } else if( - (DURATION_DIFF(duration, instance->common.te_short * 2) < instance->common.te_delta) || - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - if(DURATION_DIFF(instance->common.te_last, instance->common.te_short * 2) < - instance->common.te_delta) { - // Found header - instance->common.header_count++; - break; - } else if( - DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) { - // Found start bit - if(instance->common.header_count >= 2) { - instance->common.parser_step = ScherKhanDecoderStepSaveDuration; - instance->common.code_found = 0; - instance->common.code_count_bit = 1; - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - break; - case ScherKhanDecoderStepSaveDuration: - if(level) { - if(duration >= (instance->common.te_long + instance->common.te_delta * 2)) { - //Found stop bit - instance->common.parser_step = ScherKhanDecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = ScherKhanDecoderStepCheckDuration; - } - - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - break; - case ScherKhanDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = ScherKhanDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = ScherKhanDecoderStepSaveDuration; - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; - } - break; - } -} - -void subghz_protocol_scher_khan_to_str(SubGhzProtocolScherKhan* instance, string_t output) { - subghz_protocol_scher_khan_check_remote_controller(instance); - - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:%07lX Btn:%lX Cnt:%04X\r\n" - "Pt: %s\r\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found >> 32), - (uint32_t)instance->common.code_last_found, - instance->common.serial, - instance->common.btn, - instance->common.cnt, - instance->protocol_name); -} - -void subghz_decoder_scher_khan_to_load_protocol(SubGhzProtocolScherKhan* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_scher_khan_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_scher_khan.h b/lib/subghz/protocols/subghz_protocol_scher_khan.h deleted file mode 100644 index ccbccf2f..00000000 --- a/lib/subghz/protocols/subghz_protocol_scher_khan.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolScherKhan SubGhzProtocolScherKhan; - -/** Allocate SubGhzProtocolScherKhan - * - * @return SubGhzProtocolScherKhan* - */ -SubGhzProtocolScherKhan* subghz_protocol_scher_khan_alloc(); - -/** Free SubGhzProtocolScherKhan - * - * @param instance - */ -void subghz_protocol_scher_khan_free(SubGhzProtocolScherKhan* instance); - -/** Sends the key on the air - * - * @param instance - SubGhzProtocolScherKhan instance - * @param key - key send - * @param bit - count bit key - * @param repeat - repeat send key - */ -void subghz_protocol_scher_khan_send_key( - SubGhzProtocolScherKhan* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat); - -/** Reset internal state - * @param instance - SubGhzProtocolScherKhan instance - */ -void subghz_protocol_scher_khan_reset(SubGhzProtocolScherKhan* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolScherKhan instance - */ -void subghz_protocol_scher_khan_check_remote_controller(SubGhzProtocolScherKhan* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolScherKhan instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_scher_khan_parse( - SubGhzProtocolScherKhan* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolScherKhan* instance - * @param output - output string - */ -void subghz_protocol_scher_khan_to_str(SubGhzProtocolScherKhan* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolScherKhan instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_scher_khan_to_load_protocol(SubGhzProtocolScherKhan* instance, void* context); diff --git a/lib/subghz/protocols/subghz_protocol_somfy_keytis.c b/lib/subghz/protocols/subghz_protocol_somfy_keytis.c deleted file mode 100644 index a58100db..00000000 --- a/lib/subghz/protocols/subghz_protocol_somfy_keytis.c +++ /dev/null @@ -1,345 +0,0 @@ -#include "subghz_protocol_somfy_keytis.h" -#include "subghz_protocol_common.h" -#include - -#define TAG "SubGhzSomfyKeytis" - -struct SubGhzProtocolSomfyKeytis { - SubGhzProtocolCommon common; - ManchesterState manchester_saved_state; - uint32_t press_duration_counter; -}; - -typedef enum { - SomfyKeytisDecoderStepReset = 0, - SomfyKeytisDecoderStepCheckPreambula, - SomfyKeytisDecoderStepFoundPreambula, - SomfyKeytisDecoderStepStartDecode, - SomfyKeytisDecoderStepDecoderData, -} SomfyKeytisDecoderStep; - -SubGhzProtocolSomfyKeytis* subghz_protocol_somfy_keytis_alloc() { - SubGhzProtocolSomfyKeytis* instance = malloc(sizeof(SubGhzProtocolSomfyKeytis)); - - instance->common.name = "Somfy Keytis"; - instance->common.code_min_count_bit_for_found = 80; - instance->common.te_short = 640; - instance->common.te_long = 1280; - instance->common.te_delta = 250; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_somfy_keytis_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_somfy_keytis_to_load_protocol; - - return instance; -} - -void subghz_protocol_somfy_keytis_free(SubGhzProtocolSomfyKeytis* instance) { - furi_assert(instance); - free(instance); -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolSomfyKeytis instance - */ -void subghz_protocol_somfy_keytis_remote_controller(SubGhzProtocolSomfyKeytis* instance) { - //https://pushstack.wordpress.com/somfy-rts-protocol/ - /* - * 604 us - * / - * | 2416us | 2416us | 2416us | 2416us | 4550 us | | - * - * +--------+ +--------+ +---...---+ - * + +--------+ +--------+ +--+XXXX...XXX+ - * - * | hw. sync. | soft. | | - * | | sync. | data | - * - * - * encrypt | decrypt - * - * package 80 bit pdc key btn crc cnt serial - * - * 0xA453537C4B9855 C40019 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 C80026 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 CC0033 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 D00049 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 D4005C => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 D80063 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 DC0076 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 E00086 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 E40093 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 E800AC => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 EC00B9 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 F000C3 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 F400D6 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 F800E9 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 FC00FC => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 FC0102 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 FC0113 => 0xA 4 F 7 002F 37D3CD - * 0xA453537C4B9855 FC0120 => 0xA 4 F 7 002F 37D3CD - * .......... - * 0xA453537C4B9855 FC048F => 0xA 4 F 7 002F 37D3CD - * - * Pdc: "Press Duration Counter" the total delay of the button is sent 72 parcels, - * pdc cnt4b cnt8b pdc_crc - * C40019 => 11 0001 00 0000 00000001 1001 - * C80026 => 11 0010 00 0000 00000010 0110 - * CC0033 => 11 0011 00 0000 00000011 0011 - * D00049 => 11 0100 00 0000 00000100 1001 - * D4005C => 11 0101 00 0000 00000101 1100 - * D80063 => 11 0110 00 0000 00000110 0011 - * DC0076 => 11 0111 00 0000 00000111 0110 - * E00086 => 11 1000 00 0000 00001000 0110 - * E40093 => 11 1001 00 0000 00001001 0011 - * E800AC => 11 1010 00 0000 00001010 1100 - * EC00B9 => 11 1011 00 0000 00001011 1001 - * F000C3 => 11 1100 00 0000 00001100 0011 - * F400D6 => 11 1101 00 0000 00001101 0110 - * F800E9 => 11 1110 00 0000 00001110 1001 - * FC00FC => 11 1111 00 0000 00001111 1100 - * FC0102 => 11 1111 00 0000 00010000 0010 - * FC0113 => 11 1111 00 0000 00010001 0011 - * FC0120 => 11 1111 00 0000 00010010 0000 - * - * Cnt4b: 4-bit counter changes from 1 to 15 then always equals 15 - * Cnt8b: 8-bit counter changes from 1 to 72 (0x48) - * Ppdc_crc: - * uint8_t crc=0; - * for(i=4; i<24; i+=4){ - * crc ^=(pdc>>i); - * } - * return crc; - * example: crc = 1^0^0^4^C = 9 - * 11, 00, 0000: const - * - * Key: “Encryption Key”, Most significant 4-bit are always 0xA, Least Significant bits is - * a linear counter. In the Smoove Origin this counter is increased together with the - * rolling code. But leaving this on a constant value also works. Gerardwr notes that - * for some other types of remotes the MSB is not constant. - * Btn: 4-bit Control codes, this indicates the button that is pressed - * CRC: 4-bit Checksum. - * Ctn: 16-bit rolling code (big-endian) increased with every button press. - * Serial: 24-bit identifier of sending device (little-endian) - * - * - * Decrypt - * - * uint8_t frame[7]; - * for (i=1; i < 7; i++) { - * frame[i] = frame[i] ^ frame[i-1]; - * } - * or - * uint64 Decrypt = frame ^ (frame>>8); - * - * CRC - * - * uint8_t frame[7]; - * for (i=0; i < 7; i++) { - * crc = crc ^ frame[i] ^ (frame[i] >> 4); - * } - * crc = crc & 0xf; - * - */ - - uint64_t data = instance->common.code_last_found ^ (instance->common.code_last_found >> 8); - instance->common.btn = (data >> 48) & 0xF; - instance->common.cnt = (data >> 24) & 0xFFFF; - instance->common.serial = data & 0xFFFFFF; -} - -uint8_t subghz_protocol_somfy_keytis_crc(uint64_t data) { - uint8_t crc = 0; - data &= 0xFFF0FFFFFFFFFF; - for(uint8_t i = 0; i < 56; i += 8) { - crc = crc ^ data >> i ^ (data >> (i + 4)); - } - return crc & 0xf; -} -const char* subghz_protocol_somfy_keytis_get_name_button(uint8_t btn) { - const char* name_btn[0x10] = { - "Unknown", - "0x01", - "0x02", - "Prog", - "Key_1", - "0x05", - "0x06", - "0x07", - "0x08", - "0x09", - "0x0A", - "0x0B", - "0x0C", - "0x0D", - "0x0E", - "0x0F"}; - return btn <= 0xf ? name_btn[btn] : name_btn[0]; -} - -uint32_t subghz_protocol_somfy_keytis_get_press_duration(void* context) { - SubGhzProtocolSomfyKeytis* instance = context; - return instance->press_duration_counter; -} - -void subghz_protocol_somfy_keytis_reset(SubGhzProtocolSomfyKeytis* instance) { - instance->common.parser_step = SomfyKeytisDecoderStepReset; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); -} - -void subghz_protocol_somfy_keytis_parse( - SubGhzProtocolSomfyKeytis* instance, - bool level, - uint32_t duration) { - ManchesterEvent event = ManchesterEventReset; - switch(instance->common.parser_step) { - case SomfyKeytisDecoderStepReset: - if((level) && DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta * 4) { - instance->common.parser_step = SomfyKeytisDecoderStepFoundPreambula; - instance->common.header_count++; - } - break; - case SomfyKeytisDecoderStepFoundPreambula: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta * 4)) { - instance->common.parser_step = SomfyKeytisDecoderStepCheckPreambula; - } else { - instance->common.header_count = 0; - instance->common.parser_step = SomfyKeytisDecoderStepReset; - } - break; - case SomfyKeytisDecoderStepCheckPreambula: - if(level) { - if(DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta * 4) { - instance->common.parser_step = SomfyKeytisDecoderStepFoundPreambula; - instance->common.header_count++; - } else if( - (instance->common.header_count > 1) && - (DURATION_DIFF(duration, instance->common.te_short * 7) < - instance->common.te_delta * 4)) { - instance->common.parser_step = SomfyKeytisDecoderStepDecoderData; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->press_duration_counter = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongHigh, - &instance->manchester_saved_state, - NULL); - } - } - - break; - - case SomfyKeytisDecoderStepDecoderData: - if(!level) { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortLow; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongLow; - } else if(duration >= (instance->common.te_long + instance->common.te_delta)) { - if(instance->common.code_count_bit == - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - //check crc - uint64_t data_tmp = instance->common.code_last_found ^ - (instance->common.code_last_found >> 8); - if(((data_tmp >> 40) & 0xF) == subghz_protocol_somfy_keytis_crc(data_tmp)) { - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongHigh, - &instance->manchester_saved_state, - NULL); - instance->common.parser_step = SomfyKeytisDecoderStepReset; - } else { - instance->common.parser_step = SomfyKeytisDecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortHigh; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongHigh; - } else { - instance->common.parser_step = SomfyKeytisDecoderStepReset; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - - if(data_ok) { - if(instance->common.code_count_bit < 56) { - instance->common.code_found = (instance->common.code_found << 1) | data; - } else { - instance->press_duration_counter = (instance->press_duration_counter << 1) | - data; - } - - instance->common.code_count_bit++; - } - } - break; - } -} - -void subghz_protocol_somfy_keytis_to_str(SubGhzProtocolSomfyKeytis* instance, string_t output) { - subghz_protocol_somfy_keytis_remote_controller(instance); - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %db\r\n" - "%lX%08lX%06lX\r\n" - "Sn:0x%06lX \r\n" - "Cnt:0x%04X\r\n" - "Btn:%s\r\n", - - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->press_duration_counter, - instance->common.serial, - instance->common.cnt, - subghz_protocol_somfy_keytis_get_name_button(instance->common.btn)); -} - -void subghz_decoder_somfy_keytis_to_load_protocol( - SubGhzProtocolSomfyKeytis* instance, - void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - instance->press_duration_counter = data->param1; - subghz_protocol_somfy_keytis_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_somfy_keytis.h b/lib/subghz/protocols/subghz_protocol_somfy_keytis.h deleted file mode 100644 index c5aa4244..00000000 --- a/lib/subghz/protocols/subghz_protocol_somfy_keytis.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolSomfyKeytis SubGhzProtocolSomfyKeytis; - -/** Allocate SubGhzProtocolSomfyKeytis - * - * @return SubGhzProtocolSomfyKeytis* - */ -SubGhzProtocolSomfyKeytis* subghz_protocol_somfy_keytis_alloc(); - -/** Free SubGhzProtocolSomfyKeytis - * - * @param instance - */ -void subghz_protocol_somfy_keytis_free(SubGhzProtocolSomfyKeytis* instance); - -uint32_t subghz_protocol_somfy_keytis_get_press_duration(void* context); - -/** Reset internal state - * @param instance - SubGhzProtocolSomfyKeytis instance - */ -void subghz_protocol_somfy_keytis_reset(SubGhzProtocolSomfyKeytis* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolSomfyKeytis instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_somfy_keytis_parse( - SubGhzProtocolSomfyKeytis* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolSomfyKeytis* instance - * @param output - output string - */ -void subghz_protocol_somfy_keytis_to_str(SubGhzProtocolSomfyKeytis* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolSomfyKeytis instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_somfy_keytis_to_load_protocol( - SubGhzProtocolSomfyKeytis* instance, - void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_somfy_telis.c b/lib/subghz/protocols/subghz_protocol_somfy_telis.c deleted file mode 100644 index 8de0ec27..00000000 --- a/lib/subghz/protocols/subghz_protocol_somfy_telis.c +++ /dev/null @@ -1,293 +0,0 @@ -#include "subghz_protocol_somfy_telis.h" -#include "subghz_protocol_common.h" -#include - -#define TAG "SubGhzSomfyTelis" - -struct SubGhzProtocolSomfyTelis { - SubGhzProtocolCommon common; - ManchesterState manchester_saved_state; -}; - -typedef enum { - SomfyTelisDecoderStepReset = 0, - SomfyTelisDecoderStepCheckPreambula, - SomfyTelisDecoderStepFoundPreambula, - SomfyTelisDecoderStepStartDecode, - SomfyTelisDecoderStepDecoderData, -} SomfyTelisDecoderStep; - -SubGhzProtocolSomfyTelis* subghz_protocol_somfy_telis_alloc() { - SubGhzProtocolSomfyTelis* instance = malloc(sizeof(SubGhzProtocolSomfyTelis)); - - instance->common.name = "Somfy Telis"; - instance->common.code_min_count_bit_for_found = 56; - instance->common.te_short = 640; - instance->common.te_long = 1280; - instance->common.te_delta = 250; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_somfy_telis_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_somfy_telis_to_load_protocol; - - return instance; -} - -void subghz_protocol_somfy_telis_free(SubGhzProtocolSomfyTelis* instance) { - furi_assert(instance); - free(instance); -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolSomfyTelis instance - */ -void subghz_protocol_somfy_telis_remote_controller(SubGhzProtocolSomfyTelis* instance) { - //https://pushstack.wordpress.com/somfy-rts-protocol/ - /* - * 604 us - * / - * | 2416us | 2416us | 2416us | 2416us | 4550 us | | 67648 us | 30415 us | - * - * +--------+ +--------+ +---...---+ - * + +--------+ +--------+ +--+XXXX...XXX+-----...----- - * - * | hw. sync. | soft. | | Inter-frame - * | | sync. | data | gap - * - * - * encrypt | decrypt - * - * package 56 bit cnt key btn|crc cnt serial - * 0xA7232323312222 - 0 => A7 8 0 | 00 00 | 12 13 00 - * 0xA7222223312222 - 1 => A7 8 5 | 00 01 | 12 13 00 - * 0xA7212123312222 - 2 => A7 8 6 | 00 02 | 12 13 00 - * - * Key: “Encryption Key”, Most significant 4-bit are always 0xA, Least Significant bits is - * a linear counter. In the Smoove Origin this counter is increased together with the - * rolling code. But leaving this on a constant value also works. Gerardwr notes that - * for some other types of remotes the MSB is not constant. - * Btn: 4-bit Control codes, this indicates the button that is pressed - * CRC: 4-bit Checksum. - * Ctn: 16-bit rolling code (big-endian) increased with every button press. - * Serial: 24-bit identifier of sending device (little-endian) - * - * - * Decrypt - * - * uint8_t frame[7]; - * for (i=1; i < 7; i++) { - * frame[i] = frame[i] ^ frame[i-1]; - * } - * or - * uint64 Decrypt = frame ^ (frame>>8); - * - * Btn - * - * Value Button(s) Description - * 0x1 My Stop or move to favourite position - * 0x2 Up Move up - * 0x3 My + Up Set upper motor limit in initial programming mode - * 0x4 Down Move down - * 0x5 My + Down Set lower motor limit in initial programming mode - * 0x6 Up + Down Change motor limit and initial programming mode - * 0x8 Prog Used for (de-)registering remotes, see below - * 0x9 Sun + Flag Enable sun and wind detector (SUN and FLAG symbol on the Telis Soliris RC) - * 0xA Flag Disable sun detector (FLAG symbol on the Telis Soliris RC) - * - * CRC - * - * uint8_t frame[7]; - * for (i=0; i < 7; i++) { - * cksum = cksum ^ frame[i] ^ (frame[i] >> 4); - * } - * cksum = cksum & 0xf; - * - */ - - uint64_t data = instance->common.code_last_found ^ (instance->common.code_last_found >> 8); - instance->common.btn = (data >> 44) & 0xF; - instance->common.cnt = (data >> 24) & 0xFFFF; - instance->common.serial = data & 0xFFFFFF; -} - -uint8_t subghz_protocol_somfy_telis_crc(uint64_t data) { - uint8_t crc = 0; - data &= 0xFFF0FFFFFFFFFF; - for(uint8_t i = 0; i < 56; i += 8) { - crc = crc ^ data >> i ^ (data >> (i + 4)); - } - return crc & 0xf; -} - -const char* subghz_protocol_somfy_telis_get_name_button(uint8_t btn) { - const char* name_btn[0x10] = { - "Unknown", - "My", - "Up", - "My+Up", - "Down", - "My+Down", - "Up+Down", - "0x07", - "Prog", - "Sun+Flag", - "Flag", - "0x0B", - "0x0C", - "0x0D", - "0x0E", - "0x0F"}; - return btn <= 0xf ? name_btn[btn] : name_btn[0]; -} - -void subghz_protocol_somfy_telis_reset(SubGhzProtocolSomfyTelis* instance) { - instance->common.parser_step = SomfyTelisDecoderStepReset; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); -} - -void subghz_protocol_somfy_telis_parse( - SubGhzProtocolSomfyTelis* instance, - bool level, - uint32_t duration) { - ManchesterEvent event = ManchesterEventReset; - switch(instance->common.parser_step) { - case SomfyTelisDecoderStepReset: - if((level) && DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta * 4) { - instance->common.parser_step = SomfyTelisDecoderStepFoundPreambula; - instance->common.header_count++; - } - break; - case SomfyTelisDecoderStepFoundPreambula: - if((!level) && (DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta * 4)) { - instance->common.parser_step = SomfyTelisDecoderStepCheckPreambula; - } else { - instance->common.header_count = 0; - instance->common.parser_step = SomfyTelisDecoderStepReset; - } - break; - case SomfyTelisDecoderStepCheckPreambula: - if(level) { - if(DURATION_DIFF(duration, instance->common.te_short * 4) < - instance->common.te_delta * 4) { - instance->common.parser_step = SomfyTelisDecoderStepFoundPreambula; - instance->common.header_count++; - } else if( - (instance->common.header_count > 1) && - (DURATION_DIFF(duration, instance->common.te_short * 7) < - instance->common.te_delta * 4)) { - instance->common.parser_step = SomfyTelisDecoderStepDecoderData; - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->common.header_count = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongHigh, - &instance->manchester_saved_state, - NULL); - } - } - - break; - - case SomfyTelisDecoderStepDecoderData: - if(!level) { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortLow; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongLow; - } else if(duration >= (instance->common.te_long + instance->common.te_delta)) { - if(instance->common.code_count_bit == - instance->common.code_min_count_bit_for_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - - //check crc - uint64_t data_tmp = instance->common.code_last_found ^ - (instance->common.code_last_found >> 8); - if(((data_tmp >> 40) & 0xF) == subghz_protocol_somfy_telis_crc(data_tmp)) { - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - manchester_advance( - instance->manchester_saved_state, - ManchesterEventLongHigh, - &instance->manchester_saved_state, - NULL); - instance->common.parser_step = SomfyTelisDecoderStepReset; - } else { - instance->common.parser_step = SomfyTelisDecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { - event = ManchesterEventShortHigh; - } else if(DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta) { - event = ManchesterEventLongHigh; - } else { - instance->common.parser_step = SomfyTelisDecoderStepReset; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - - if(data_ok) { - instance->common.code_found = (instance->common.code_found << 1) | data; - instance->common.code_count_bit++; - } - } - break; - } -} - -void subghz_protocol_somfy_telis_to_str(SubGhzProtocolSomfyTelis* instance, string_t output) { - subghz_protocol_somfy_telis_remote_controller(instance); - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - string_cat_printf( - output, - "%s %db\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%06lX \r\n" - "Cnt:0x%04X\r\n" - "Btn:%s\r\n", - - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - instance->common.serial, - instance->common.cnt, - subghz_protocol_somfy_telis_get_name_button(instance->common.btn)); -} - -void subghz_decoder_somfy_telis_to_load_protocol(SubGhzProtocolSomfyTelis* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_somfy_telis_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_somfy_telis.h b/lib/subghz/protocols/subghz_protocol_somfy_telis.h deleted file mode 100644 index 007ae829..00000000 --- a/lib/subghz/protocols/subghz_protocol_somfy_telis.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzProtocolSomfyTelis SubGhzProtocolSomfyTelis; - -/** Allocate SubGhzProtocolSomfyTelis - * - * @return SubGhzProtocolSomfyTelis* - */ -SubGhzProtocolSomfyTelis* subghz_protocol_somfy_telis_alloc(); - -/** Free SubGhzProtocolSomfyTelis - * - * @param instance - */ -void subghz_protocol_somfy_telis_free(SubGhzProtocolSomfyTelis* instance); - -/** Reset internal state - * @param instance - SubGhzProtocolSomfyTelis instance - */ -void subghz_protocol_somfy_telis_reset(SubGhzProtocolSomfyTelis* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolSomfyTelis instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_somfy_telis_parse( - SubGhzProtocolSomfyTelis* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolSomfyTelis* instance - * @param output - output string - */ -void subghz_protocol_somfy_telis_to_str(SubGhzProtocolSomfyTelis* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzProtocolSomfyTelis instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_somfy_telis_to_load_protocol(SubGhzProtocolSomfyTelis* instance, void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_star_line.c b/lib/subghz/protocols/subghz_protocol_star_line.c deleted file mode 100644 index 70d9c805..00000000 --- a/lib/subghz/protocols/subghz_protocol_star_line.c +++ /dev/null @@ -1,341 +0,0 @@ -#include "subghz_protocol_star_line.h" -#include "subghz_protocol_keeloq_common.h" - -#include "../subghz_keystore.h" - -#include - -#include -#include - -struct SubGhzProtocolStarLine { - SubGhzProtocolCommon common; - SubGhzKeystore* keystore; - const char* manufacture_name; -}; - -typedef enum { - StarLineDecoderStepReset = 0, - StarLineDecoderStepCheckPreambula, - StarLineDecoderStepSaveDuration, - StarLineDecoderStepCheckDuration, -} StarLineDecoderStep; - -SubGhzProtocolStarLine* subghz_protocol_star_line_alloc(SubGhzKeystore* keystore) { - SubGhzProtocolStarLine* instance = malloc(sizeof(SubGhzProtocolStarLine)); - - instance->keystore = keystore; - - instance->common.name = "Star Line"; - instance->common.code_min_count_bit_for_found = 64; - instance->common.te_short = 250; - instance->common.te_long = 500; - instance->common.te_delta = 120; - instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; - instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_star_line_to_str; - instance->common.to_load_protocol = - (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_star_line_to_load_protocol; - - return instance; -} - -void subghz_protocol_star_line_free(SubGhzProtocolStarLine* instance) { - furi_assert(instance); - free(instance); -} - -const char* subghz_protocol_star_line_find_and_get_manufacture_name(void* context) { - SubGhzProtocolStarLine* instance = context; - subghz_protocol_star_line_check_remote_controller(instance); - return instance->manufacture_name; -} - -const char* subghz_protocol_star_line_get_manufacture_name(void* context) { - SubGhzProtocolStarLine* instance = context; - return instance->manufacture_name; -} - -/** Send bit - * - * @param instance - SubGhzProtocolStarLine instance - * @param bit - bit - */ -void subghz_protocol_star_line_send_bit(SubGhzProtocolStarLine* instance, uint8_t bit) { - if(bit) { - //send bit 1 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_long); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_long); - } else { - //send bit 0 - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_short); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_short); - } -} - -void subghz_protocol_star_line_send_key( - SubGhzProtocolStarLine* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat) { - while(repeat--) { - //Send header - for(uint8_t i = 0; i < 6; i++) { - SUBGHZ_TX_PIN_HIGH(); - delay_us(instance->common.te_long * 2); - SUBGHZ_TX_PIN_LOW(); - delay_us(instance->common.te_long * 2); - } - //Send Start bit ?????????? - //Send key data - for(uint8_t i = bit; i > 0; i--) { - subghz_protocol_star_line_send_bit(instance, bit_read(key, i - 1)); - } - //Send Stop bit ?????????? - } -} - -void subghz_protocol_star_line_reset(SubGhzProtocolStarLine* instance) { - instance->common.parser_step = StarLineDecoderStepReset; -} - -/** Checking the accepted code against the database manafacture key - * - * @param instance SubGhzProtocolStarLine instance - * @param fix fix part of the parcel - * @param hop hop encrypted part of the parcel - * @return true on successful search - */ -uint8_t subghz_protocol_star_line_check_remote_controller_selector( - SubGhzProtocolStarLine* instance, - uint32_t fix, - uint32_t hop) { - uint16_t end_serial = (uint16_t)(fix & 0xFF); - uint8_t btn = (uint8_t)(fix >> 24); - uint32_t decrypt = 0; - uint64_t man_normal_learning; - - for - M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_SIMPLE: - //Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if((decrypt >> 24 == btn) && - ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - instance->common.cnt = decrypt & 0x0000FFFF; - return 1; - } - break; - case KEELOQ_LEARNING_NORMAL: - // Normal_Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man_normal_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); - if((decrypt >> 24 == btn) && - ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - instance->common.cnt = decrypt & 0x0000FFFF; - return 1; - } - break; - case KEELOQ_LEARNING_UNKNOWN: - // Simple Learning - decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); - if((decrypt >> 24 == btn) && - ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - instance->common.cnt = decrypt & 0x0000FFFF; - return 1; - } - // Check for mirrored man - uint64_t man_rev = 0; - uint64_t man_rev_byte = 0; - for(uint8_t i = 0; i < 64; i += 8) { - man_rev_byte = (uint8_t)(manufacture_code->key >> i); - man_rev = man_rev | man_rev_byte << (56 - i); - } - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); - if((decrypt >> 24 == btn) && - ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - instance->common.cnt = decrypt & 0x0000FFFF; - return 1; - } - //########################### - // Normal_Learning - // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 - man_normal_learning = - subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); - if((decrypt >> 24 == btn) && - ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - instance->common.cnt = decrypt & 0x0000FFFF; - return 1; - } - // Check for mirrored man - man_rev = 0; - man_rev_byte = 0; - for(uint8_t i = 0; i < 64; i += 8) { - man_rev_byte = (uint8_t)(manufacture_code->key >> i); - man_rev = man_rev | man_rev_byte << (56 - i); - } - man_normal_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); - decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); - if((decrypt >> 24 == btn) && - ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { - instance->manufacture_name = string_get_cstr(manufacture_code->name); - instance->common.cnt = decrypt & 0x0000FFFF; - return 1; - } - break; - } - } - - instance->manufacture_name = "Unknown"; - instance->common.cnt = 0; - - return 0; -} - -/** Analysis of received data - * - * @param instance SubGhzProtocolStarLine instance - */ -void subghz_protocol_star_line_check_remote_controller(SubGhzProtocolStarLine* instance) { - uint64_t key = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - uint32_t key_fix = key >> 32; - uint32_t key_hop = key & 0x00000000ffffffff; - - subghz_protocol_star_line_check_remote_controller_selector(instance, key_fix, key_hop); - - instance->common.serial = key_fix & 0x00FFFFFF; - instance->common.btn = key_fix >> 24; -} - -void subghz_protocol_star_line_parse( - SubGhzProtocolStarLine* instance, - bool level, - uint32_t duration) { - switch(instance->common.parser_step) { - case StarLineDecoderStepReset: - if(level) { - if(DURATION_DIFF(duration, instance->common.te_long * 2) < - instance->common.te_delta * 2) { - instance->common.parser_step = StarLineDecoderStepCheckPreambula; - instance->common.header_count++; - } else if(instance->common.header_count > 4) { - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->common.te_last = duration; - instance->common.parser_step = StarLineDecoderStepCheckDuration; - } - } else { - instance->common.header_count = 0; - } - break; - case StarLineDecoderStepCheckPreambula: - if((!level) && (DURATION_DIFF(duration, instance->common.te_long * 2) < - instance->common.te_delta * 2)) { - //Found Preambula - instance->common.parser_step = StarLineDecoderStepReset; - } else { - instance->common.header_count = 0; - instance->common.parser_step = StarLineDecoderStepReset; - } - break; - case StarLineDecoderStepSaveDuration: - if(level) { - if(duration >= (instance->common.te_long + instance->common.te_delta)) { - instance->common.parser_step = StarLineDecoderStepReset; - if(instance->common.code_count_bit >= - instance->common.code_min_count_bit_for_found) { - if(instance->common.code_last_found != instance->common.code_found) { - instance->common.code_last_found = instance->common.code_found; - instance->common.code_last_count_bit = instance->common.code_count_bit; - if(instance->common.callback) - instance->common.callback( - (SubGhzProtocolCommon*)instance, instance->common.context); - } - } - instance->common.code_found = 0; - instance->common.code_count_bit = 0; - instance->common.header_count = 0; - break; - } else { - instance->common.te_last = duration; - instance->common.parser_step = StarLineDecoderStepCheckDuration; - } - - } else { - instance->common.parser_step = StarLineDecoderStepReset; - } - break; - case StarLineDecoderStepCheckDuration: - if(!level) { - if((DURATION_DIFF(instance->common.te_last, instance->common.te_short) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 0); - instance->common.parser_step = StarLineDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->common.te_last, instance->common.te_long) < - instance->common.te_delta) && - (DURATION_DIFF(duration, instance->common.te_long) < instance->common.te_delta)) { - subghz_protocol_common_add_bit(&instance->common, 1); - instance->common.parser_step = StarLineDecoderStepSaveDuration; - } else { - instance->common.parser_step = StarLineDecoderStepReset; - } - } else { - instance->common.parser_step = StarLineDecoderStepReset; - } - break; - } -} - -void subghz_protocol_star_line_to_str(SubGhzProtocolStarLine* instance, string_t output) { - subghz_protocol_star_line_check_remote_controller(instance); - uint32_t code_found_hi = instance->common.code_last_found >> 32; - uint32_t code_found_lo = instance->common.code_last_found & 0x00000000ffffffff; - - uint64_t code_found_reverse = subghz_protocol_common_reverse_key( - instance->common.code_last_found, instance->common.code_last_count_bit); - - uint32_t code_found_reverse_hi = code_found_reverse >> 32; - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( - output, - "%s %dbit\r\n" - "Key:%08lX%08lX\r\n" - "Fix:0x%08lX Cnt:%04X\r\n" - "Hop:0x%08lX Btn:%02lX\r\n" - "MF:%s\r\n" - "Sn:0x%07lX \r\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo, - code_found_reverse_hi, - instance->common.cnt, - code_found_reverse_lo, - instance->common.btn, - instance->manufacture_name, - instance->common.serial); -} - -void subghz_decoder_star_line_to_load_protocol(SubGhzProtocolStarLine* instance, void* context) { - furi_assert(context); - furi_assert(instance); - SubGhzProtocolCommonLoad* data = context; - instance->common.code_last_found = data->code_found; - instance->common.code_last_count_bit = data->code_count_bit; - subghz_protocol_star_line_check_remote_controller(instance); -} diff --git a/lib/subghz/protocols/subghz_protocol_star_line.h b/lib/subghz/protocols/subghz_protocol_star_line.h deleted file mode 100644 index 9e89ff24..00000000 --- a/lib/subghz/protocols/subghz_protocol_star_line.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include "subghz_protocol_common.h" - -typedef struct SubGhzKeystore SubGhzKeystore; - -typedef struct SubGhzProtocolStarLine SubGhzProtocolStarLine; - -/** Allocate SubGhzProtocolStarLine - * - * @return SubGhzProtocolStarLine* - */ -SubGhzProtocolStarLine* subghz_protocol_star_line_alloc(SubGhzKeystore* keystore); - -/** Free SubGhzProtocolStarLine - * - * @param instance - */ -void subghz_protocol_star_line_free(SubGhzProtocolStarLine* instance); - -/** Find and get manufacture name - * - * @param context - SubGhzProtocolStarLine context - * @return name - char* manufacture name - */ -const char* subghz_protocol_star_line_find_and_get_manufacture_name(void* context); - -/** Get manufacture name - * - * @param context - SubGhzProtocolStarLine context - * @return name - char* manufacture name - */ -const char* subghz_protocol_star_line_get_manufacture_name(void* context); - -/** Sends the key on the air - * - * @param instance - SubGhzProtocolStarLine instance - * @param key - key send - * @param bit - count bit key - * @param repeat - repeat send key - */ -void subghz_protocol_star_line_send_key( - SubGhzProtocolStarLine* instance, - uint64_t key, - uint8_t bit, - uint8_t repeat); - -/** Reset internal state - * @param instance - SubGhzProtocolStarLine instance - */ -void subghz_protocol_star_line_reset(SubGhzProtocolStarLine* instance); - -/** Analysis of received data - * - * @param instance SubGhzProtocolStarLine instance - */ -void subghz_protocol_star_line_check_remote_controller(SubGhzProtocolStarLine* instance); - -/** Parse accepted duration - * - * @param instance - SubGhzProtocolStarLine instance - * @param data - LevelDuration level_duration - */ -void subghz_protocol_star_line_parse( - SubGhzProtocolStarLine* instance, - bool level, - uint32_t duration); - -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolStarLine* instance - * @param output - output string - */ -void subghz_protocol_star_line_to_str(SubGhzProtocolStarLine* instance, string_t output); - -/** Loading protocol from bin data - * - * @param instance - SubGhzDecoderPrinceton instance - * @param context - SubGhzProtocolCommonLoad context - */ -void subghz_decoder_star_line_to_load_protocol(SubGhzProtocolStarLine* instance, void* context); diff --git a/lib/subghz/receiver.c b/lib/subghz/receiver.c new file mode 100644 index 00000000..652a5f3c --- /dev/null +++ b/lib/subghz/receiver.c @@ -0,0 +1,121 @@ +#include "receiver.h" + +#include "protocols/registry.h" + +#include + +typedef struct { + SubGhzProtocolEncoderBase* base; +} SubGhzReceiverSlot; + +ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST); +#define M_OPL_SubGhzReceiverSlotArray_t() ARRAY_OPLIST(SubGhzReceiverSlotArray, M_POD_OPLIST) + +struct SubGhzReceiver { + SubGhzReceiverSlotArray_t slots; + SubGhzProtocolFlag filter; + + SubGhzReceiverCallback callback; + void* context; +}; + +SubGhzReceiver* subghz_receiver_alloc(SubGhzEnvironment* environment) { + SubGhzReceiver* instance = malloc(sizeof(SubGhzReceiver)); + SubGhzReceiverSlotArray_init(instance->slots); + + for(size_t i = 0; i < subghz_protocol_registry_count(); ++i) { + const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_index(i); + + if(protocol->decoder && protocol->decoder->alloc) { + SubGhzReceiverSlot* slot = SubGhzReceiverSlotArray_push_new(instance->slots); + slot->base = protocol->decoder->alloc(environment); + } + } + + instance->callback = NULL; + instance->context = NULL; + + return instance; +} + +void subghz_receiver_free(SubGhzReceiver* instance) { + furi_assert(instance); + + instance->callback = NULL; + instance->context = NULL; + + // Release allocated slots + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + slot->base->protocol->decoder->free(slot->base); + slot->base = NULL; + } + SubGhzReceiverSlotArray_clear(instance->slots); + + free(instance); +} + +void subghz_receiver_decode(SubGhzReceiver* instance, bool level, uint32_t duration) { + furi_assert(instance); + furi_assert(instance->slots); + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + if((slot->base->protocol->flag & instance->filter) == instance->filter) { + slot->base->protocol->decoder->feed(slot->base, level, duration); + } + } +} + +void subghz_receiver_reset(SubGhzReceiver* instance) { + furi_assert(instance); + furi_assert(instance->slots); + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + slot->base->protocol->decoder->reset(slot->base); + } +} + +static void subghz_receiver_rx_callback(SubGhzProtocolDecoderBase* decoder_base, void* context) { + SubGhzReceiver* instance = context; + if(instance->callback) { + instance->callback(instance, decoder_base, instance->context); + } +} + +void subghz_receiver_set_rx_callback( + SubGhzReceiver* instance, + SubGhzReceiverCallback callback, + void* context) { + furi_assert(instance); + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + subghz_protocol_decoder_base_set_decoder_callback( + (SubGhzProtocolDecoderBase*)slot->base, subghz_receiver_rx_callback, instance); + } + + instance->callback = callback; + instance->context = context; +} + +void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag filter) { + furi_assert(instance); + instance->filter = filter; +} + +SubGhzProtocolDecoderBase* subghz_receiver_search_decoder_base_by_name( + SubGhzReceiver* instance, + const char* decoder_name) { + SubGhzProtocolDecoderBase* result = NULL; + + for + M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { + if(strcmp(slot->base->protocol->name, decoder_name) == 0) { + result = (SubGhzProtocolDecoderBase*)slot->base; + break; + } + } + return result; +} diff --git a/lib/subghz/receiver.h b/lib/subghz/receiver.h new file mode 100644 index 00000000..eb66bcee --- /dev/null +++ b/lib/subghz/receiver.h @@ -0,0 +1,28 @@ +#pragma once + +#include "types.h" +#include "protocols/base.h" + +typedef struct SubGhzReceiver SubGhzReceiver; + +typedef void (*SubGhzReceiverCallback)( + SubGhzReceiver* decoder, + SubGhzProtocolDecoderBase* decoder_base, + void* context); + +SubGhzReceiver* subghz_receiver_alloc(SubGhzEnvironment* environment); + +void subghz_receiver_free(SubGhzReceiver* instance); + +void subghz_receiver_decode(SubGhzReceiver* instance, bool level, uint32_t duration); + +void subghz_receiver_reset(SubGhzReceiver* instance); + +void subghz_receiver_set_rx_callback( + SubGhzReceiver* instance, + SubGhzReceiverCallback callback, + void* context); + +void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag filter); +SubGhzProtocolDecoderBase* + subghz_receiver_search_decoder_base_by_name(SubGhzReceiver* instance, const char* decoder_name); diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 07acb4f6..0fbb5bbe 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -185,7 +185,7 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { SubGhzFileEncoderWorker* instance = malloc(sizeof(SubGhzFileEncoderWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubghzFEWorker"); + furi_thread_set_name(instance->thread, "SubGhzFEWorker"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_file_encoder_worker_thread); diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index b63ea697..d0efc6a0 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -224,6 +224,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { FURI_LOG_E(TAG, "Unknown encryption"); break; } + FURI_LOG_I(TAG, "Loading keystore %s", file_name); } while(0); flipper_format_free(flipper_format); diff --git a/lib/subghz/subghz_parser.c b/lib/subghz/subghz_parser.c deleted file mode 100644 index f47acca5..00000000 --- a/lib/subghz/subghz_parser.c +++ /dev/null @@ -1,336 +0,0 @@ - -#include "subghz_parser.h" -#include "protocols/subghz_protocol_came.h" -#include "protocols/subghz_protocol_came_twee.h" -#include "protocols/subghz_protocol_came_atomo.h" -#include "protocols/subghz_protocol_cfm.h" -#include "protocols/subghz_protocol_keeloq.h" -#include "protocols/subghz_protocol_nice_flo.h" -#include "protocols/subghz_protocol_nice_flor_s.h" -#include "protocols/subghz_protocol_princeton.h" -#include "protocols/subghz_protocol_gate_tx.h" -#include "protocols/subghz_protocol_ido.h" -#include "protocols/subghz_protocol_faac_slh.h" -#include "protocols/subghz_protocol_nero_sketch.h" -#include "protocols/subghz_protocol_star_line.h" -#include "protocols/subghz_protocol_nero_radio.h" -#include "protocols/subghz_protocol_scher_khan.h" -#include "protocols/subghz_protocol_kia.h" -#include "protocols/subghz_protocol_raw.h" -#include "protocols/subghz_protocol_hormann.h" -#include "protocols/subghz_protocol_somfy_telis.h" -#include "protocols/subghz_protocol_somfy_keytis.h" - -#include "subghz_keystore.h" - -#include -#include - -#define SUBGHZ_PARSER_TAG "SubGhzParser" - -typedef enum { - SubGhzProtocolTypeCame, - SubGhzProtocolTypeCameTwee, - SubGhzProtocolTypeCameAtomo, - SubGhzProtocolTypeKeeloq, - SubGhzProtocolTypeNiceFlo, - SubGhzProtocolTypeNiceFlorS, - SubGhzProtocolTypePrinceton, - SubGhzProtocolTypeGateTX, - SubGhzProtocolTypeIDo, - SubGhzProtocolTypeFaacSLH, - SubGhzProtocolTypeNeroSketch, - SubGhzProtocolTypeStarLine, - SubGhzProtocolTypeNeroRadio, - SubGhzProtocolTypeScherKhan, - SubGhzProtocolTypeKIA, - SubGhzProtocolTypeRAW, - SubGhzProtocolTypeHormann, - SubGhzProtocolTypeSomfyTelis, - SubGhzProtocolTypeSomfyKeytis, - - SubGhzProtocolTypeMax, -} SubGhzProtocolType; - -struct SubGhzParser { - SubGhzKeystore* keystore; - - SubGhzProtocolCommon* protocols[SubGhzProtocolTypeMax]; - - SubGhzProtocolTextCallback text_callback; - void* text_callback_context; - SubGhzProtocolCommonCallbackDump parser_callback; - void* parser_callback_context; -}; - -static void subghz_parser_text_rx_callback(SubGhzProtocolCommon* parser, void* context) { - SubGhzParser* instance = context; - - string_t output; - string_init(output); - subghz_protocol_common_to_str((SubGhzProtocolCommon*)parser, output); - if(instance->text_callback) { - instance->text_callback(output, instance->text_callback_context); - } else { - printf("%s", string_get_cstr(output)); - } - string_clear(output); -} - -static void subghz_parser_parser_rx_callback(SubGhzProtocolCommon* parser, void* context) { - SubGhzParser* instance = context; - if(instance->parser_callback) { - instance->parser_callback(parser, instance->parser_callback_context); - } -} - -SubGhzParser* subghz_parser_alloc() { - SubGhzParser* instance = malloc(sizeof(SubGhzParser)); - - instance->keystore = subghz_keystore_alloc(); - - instance->protocols[SubGhzProtocolTypeCame] = - (SubGhzProtocolCommon*)subghz_protocol_came_alloc(); - instance->protocols[SubGhzProtocolTypeCameTwee] = - (SubGhzProtocolCommon*)subghz_protocol_came_twee_alloc(); - instance->protocols[SubGhzProtocolTypeCameAtomo] = - (SubGhzProtocolCommon*)subghz_protocol_came_atomo_alloc(); - instance->protocols[SubGhzProtocolTypeKeeloq] = - (SubGhzProtocolCommon*)subghz_protocol_keeloq_alloc(instance->keystore); - instance->protocols[SubGhzProtocolTypePrinceton] = - (SubGhzProtocolCommon*)subghz_decoder_princeton_alloc(); - instance->protocols[SubGhzProtocolTypeNiceFlo] = - (SubGhzProtocolCommon*)subghz_protocol_nice_flo_alloc(); - instance->protocols[SubGhzProtocolTypeNiceFlorS] = - (SubGhzProtocolCommon*)subghz_protocol_nice_flor_s_alloc(); - instance->protocols[SubGhzProtocolTypeGateTX] = - (SubGhzProtocolCommon*)subghz_protocol_gate_tx_alloc(); - instance->protocols[SubGhzProtocolTypeIDo] = - (SubGhzProtocolCommon*)subghz_protocol_ido_alloc(); - instance->protocols[SubGhzProtocolTypeFaacSLH] = - (SubGhzProtocolCommon*)subghz_protocol_faac_slh_alloc(); - instance->protocols[SubGhzProtocolTypeNeroSketch] = - (SubGhzProtocolCommon*)subghz_protocol_nero_sketch_alloc(); - instance->protocols[SubGhzProtocolTypeStarLine] = - (SubGhzProtocolCommon*)subghz_protocol_star_line_alloc(instance->keystore); - instance->protocols[SubGhzProtocolTypeNeroRadio] = - (SubGhzProtocolCommon*)subghz_protocol_nero_radio_alloc(); - instance->protocols[SubGhzProtocolTypeScherKhan] = - (SubGhzProtocolCommon*)subghz_protocol_scher_khan_alloc(); - instance->protocols[SubGhzProtocolTypeKIA] = - (SubGhzProtocolCommon*)subghz_protocol_kia_alloc(); - instance->protocols[SubGhzProtocolTypeRAW] = - (SubGhzProtocolCommon*)subghz_protocol_raw_alloc(); - instance->protocols[SubGhzProtocolTypeHormann] = - (SubGhzProtocolCommon*)subghz_protocol_hormann_alloc(); - instance->protocols[SubGhzProtocolTypeSomfyTelis] = - (SubGhzProtocolCommon*)subghz_protocol_somfy_telis_alloc(); - instance->protocols[SubGhzProtocolTypeSomfyKeytis] = - (SubGhzProtocolCommon*)subghz_protocol_somfy_keytis_alloc(); - - return instance; -} - -void subghz_parser_free(SubGhzParser* instance) { - furi_assert(instance); - - subghz_protocol_came_free((SubGhzProtocolCame*)instance->protocols[SubGhzProtocolTypeCame]); - subghz_protocol_came_twee_free( - (SubGhzProtocolCameTwee*)instance->protocols[SubGhzProtocolTypeCameTwee]); - subghz_protocol_came_atomo_free( - (SubGhzProtocolCameAtomo*)instance->protocols[SubGhzProtocolTypeCameAtomo]); - subghz_protocol_keeloq_free( - (SubGhzProtocolKeeloq*)instance->protocols[SubGhzProtocolTypeKeeloq]); - subghz_decoder_princeton_free( - (SubGhzDecoderPrinceton*)instance->protocols[SubGhzProtocolTypePrinceton]); - subghz_protocol_nice_flo_free( - (SubGhzProtocolNiceFlo*)instance->protocols[SubGhzProtocolTypeNiceFlo]); - subghz_protocol_nice_flor_s_free( - (SubGhzProtocolNiceFlorS*)instance->protocols[SubGhzProtocolTypeNiceFlorS]); - subghz_protocol_gate_tx_free( - (SubGhzProtocolGateTX*)instance->protocols[SubGhzProtocolTypeGateTX]); - subghz_protocol_ido_free((SubGhzProtocolIDo*)instance->protocols[SubGhzProtocolTypeIDo]); - subghz_protocol_faac_slh_free( - (SubGhzProtocolFaacSLH*)instance->protocols[SubGhzProtocolTypeFaacSLH]); - subghz_protocol_nero_sketch_free( - (SubGhzProtocolNeroSketch*)instance->protocols[SubGhzProtocolTypeNeroSketch]); - subghz_protocol_star_line_free( - (SubGhzProtocolStarLine*)instance->protocols[SubGhzProtocolTypeStarLine]); - subghz_protocol_nero_radio_free( - (SubGhzProtocolNeroRadio*)instance->protocols[SubGhzProtocolTypeNeroRadio]); - subghz_protocol_scher_khan_free( - (SubGhzProtocolScherKhan*)instance->protocols[SubGhzProtocolTypeScherKhan]); - subghz_protocol_kia_free((SubGhzProtocolKIA*)instance->protocols[SubGhzProtocolTypeKIA]); - subghz_protocol_raw_free((SubGhzProtocolRAW*)instance->protocols[SubGhzProtocolTypeRAW]); - subghz_protocol_hormann_free( - (SubGhzProtocolHormann*)instance->protocols[SubGhzProtocolTypeHormann]); - subghz_protocol_somfy_telis_free( - (SubGhzProtocolSomfyTelis*)instance->protocols[SubGhzProtocolTypeSomfyTelis]); - subghz_protocol_somfy_keytis_free( - (SubGhzProtocolSomfyKeytis*)instance->protocols[SubGhzProtocolTypeSomfyKeytis]); - - subghz_keystore_free(instance->keystore); - - free(instance); -} - -SubGhzProtocolCommon* subghz_parser_get_by_name(SubGhzParser* instance, const char* name) { - SubGhzProtocolCommon* result = NULL; - - for(size_t i = 0; i < SubGhzProtocolTypeMax; i++) { - if(strcmp(instance->protocols[i]->name, name) == 0) { - result = instance->protocols[i]; - break; - } - } - - return result; -} - -void subghz_parser_enable_dump_text( - SubGhzParser* instance, - SubGhzProtocolTextCallback callback, - void* context) { - furi_assert(instance); - - for(size_t i = 0; i < SubGhzProtocolTypeMax; i++) { - subghz_protocol_common_set_callback( - instance->protocols[i], subghz_parser_text_rx_callback, instance); - } - - instance->text_callback = callback; - instance->text_callback_context = context; -} - -void subghz_parser_enable_dump( - SubGhzParser* instance, - SubGhzProtocolCommonCallbackDump callback, - void* context) { - furi_assert(instance); - - for(size_t i = 0; i < SubGhzProtocolTypeMax; i++) { - subghz_protocol_common_set_callback( - instance->protocols[i], subghz_parser_parser_rx_callback, instance); - } - - instance->parser_callback = callback; - instance->parser_callback_context = context; -} - -void subghz_parser_load_nice_flor_s_file(SubGhzParser* instance, const char* file_name) { - subghz_protocol_nice_flor_s_name_file( - (SubGhzProtocolNiceFlorS*)instance->protocols[SubGhzProtocolTypeNiceFlorS], file_name); -} - -void subghz_parser_load_came_atomo_file(SubGhzParser* instance, const char* file_name) { - subghz_protocol_came_atomo_name_file( - (SubGhzProtocolCameAtomo*)instance->protocols[SubGhzProtocolTypeCameAtomo], file_name); -} - -bool subghz_parser_load_keeloq_file(SubGhzParser* instance, const char* file_name) { - bool ret = false; - if(subghz_keystore_load(instance->keystore, file_name)) { - FURI_LOG_I(SUBGHZ_PARSER_TAG, "Successfully loaded keeloq keys from %s", file_name); - ret = true; - } else { - FURI_LOG_W(SUBGHZ_PARSER_TAG, "Failed to load keeloq keysfrom %s", file_name); - } - return ret; -} - -void subghz_parser_reset(SubGhzParser* instance) { - subghz_protocol_came_reset((SubGhzProtocolCame*)instance->protocols[SubGhzProtocolTypeCame]); - subghz_protocol_came_twee_reset( - (SubGhzProtocolCameTwee*)instance->protocols[SubGhzProtocolTypeCameTwee]); - subghz_protocol_came_atomo_reset( - (SubGhzProtocolCameAtomo*)instance->protocols[SubGhzProtocolTypeCameAtomo]); - subghz_protocol_keeloq_reset( - (SubGhzProtocolKeeloq*)instance->protocols[SubGhzProtocolTypeKeeloq]); - subghz_decoder_princeton_reset( - (SubGhzDecoderPrinceton*)instance->protocols[SubGhzProtocolTypePrinceton]); - subghz_protocol_nice_flo_reset( - (SubGhzProtocolNiceFlo*)instance->protocols[SubGhzProtocolTypeNiceFlo]); - subghz_protocol_nice_flor_s_reset( - (SubGhzProtocolNiceFlorS*)instance->protocols[SubGhzProtocolTypeNiceFlorS]); - subghz_protocol_gate_tx_reset( - (SubGhzProtocolGateTX*)instance->protocols[SubGhzProtocolTypeGateTX]); - subghz_protocol_ido_reset((SubGhzProtocolIDo*)instance->protocols[SubGhzProtocolTypeIDo]); - subghz_protocol_faac_slh_reset( - (SubGhzProtocolFaacSLH*)instance->protocols[SubGhzProtocolTypeFaacSLH]); - subghz_protocol_nero_sketch_reset( - (SubGhzProtocolNeroSketch*)instance->protocols[SubGhzProtocolTypeNeroSketch]); - subghz_protocol_star_line_reset( - (SubGhzProtocolStarLine*)instance->protocols[SubGhzProtocolTypeStarLine]); - subghz_protocol_nero_radio_reset( - (SubGhzProtocolNeroRadio*)instance->protocols[SubGhzProtocolTypeNeroRadio]); - subghz_protocol_scher_khan_reset( - (SubGhzProtocolScherKhan*)instance->protocols[SubGhzProtocolTypeScherKhan]); - subghz_protocol_kia_reset((SubGhzProtocolKIA*)instance->protocols[SubGhzProtocolTypeKIA]); - subghz_protocol_raw_reset((SubGhzProtocolRAW*)instance->protocols[SubGhzProtocolTypeRAW]); - subghz_protocol_hormann_reset( - (SubGhzProtocolHormann*)instance->protocols[SubGhzProtocolTypeHormann]); - subghz_protocol_somfy_telis_reset( - (SubGhzProtocolSomfyTelis*)instance->protocols[SubGhzProtocolTypeSomfyTelis]); - subghz_protocol_somfy_keytis_reset( - (SubGhzProtocolSomfyKeytis*)instance->protocols[SubGhzProtocolTypeSomfyKeytis]); -} - -void subghz_parser_raw_parse(SubGhzParser* instance, bool level, uint32_t duration) { - subghz_protocol_raw_parse( - (SubGhzProtocolRAW*)instance->protocols[SubGhzProtocolTypeRAW], level, duration); -} - -void subghz_parser_parse(SubGhzParser* instance, bool level, uint32_t duration) { - subghz_protocol_came_parse( - (SubGhzProtocolCame*)instance->protocols[SubGhzProtocolTypeCame], level, duration); - subghz_protocol_came_twee_parse( - (SubGhzProtocolCameTwee*)instance->protocols[SubGhzProtocolTypeCameTwee], level, duration); - subghz_protocol_came_atomo_parse( - (SubGhzProtocolCameAtomo*)instance->protocols[SubGhzProtocolTypeCameAtomo], - level, - duration); - subghz_protocol_keeloq_parse( - (SubGhzProtocolKeeloq*)instance->protocols[SubGhzProtocolTypeKeeloq], level, duration); - subghz_decoder_princeton_parse( - (SubGhzDecoderPrinceton*)instance->protocols[SubGhzProtocolTypePrinceton], - level, - duration); - subghz_protocol_nice_flo_parse( - (SubGhzProtocolNiceFlo*)instance->protocols[SubGhzProtocolTypeNiceFlo], level, duration); - subghz_protocol_nice_flor_s_parse( - (SubGhzProtocolNiceFlorS*)instance->protocols[SubGhzProtocolTypeNiceFlorS], - level, - duration); - subghz_protocol_gate_tx_parse( - (SubGhzProtocolGateTX*)instance->protocols[SubGhzProtocolTypeGateTX], level, duration); - subghz_protocol_ido_parse( - (SubGhzProtocolIDo*)instance->protocols[SubGhzProtocolTypeIDo], level, duration); - subghz_protocol_faac_slh_parse( - (SubGhzProtocolFaacSLH*)instance->protocols[SubGhzProtocolTypeFaacSLH], level, duration); - subghz_protocol_nero_sketch_parse( - (SubGhzProtocolNeroSketch*)instance->protocols[SubGhzProtocolTypeNeroSketch], - level, - duration); - subghz_protocol_star_line_parse( - (SubGhzProtocolStarLine*)instance->protocols[SubGhzProtocolTypeStarLine], level, duration); - subghz_protocol_nero_radio_parse( - (SubGhzProtocolNeroRadio*)instance->protocols[SubGhzProtocolTypeNeroRadio], - level, - duration); - subghz_protocol_scher_khan_parse( - (SubGhzProtocolScherKhan*)instance->protocols[SubGhzProtocolTypeScherKhan], - level, - duration); - subghz_protocol_kia_parse( - (SubGhzProtocolKIA*)instance->protocols[SubGhzProtocolTypeKIA], level, duration); - subghz_protocol_hormann_parse( - (SubGhzProtocolHormann*)instance->protocols[SubGhzProtocolTypeHormann], level, duration); - subghz_protocol_somfy_telis_parse( - (SubGhzProtocolSomfyTelis*)instance->protocols[SubGhzProtocolTypeSomfyTelis], - level, - duration); - subghz_protocol_somfy_keytis_parse( - (SubGhzProtocolSomfyKeytis*)instance->protocols[SubGhzProtocolTypeSomfyKeytis], - level, - duration); -} diff --git a/lib/subghz/subghz_parser.h b/lib/subghz/subghz_parser.h deleted file mode 100644 index c5a46dd7..00000000 --- a/lib/subghz/subghz_parser.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include "protocols/subghz_protocol_common.h" - -typedef void (*SubGhzProtocolTextCallback)(string_t text, void* context); -typedef void (*SubGhzProtocolCommonCallbackDump)(SubGhzProtocolCommon* parser, void* context); - -typedef struct SubGhzParser SubGhzParser; - -/** Allocate SubGhzParser - * - * @return SubGhzParser* - */ -SubGhzParser* subghz_parser_alloc(); - -/** Free SubGhzParser - * - * @param instance - */ -void subghz_parser_free(SubGhzParser* instance); - -/** Get protocol by name - * - * @param instance - SubGhzParser instance - * @param name - name protocol - * @param SubGhzProtocolCommon - */ -SubGhzProtocolCommon* subghz_parser_get_by_name(SubGhzParser* instance, const char* name); - -/** Outputting data text from all parsers - * - * @param instance - SubGhzParser instance - * @param callback - SubGhzProtocolTextCallback callback - * @param context - */ -void subghz_parser_enable_dump_text( - SubGhzParser* instance, - SubGhzProtocolTextCallback callback, - void* context); - -/** Outputting data SubGhzParser from all parsers - * - * @param instance - SubGhzParser instance - * @param callback - SubGhzProtocolTextCallback callback - * @param context - */ -void subghz_parser_enable_dump( - SubGhzParser* instance, - SubGhzProtocolCommonCallbackDump callback, - void* context); - -/** File name rainbow table Nice Flor-S - * - * @param instance - SubGhzParser instance - * @param file_name - "path/file_name" - */ -void subghz_parser_load_nice_flor_s_file(SubGhzParser* instance, const char* file_name); - -/** File name rainbow table Came Atomo - * - * @param instance - SubGhzParser instance - * @param file_name - "path/file_name" - */ -void subghz_parser_load_came_atomo_file(SubGhzParser* instance, const char* file_name); - -/** File upload manufacture keys - * - * @param instance - SubGhzParser instance - * @param file_name - "path/file_name" - * @return bool - */ -bool subghz_parser_load_keeloq_file(SubGhzParser* instance, const char* file_name); - -/** Restarting all parsers - * - * @param instance - SubGhzParser instance - */ -void subghz_parser_reset(SubGhzParser* instance); - -void subghz_parser_raw_parse(SubGhzParser* instance, bool level, uint32_t duration); - -/** Loading data into all parsers - * - * @param instance - SubGhzParser instance - * @param level - true is high, false if low - * @param duration - level duration in microseconds - */ -void subghz_parser_parse(SubGhzParser* instance, bool level, uint32_t duration); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 38569039..f137b5fe 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -200,7 +200,7 @@ SubGhzTxRxWorker* subghz_tx_rx_worker_alloc() { SubGhzTxRxWorker* instance = malloc(sizeof(SubGhzTxRxWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubghzTxRxWorker"); + furi_thread_set_name(instance->thread, "SubGhzTxRxWorker"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_tx_rx_worker_thread); diff --git a/lib/subghz/subghz_worker.c b/lib/subghz/subghz_worker.c index 46d7fd9a..dcebdfff 100644 --- a/lib/subghz/subghz_worker.c +++ b/lib/subghz/subghz_worker.c @@ -93,7 +93,7 @@ SubGhzWorker* subghz_worker_alloc() { SubGhzWorker* instance = malloc(sizeof(SubGhzWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubghzWorker"); + furi_thread_set_name(instance->thread, "SubGhzWorker"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_worker_thread_callback); diff --git a/lib/subghz/transmitter.c b/lib/subghz/transmitter.c new file mode 100644 index 00000000..c74c38ea --- /dev/null +++ b/lib/subghz/transmitter.c @@ -0,0 +1,51 @@ +#include "transmitter.h" + +#include "protocols/base.h" +#include "protocols/registry.h" + +SubGhzTransmitter* + subghz_transmitter_alloc_init(SubGhzEnvironment* environment, const char* protocol_name) { + SubGhzTransmitter* instance = NULL; + const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_name(protocol_name); + + if(protocol && protocol->encoder && protocol->encoder->alloc) { + instance = malloc(sizeof(SubGhzTransmitter)); + instance->protocol = protocol; + instance->protocol_instance = instance->protocol->encoder->alloc(environment); + } + + return instance; +} + +void subghz_transmitter_free(SubGhzTransmitter* instance) { + furi_assert(instance); + instance->protocol->encoder->free(instance->protocol_instance); + free(instance); +} + +bool subghz_transmitter_stop(SubGhzTransmitter* instance) { + furi_assert(instance); + bool ret = false; + if(instance->protocol && instance->protocol->encoder && instance->protocol->encoder->stop) { + instance->protocol->encoder->stop(instance->protocol_instance); + ret = true; + } + return ret; +} + +bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool ret = false; + if(instance->protocol && instance->protocol->encoder && + instance->protocol->encoder->deserialize) { + ret = + instance->protocol->encoder->deserialize(instance->protocol_instance, flipper_format); + } + return ret; +} + +LevelDuration subghz_transmitter_yield(void* context) { + SubGhzTransmitter* instance = context; + + return instance->protocol->encoder->yield(instance->protocol_instance); +} diff --git a/lib/subghz/transmitter.h b/lib/subghz/transmitter.h new file mode 100644 index 00000000..80ecc40b --- /dev/null +++ b/lib/subghz/transmitter.h @@ -0,0 +1,23 @@ +#pragma once + +#include "types.h" +#include "environment.h" +#include "protocols/base.h" + +typedef struct SubGhzTransmitter SubGhzTransmitter; + +struct SubGhzTransmitter { + const SubGhzProtocol* protocol; + SubGhzProtocolEncoderBase* protocol_instance; +}; + +SubGhzTransmitter* + subghz_transmitter_alloc_init(SubGhzEnvironment* environment, const char* protocol_name); + +void subghz_transmitter_free(SubGhzTransmitter* instance); + +bool subghz_transmitter_stop(SubGhzTransmitter* instance); + +bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format); + +LevelDuration subghz_transmitter_yield(void* context); diff --git a/lib/subghz/types.h b/lib/subghz/types.h new file mode 100644 index 00000000..49f971f0 --- /dev/null +++ b/lib/subghz/types.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "environment.h" +#include +#include + +#define SUBGHZ_APP_FOLDER "/any/subghz" +#define SUBGHZ_RAW_FOLDER "/ext/subghz" +#define SUBGHZ_APP_EXTENSION ".sub" + +#define SUBGHZ_KEY_FILE_VERSION 1 +#define SUBGHZ_KEY_FILE_TYPE "Flipper SubGhz Key File" + +#define SUBGHZ_RAW_FILE_VERSION 1 +#define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" + +// +// Abstract method types +// + +// Allocator and Deallocator +typedef void* (*SubGhzAlloc)(SubGhzEnvironment* environment); +typedef void (*SubGhzFree)(void* context); + +// Serialize and Deserialize +typedef bool (*SubGhzSerialize)( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); +typedef bool (*SubGhzDeserialize)(void* context, FlipperFormat* flipper_format); + +// Decoder specific +typedef void (*SubGhzDecoderFeed)(void* decoder, bool level, uint32_t duration); +typedef void (*SubGhzDecoderReset)(void* decoder); +typedef uint8_t (*SubGhzGetHashData)(void* decoder); +typedef void (*SubGhzGetString)(void* decoder, string_t output); + +// Encoder specific +typedef void (*SubGhzEncoderStop)(void* encoder); +typedef LevelDuration (*SubGhzEncoderYield)(void* context); + +typedef struct { + SubGhzAlloc alloc; + SubGhzFree free; + + SubGhzDecoderFeed feed; + SubGhzDecoderReset reset; + + SubGhzGetHashData get_hash_data; + SubGhzGetString get_string; + SubGhzSerialize serialize; + SubGhzDeserialize deserialize; +} SubGhzProtocolDecoder; + +typedef struct { + SubGhzAlloc alloc; + SubGhzFree free; + + SubGhzDeserialize deserialize; + SubGhzEncoderStop stop; + SubGhzEncoderYield yield; +} SubGhzProtocolEncoder; + +typedef enum { + SubGhzProtocolTypeUnknown = 0, + SubGhzProtocolTypeStatic, + SubGhzProtocolTypeDynamic, + SubGhzProtocolTypeRAW, +} SubGhzProtocolType; + +typedef enum { + SubGhzProtocolFlag_RAW = (1 << 0), + SubGhzProtocolFlag_Decodable = (1 << 1), + SubGhzProtocolFlag_315 = (1 << 2), + SubGhzProtocolFlag_433 = (1 << 3), + SubGhzProtocolFlag_868 = (1 << 4), + SubGhzProtocolFlag_AM = (1 << 5), + SubGhzProtocolFlag_FM = (1 << 6), + SubGhzProtocolFlag_Save = (1 << 7), + SubGhzProtocolFlag_Load = (1 << 8), + SubGhzProtocolFlag_Send = (1 << 9), +} SubGhzProtocolFlag; + +typedef struct { + const char* name; + SubGhzProtocolType type; + SubGhzProtocolFlag flag; + + const SubGhzProtocolEncoder* encoder; + const SubGhzProtocolDecoder* decoder; +} SubGhzProtocol; diff --git a/make/rules.mk b/make/rules.mk index abb265d4..c67a3730 100644 --- a/make/rules.mk +++ b/make/rules.mk @@ -1,12 +1,16 @@ OBJ_DIR := $(OBJ_DIR)/$(TARGET) # Include source folder paths to virtual paths -VPATH = $(sort $(dir $(C_SOURCES)) $(dir $(ASM_SOURCES)) $(dir $(CPP_SOURCES))) +C_SOURCES := $(abspath ${C_SOURCES}) +ASM_SOURCES := $(abspath ${ASM_SOURCES}) +CPP_SOURCES := $(abspath ${CPP_SOURCES}) # Gather object -OBJECTS = $(addprefix $(OBJ_DIR)/, $(notdir $(C_SOURCES:.c=.o))) -OBJECTS += $(addprefix $(OBJ_DIR)/, $(notdir $(ASM_SOURCES:.s=.o))) -OBJECTS += $(addprefix $(OBJ_DIR)/, $(notdir $(CPP_SOURCES:.cpp=.o))) +OBJECTS = $(addprefix $(OBJ_DIR)/, $(C_SOURCES:.c=.o)) +OBJECTS += $(addprefix $(OBJ_DIR)/, $(ASM_SOURCES:.s=.o)) +OBJECTS += $(addprefix $(OBJ_DIR)/, $(CPP_SOURCES:.cpp=.o)) + +OBJECT_DIRS = $(sort $(dir $(OBJECTS))) # Generate dependencies DEPS = $(OBJECTS:.o=.d) @@ -15,7 +19,7 @@ ifdef DFU_SERIAL DFU_OPTIONS += -S $(DFU_SERIAL) endif -$(shell test -d $(OBJ_DIR) || mkdir -p $(OBJ_DIR)) +$(foreach dir, $(OBJECT_DIRS),$(shell mkdir -p $(dir))) BUILD_FLAGS_SHELL=\ echo "$(CFLAGS)" > $(OBJ_DIR)/BUILD_FLAGS.tmp; \ @@ -32,6 +36,7 @@ CHECK_AND_REINIT_SUBMODULES_SHELL=\ fi $(info $(shell $(CHECK_AND_REINIT_SUBMODULES_SHELL))) + all: $(OBJ_DIR)/$(PROJECT).elf $(OBJ_DIR)/$(PROJECT).hex $(OBJ_DIR)/$(PROJECT).bin $(OBJ_DIR)/$(PROJECT).dfu $(OBJ_DIR)/$(PROJECT).json @: @@ -58,18 +63,18 @@ $(OBJ_DIR)/$(PROJECT).dfu: $(OBJ_DIR)/$(PROJECT).bin $(OBJ_DIR)/$(PROJECT).json: $(OBJ_DIR)/$(PROJECT).dfu @echo "\tJSON\t" $@ - @../scripts/meta.py generate -p $(PROJECT) $(CFLAGS) > $(OBJ_DIR)/$(PROJECT).json + @$(PROJECT_ROOT)/scripts/meta.py generate -p $(PROJECT) $(CFLAGS) > $(OBJ_DIR)/$(PROJECT).json $(OBJ_DIR)/%.o: %.c $(OBJ_DIR)/BUILD_FLAGS - @echo "\tCC\t" $(subst $(PROJECT_ROOT)/,,$(realpath $<)) "->" $@ + @echo "\tCC\t" $(subst $(PROJECT_ROOT)/, , $<) @$(CC) $(CFLAGS) -c $< -o $@ $(OBJ_DIR)/%.o: %.s $(OBJ_DIR)/BUILD_FLAGS - @echo "\tASM\t" $(subst $(PROJECT_ROOT)/,,$(realpath $<)) "->" $@ + @echo "\tASM\t" $(subst $(PROJECT_ROOT)/, , $<) @$(AS) $(CFLAGS) -c $< -o $@ $(OBJ_DIR)/%.o: %.cpp $(OBJ_DIR)/BUILD_FLAGS - @echo "\tCPP\t" $(subst $(PROJECT_ROOT)/,,$(realpath $<)) "->" $@ + @echo "\tCPP\t" $(subst $(PROJECT_ROOT)/, , $<) @$(CPP) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ $(OBJ_DIR)/flash: $(OBJ_DIR)/$(PROJECT).bin @@ -80,10 +85,14 @@ $(OBJ_DIR)/upload: $(OBJ_DIR)/$(PROJECT).bin dfu-util -d 0483:df11 -D $(OBJ_DIR)/$(PROJECT).bin -a 0 -s $(FLASH_ADDRESS) $(DFU_OPTIONS) touch $@ + +.PHONY: flash flash: $(OBJ_DIR)/flash +.PHONY: upload upload: $(OBJ_DIR)/upload +.PHONY: debug debug: flash arm-none-eabi-gdb-py \ -ex 'target extended-remote | openocd -c "gdb_port pipe" $(OPENOCD_OPTS)' \ @@ -94,6 +103,7 @@ debug: flash -ex "compare-sections" \ $(OBJ_DIR)/$(PROJECT).elf; \ +.PHONY: debug_other debug_other: arm-none-eabi-gdb-py \ -ex 'target extended-remote | openocd -c "gdb_port pipe" $(OPENOCD_OPTS)' \ @@ -101,7 +111,7 @@ debug_other: -ex "source ../debug/PyCortexMDebug/PyCortexMDebug.py" \ -ex "svd_load $(SVD_FILE)" \ - +.PHONY: blackmagic blackmagic: arm-none-eabi-gdb-py \ -ex 'target extended-remote $(BLACKMAGIC)' \ @@ -116,22 +126,28 @@ blackmagic: -ex "compare-sections" \ $(OBJ_DIR)/$(PROJECT).elf; \ +.PHONY: openocd openocd: openocd $(OPENOCD_OPTS) +.PHONY: clean clean: @echo "\tCLEAN\t" - @$(RM) $(OBJ_DIR)/* + @$(RM) -rf $(OBJ_DIR) +.PHONY: z z: clean $(MAKE) all +.PHONY: zz zz: clean $(MAKE) flash +.PHONY: zzz zzz: clean $(MAKE) debug +.PHONY: generate_cscope_db generate_cscope_db: @echo "$(C_SOURCES) $(CPP_SOURCES) $(ASM_SOURCES)" | tr ' ' '\n' > $(OBJ_DIR)/source.list.p @cat ~/headers.list >> $(OBJ_DIR)/source.list.p @@ -139,7 +155,15 @@ generate_cscope_db: @cscope -b -k -i $(OBJ_DIR)/source.list -f $(OBJ_DIR)/cscope.out @rm -rf $(OBJ_DIR)/source.list $(OBJ_DIR)/source.list.p -# Prevent make from trying to find .d targets +# Prevent make from searching targets for real files %.d: ; +%.c: ; + +%.cpp: ; + +%.s: ; + +$(OBJ_DIR)/BUILD_FLAGS: ; + -include $(DEPS) diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index 5d088ea5..3c911bc4 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -16,8 +16,6 @@ class App: def __call__(self): self.args, _ = self.parser.parse_known_args() - if "func" not in self.args: - self.parser.error("Choose something to do") # configure log output self.log_level = logging.DEBUG if self.args.debug else logging.INFO self.logger.setLevel(self.log_level) @@ -29,7 +27,7 @@ class App: # execute requested function self.before() - return_code = self.args.func() + return_code = self.call() self.after() if isinstance(return_code, int): exit(return_code) @@ -37,6 +35,11 @@ class App: self.logger.error(f"Missing return code") exit(255) + def call(self): + if "func" not in self.args: + self.parser.error("Choose something to do") + return self.args.func() + def init(self): raise Exception("init() is not implemented") diff --git a/scripts/guruguru.py b/scripts/guruguru.py new file mode 100755 index 00000000..3560bcd9 --- /dev/null +++ b/scripts/guruguru.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +import asyncio +import os + +from flipper.app import App + + +class Main(App): + def init(self): + self.parser.add_argument("watch_list", nargs="+", help="Directories to watch") + self.is_building = False + + def clearConsole(self): + os.system("cls" if os.name in ("nt", "dos") else "clear") + + async def rebuild(self, line): + self.clearConsole() + self.logger.info(f"Triggered by: {line}") + proc = await asyncio.create_subprocess_exec("make") + await proc.wait() + await asyncio.sleep(1) + self.is_building = False + + async def run(self): + proc = await asyncio.create_subprocess_exec( + "fswatch", *self.args.watch_list, stdout=asyncio.subprocess.PIPE + ) + + while True: + data = await proc.stdout.readline() + line = data.decode().strip() + if not self.is_building: + self.is_building = True + asyncio.create_task(self.rebuild(line)) + + def call(self): + try: + asyncio.run(self.run()) + except KeyboardInterrupt: + pass + return 0 + + +if __name__ == "__main__": + Main()() From 3b8b2e59df8bdaac9956ae7521a91ca1f39da705 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 3 Mar 2022 23:30:53 +1000 Subject: [PATCH 04/13] SubGhz assets: niceflor is now in the correct state (#1011) --- assets/resources/subghz/{ => assets}/nice_flor_s | 0 assets/resources/subghz/assets/nice_flor_s_rx | 0 assets/resources/subghz/assets/nice_flor_s_tx | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename assets/resources/subghz/{ => assets}/nice_flor_s (100%) delete mode 100644 assets/resources/subghz/assets/nice_flor_s_rx delete mode 100644 assets/resources/subghz/assets/nice_flor_s_tx diff --git a/assets/resources/subghz/nice_flor_s b/assets/resources/subghz/assets/nice_flor_s similarity index 100% rename from assets/resources/subghz/nice_flor_s rename to assets/resources/subghz/assets/nice_flor_s diff --git a/assets/resources/subghz/assets/nice_flor_s_rx b/assets/resources/subghz/assets/nice_flor_s_rx deleted file mode 100644 index e69de29b..00000000 diff --git a/assets/resources/subghz/assets/nice_flor_s_tx b/assets/resources/subghz/assets/nice_flor_s_tx deleted file mode 100644 index e69de29b..00000000 From e0cebb4c1f5bbb1d99762474b79a63b235d032b9 Mon Sep 17 00:00:00 2001 From: Daniel Tse Date: Wed, 16 Mar 2022 00:21:14 -0700 Subject: [PATCH 05/13] Fixed typo in user message (#1025) --- applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c b/applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c index 7d12aa4b..d9948d4e 100755 --- a/applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c +++ b/applications/nfc/scenes/nfc_scene_run_emv_app_confirm.c @@ -17,7 +17,7 @@ void nfc_scene_run_emv_app_confirm_on_enter(void* context) { dialog_ex_set_header(dialog_ex, "Run EMV app?", 64, 8, AlignCenter, AlignCenter); dialog_ex_set_text( dialog_ex, - "It will try to run card's app\nand detect unencrypred\ndata", + "It will try to run card's app\nand detect unencrypted\ndata", 64, 18, AlignCenter, From 1251c0af8362ba30068ac95600742a1a3e6b2482 Mon Sep 17 00:00:00 2001 From: MuddledBox <101580720+MuddledBox@users.noreply.github.com> Date: Wed, 16 Mar 2022 02:25:07 -0500 Subject: [PATCH 06/13] Removed power 3v3 options (#1022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removed power 3v3 options Per skotopes, "3.3V: this line is used to power SD-Card it is highly unrecommended to turn this line off(power will be re-enabled to scan sdcard). We will remove this command in future releases." So I removed it here. No longer an option and should not be possible. * Added Debug Flag Detect When debug is enabled, "power 3v3" can be used and is shown in the help for "power" menu. When debug is disabled, will not show up and cannot be executed! * Update power_cli.c Co-authored-by: あく --- applications/power/power_cli.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/applications/power/power_cli.c b/applications/power/power_cli.c index c02f124e..f65fb095 100644 --- a/applications/power/power_cli.c +++ b/applications/power/power_cli.c @@ -54,7 +54,9 @@ static void power_cli_command_print_usage() { printf("\treboot2dfu\t - reboot to dfu bootloader\r\n"); printf("\tdebug\t - show debug information\r\n"); printf("\t5v <0 or 1>\t - enable or disable 5v ext\r\n"); - printf("\t3v3 <0 or 1>\t - enable or disable 3v3 ext\r\n"); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + printf("\t3v3 <0 or 1>\t - enable or disable 3v3 ext\r\n"); + } } void power_cli(Cli* cli, string_t args, void* context) { @@ -92,9 +94,11 @@ void power_cli(Cli* cli, string_t args, void* context) { break; } - if(string_cmp_str(cmd, "3v3") == 0) { - power_cli_3v3(cli, args); - break; + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + if(string_cmp_str(cmd, "3v3") == 0) { + power_cli_3v3(cli, args); + break; + } } power_cli_command_print_usage(); From b3ccdf74c81045873aaca0a2afcec6b9d540de6f Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 16 Mar 2022 10:31:47 +0300 Subject: [PATCH 07/13] BadUSB: fix \r\n line endings (#1017) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix to support badusb scripts with \r\n line endings * forgotten todo removed * get rid of magic numbers Co-authored-by: あく --- applications/bad_usb/bad_usb_script.c | 63 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/applications/bad_usb/bad_usb_script.c b/applications/bad_usb/bad_usb_script.c index 07df2bd1..a57a431b 100644 --- a/applications/bad_usb/bad_usb_script.c +++ b/applications/bad_usb/bad_usb_script.c @@ -12,12 +12,15 @@ #define WORKER_TAG TAG "Worker" #define FILE_BUFFER_LEN 16 +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) + typedef enum { - WorkerEvtReserved = (1 << 0), - WorkerEvtToggle = (1 << 1), - WorkerEvtEnd = (1 << 2), - WorkerEvtConnect = (1 << 3), - WorkerEvtDisconnect = (1 << 4), + WorkerEvtToggle = (1 << 0), + WorkerEvtEnd = (1 << 1), + WorkerEvtConnect = (1 << 2), + WorkerEvtDisconnect = (1 << 3), } WorkerEvtFlags; struct BadUsbScript { @@ -138,6 +141,10 @@ static uint32_t ducky_get_command_len(const char* line) { return 0; } +static bool ducky_is_line_end(const char chr) { + return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n')); +} + static void ducky_numlock_on() { if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { furi_hal_hid_kb_press(KEY_NUM_LOCK); @@ -163,7 +170,7 @@ static bool ducky_altchar(const char* charcode) { furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); - while((charcode[i] != ' ') && (charcode[i] != '\n') && (charcode[i] != '\0')) { + while(!ducky_is_line_end(charcode[i])) { state = ducky_numpad_press(charcode[i]); if(state == false) break; i++; @@ -196,8 +203,11 @@ static bool ducky_altstring(const char* param) { static bool ducky_string(const char* param) { uint32_t i = 0; while(param[i] != '\0') { - furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i])); - furi_hal_hid_kb_release(HID_ASCII_TO_KEY(param[i])); + uint16_t keycode = HID_ASCII_TO_KEY(param[i]); + if(keycode != KEY_NONE) { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } i++; } return true; @@ -207,8 +217,7 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { uint8_t key_cmd_len = strlen(ducky_keys[i].name); if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && - ((param[key_cmd_len] == ' ') || (param[key_cmd_len] == '\n') || - (param[key_cmd_len] == '\0'))) { + (ducky_is_line_end(param[key_cmd_len]))) { return ducky_keys[i].keycode; } } @@ -228,7 +237,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { line_tmp = &line_tmp[i]; break; // Skip spaces and tabs } - if(i == line_len - 1) return 0; // Skip empty lines + if(i == line_len - 1) return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } FURI_LOG_I(WORKER_TAG, "line:%s", line_tmp); @@ -245,25 +254,25 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { if((state) && (delay_val > 0)) { return (int32_t)delay_val; } - return (-1); + return SCRIPT_STATE_ERROR; } else if( (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || (strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { // DEFAULT_DELAY line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->defdelay); - return (state) ? (0) : (-1); + return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { // STRING line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_string(line_tmp); - return (state) ? (0) : (-1); + return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { // ALTCHAR line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; ducky_numlock_on(); state = ducky_altchar(line_tmp); - return (state) ? (0) : (-1); + return (state) ? (0) : SCRIPT_STATE_ERROR; } else if( (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || (strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) { @@ -271,16 +280,16 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; ducky_numlock_on(); state = ducky_altstring(line_tmp); - return (state) ? (0) : (-1); + return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { // REPEAT line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); - return (state) ? (0) : (-1); + return (state) ? (0) : SCRIPT_STATE_ERROR; } else { // Special keys + modifiers uint16_t key = ducky_get_keycode(line_tmp, false); - if(key == KEY_NONE) return (-1); + if(key == KEY_NONE) return SCRIPT_STATE_ERROR; if((key & 0xFF00) != 0) { // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; @@ -290,7 +299,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { furi_hal_hid_kb_release(key); return (0); } - return (-1); + return SCRIPT_STATE_ERROR; } static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) { @@ -326,10 +335,12 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(bad_usb->repeat_cnt > 0) { bad_usb->repeat_cnt--; delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); - if(delay_val < 0) { + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val < 0) { // Script error bad_usb->st.error_line = bad_usb->st.line_cur - 1; FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur - 1); - return (-1); + return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); } @@ -350,7 +361,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil } bad_usb->buf_start = 0; - if(bad_usb->buf_len == 0) return (-2); + if(bad_usb->buf_len == 0) return SCRIPT_STATE_END; } for(uint8_t i = bad_usb->buf_start; i < (bad_usb->buf_start + bad_usb->buf_len); i++) { if(bad_usb->file_buf[i] == '\n' && string_size(bad_usb->line) > 0) { @@ -361,7 +372,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur); - return (-1); + return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); } @@ -370,7 +381,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil } } bad_usb->buf_len = 0; - if(bad_usb->file_end) return (-2); + if(bad_usb->file_end) return SCRIPT_STATE_END; } return 0; @@ -479,11 +490,11 @@ static int32_t bad_usb_worker(void* context) { } bad_usb->st.state = BadUsbStateRunning; delay_val = ducky_script_execute_next(bad_usb, script_file); - if(delay_val == -1) { // Script error + if(delay_val == SCRIPT_STATE_ERROR) { // Script error delay_val = 0; worker_state = BadUsbStateScriptError; bad_usb->st.state = worker_state; - } else if(delay_val == -2) { // End of script + } else if(delay_val == SCRIPT_STATE_END) { // End of script delay_val = 0; worker_state = BadUsbStateIdle; bad_usb->st.state = BadUsbStateDone; From 76b737f411c82f3fc962cba15cbf974dc69fe4c4 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 16 Mar 2022 11:52:11 +0300 Subject: [PATCH 08/13] [FL-2257] RPC Refactoring (#1021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rpc: support for multiple sessions * removed debug prints * code formatting fix * compact build fix Co-authored-by: あく --- applications/gui/gui.c | 8 + applications/gui/gui.h | 9 + applications/rpc/rpc.c | 502 +++++++++++++++--------------- applications/rpc/rpc_app.c | 23 +- applications/rpc/rpc_cli.c | 2 +- applications/rpc/rpc_gui.c | 88 ++++-- applications/rpc/rpc_i.h | 21 +- applications/rpc/rpc_storage.c | 130 +++++--- applications/rpc/rpc_system.c | 114 +++---- applications/tests/rpc/rpc_test.c | 291 ++++++++++++----- 10 files changed, 712 insertions(+), 476 deletions(-) diff --git a/applications/gui/gui.c b/applications/gui/gui.c index 7d34f5e2..06733543 100644 --- a/applications/gui/gui.c +++ b/applications/gui/gui.c @@ -408,6 +408,14 @@ void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo } } +GuiCanvasCommitCallback gui_get_framebuffer_callback(Gui* gui) { + furi_assert(gui); + gui_lock(gui); + GuiCanvasCommitCallback callback = gui->canvas_callback; + gui_unlock(gui); + return callback; +} + void gui_set_lockdown(Gui* gui, bool lockdown) { furi_assert(gui); gui_lock(gui); diff --git a/applications/gui/gui.h b/applications/gui/gui.h index 5c209362..39d81c79 100644 --- a/applications/gui/gui.h +++ b/applications/gui/gui.h @@ -79,6 +79,15 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); */ void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); +/** Get gui canvas commit callback + * + * Can be used to check if some application is using framebufer + * + * @param gui Gui instance + * @return GuiCanvasCommitCallback + */ +GuiCanvasCommitCallback gui_get_framebuffer_callback(Gui* gui); + /** Set lockdown mode * * When lockdown mode is enabled, only GuiLayerDesktop is shown. diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index 7bdd9ab5..b67d036b 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -19,9 +19,12 @@ #define TAG "RpcSrv" -#define RPC_EVENT_NEW_DATA (1 << 0) -#define RPC_EVENT_DISCONNECT (1 << 1) -#define RPC_EVENTS_ALL (RPC_EVENT_DISCONNECT | RPC_EVENT_NEW_DATA) +typedef enum { + RpcEvtNewData = (1 << 0), + RpcEvtDisconnect = (1 << 1), +} RpcEvtFlags; + +#define RPC_ALL_EVENTS (RpcEvtNewData | RpcEvtDisconnect) DICT_DEF2(RpcHandlerDict, pb_size_t, M_DEFAULT_OPLIST, RpcHandler, M_POD_OPLIST) @@ -51,44 +54,45 @@ static RpcSystemCallbacks rpc_systems[] = { }; struct RpcSession { + Rpc* rpc; + + FuriThread* thread; + + RpcHandlerDict_t handlers; + StreamBufferHandle_t stream; + PB_Main* decoded_message; + bool terminate; + void** system_contexts; + bool decode_error; + + osMutexId_t callbacks_mutex; RpcSendBytesCallback send_bytes_callback; RpcBufferIsEmptyCallback buffer_is_empty_callback; RpcSessionClosedCallback closed_callback; RpcSessionTerminatedCallback terminated_callback; void* context; - osMutexId_t callbacks_mutex; - Rpc* rpc; - bool terminate; - void** system_contexts; - bool decode_error; }; struct Rpc { - bool busy; osMutexId_t busy_mutex; - RpcSession session; - osEventFlagsId_t events; - StreamBufferHandle_t stream; - RpcHandlerDict_t handlers; - PB_Main* decoded_message; }; static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg); -static void rpc_close_session_process(const PB_Main* msg_request, void* context) { - furi_assert(msg_request); - furi_assert(context); +static void rpc_close_session_process(const PB_Main* request, void* context) { + furi_assert(request); - Rpc* rpc = context; + RpcSession* session = (RpcSession*)context; + furi_assert(session); - rpc_send_and_release_empty(rpc, msg_request->command_id, PB_CommandStatus_OK); - osMutexAcquire(rpc->session.callbacks_mutex, osWaitForever); - if(rpc->session.closed_callback) { - rpc->session.closed_callback(rpc->session.context); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); + osMutexAcquire(session->callbacks_mutex, osWaitForever); + if(session->closed_callback) { + session->closed_callback(session->context); } else { - FURI_LOG_W(TAG, "Session stop doesn't processed by transport layer"); + FURI_LOG_W(TAG, "Session stop isn't processed by transport layer"); } - osMutexRelease(rpc->session.callbacks_mutex); + osMutexRelease(session->callbacks_mutex); } static size_t rpc_sprintf_msg_file( @@ -349,94 +353,8 @@ void rpc_print_message(const PB_Main* message) { string_clear(str); } -static Rpc* rpc_alloc(void) { - Rpc* rpc = malloc(sizeof(Rpc)); - rpc->busy_mutex = osMutexNew(NULL); - rpc->busy = false; - rpc->events = osEventFlagsNew(NULL); - rpc->stream = xStreamBufferCreate(RPC_BUFFER_SIZE, 1); - - rpc->decoded_message = malloc(sizeof(PB_Main)); - rpc->decoded_message->cb_content.funcs.decode = content_callback; - rpc->decoded_message->cb_content.arg = rpc; - - RpcHandlerDict_init(rpc->handlers); - - return rpc; -} - -RpcSession* rpc_session_open(Rpc* rpc) { - furi_assert(rpc); - bool result = false; - furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK); - if(rpc->busy) { - result = false; - } else { - rpc->busy = true; - result = true; - } - furi_check(osMutexRelease(rpc->busy_mutex) == osOK); - - if(result) { - RpcSession* session = &rpc->session; - session->callbacks_mutex = osMutexNew(NULL); - session->rpc = rpc; - session->terminate = false; - session->decode_error = false; - xStreamBufferReset(rpc->stream); - - session->system_contexts = malloc(COUNT_OF(rpc_systems) * sizeof(void*)); - for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { - session->system_contexts[i] = rpc_systems[i].alloc(rpc); - } - - RpcHandler rpc_handler = { - .message_handler = rpc_close_session_process, - .decode_submessage = NULL, - .context = rpc, - }; - rpc_add_handler(rpc, PB_Main_stop_session_tag, &rpc_handler); - - FURI_LOG_D(TAG, "Session started"); - } - - return result ? &rpc->session : NULL; /* support 1 open session for now */ -} - -void rpc_session_close(RpcSession* session) { - furi_assert(session); - furi_assert(session->rpc); - furi_assert(session->rpc->busy); - - rpc_session_set_send_bytes_callback(session, NULL); - rpc_session_set_close_callback(session, NULL); - rpc_session_set_buffer_is_empty_callback(session, NULL); - osEventFlagsSet(session->rpc->events, RPC_EVENT_DISCONNECT); -} - -static void rpc_free_session(RpcSession* session) { - furi_assert(session); - - for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - rpc_systems[i].free(session->system_contexts[i]); - } - } - free(session->system_contexts); - osMutexDelete(session->callbacks_mutex); - RpcHandlerDict_reset(session->rpc->handlers); - - session->context = NULL; - session->closed_callback = NULL; - session->send_bytes_callback = NULL; - session->buffer_is_empty_callback = NULL; - session->terminated_callback = NULL; -} - void rpc_session_set_context(RpcSession* session, void* context) { furi_assert(session); - furi_assert(session->rpc); - furi_assert(session->rpc->busy); osMutexAcquire(session->callbacks_mutex, osWaitForever); session->context = context; @@ -445,8 +363,6 @@ void rpc_session_set_context(RpcSession* session, void* context) { void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback) { furi_assert(session); - furi_assert(session->rpc); - furi_assert(session->rpc->busy); osMutexAcquire(session->callbacks_mutex, osWaitForever); session->closed_callback = callback; @@ -455,8 +371,6 @@ void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallbac void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback) { furi_assert(session); - furi_assert(session->rpc); - furi_assert(session->rpc->busy); osMutexAcquire(session->callbacks_mutex, osWaitForever); session->send_bytes_callback = callback; @@ -467,7 +381,6 @@ void rpc_session_set_buffer_is_empty_callback( RpcSession* session, RpcBufferIsEmptyCallback callback) { furi_assert(session); - furi_assert(session->rpc->busy); osMutexAcquire(session->callbacks_mutex, osWaitForever); session->buffer_is_empty_callback = callback; @@ -478,8 +391,6 @@ void rpc_session_set_terminated_callback( RpcSession* session, RpcSessionTerminatedCallback callback) { furi_assert(session); - furi_assert(session->rpc); - furi_assert(session->rpc->busy); osMutexAcquire(session->callbacks_mutex, osWaitForever); session->terminated_callback = callback; @@ -495,54 +406,54 @@ void rpc_session_set_terminated_callback( size_t rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { furi_assert(session); - Rpc* rpc = session->rpc; - furi_assert(rpc->busy); + size_t bytes_sent = xStreamBufferSend(session->stream, encoded_bytes, size, timeout); - size_t bytes_sent = xStreamBufferSend(rpc->stream, encoded_bytes, size, timeout); - osEventFlagsSet(rpc->events, RPC_EVENT_NEW_DATA); + osThreadFlagsSet(furi_thread_get_thread_id(session->thread), RpcEvtNewData); return bytes_sent; } size_t rpc_session_get_available_size(RpcSession* session) { furi_assert(session); - Rpc* rpc = session->rpc; - return xStreamBufferSpacesAvailable(rpc->stream); + return xStreamBufferSpacesAvailable(session->stream); } bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { - Rpc* rpc = istream->state; + RpcSession* session = istream->state; + furi_assert(session); + furi_assert(istream->bytes_left); + uint32_t flags = 0; size_t bytes_received = 0; - furi_assert(istream->bytes_left); - while(1) { bytes_received += - xStreamBufferReceive(rpc->stream, buf + bytes_received, count - bytes_received, 0); - if(xStreamBufferIsEmpty(rpc->stream)) { - if(rpc->session.buffer_is_empty_callback) { - rpc->session.buffer_is_empty_callback(rpc->session.context); + xStreamBufferReceive(session->stream, buf + bytes_received, count - bytes_received, 0); + if(xStreamBufferIsEmpty(session->stream)) { + if(session->buffer_is_empty_callback) { + session->buffer_is_empty_callback(session->context); } } - if(rpc->session.decode_error) { + if(session->decode_error) { /* never go out till RPC_EVENT_DISCONNECT come */ bytes_received = 0; } if(count == bytes_received) { break; } else { - flags = osEventFlagsWait(rpc->events, RPC_EVENTS_ALL, 0, osWaitForever); - if(flags & RPC_EVENT_DISCONNECT) { - if(xStreamBufferIsEmpty(rpc->stream)) { - rpc->session.terminate = true; + flags = osThreadFlagsWait(RPC_ALL_EVENTS, osFlagsWaitAny, osWaitForever); + if(flags & RpcEvtDisconnect) { + if(xStreamBufferIsEmpty(session->stream)) { + session->terminate = true; istream->bytes_left = 0; bytes_received = 0; break; } else { /* Save disconnect flag and continue reading buffer */ - osEventFlagsSet(rpc->events, RPC_EVENT_DISCONNECT); + osThreadFlagsSet(furi_thread_get_thread_id(session->thread), RpcEvtDisconnect); } + } else if(flags & RpcEvtNewData) { + // Just wake thread up } } } @@ -554,10 +465,210 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { return (count == bytes_received); } -void rpc_send_and_release(Rpc* rpc, PB_Main* message) { +static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) { + furi_assert(stream); + RpcSession* session = stream->state; + furi_assert(session); + + RpcHandler* handler = RpcHandlerDict_get(session->handlers, field->tag); + + if(handler && handler->decode_submessage) { + handler->decode_submessage(stream, field, arg); + } + + return true; +} + +static int32_t rpc_session_worker(void* context) { + furi_assert(context); + RpcSession* session = (RpcSession*)context; + Rpc* rpc = session->rpc; + + FURI_LOG_D(TAG, "Session started"); + + while(1) { + pb_istream_t istream = { + .callback = rpc_pb_stream_read, + .state = session, + .errmsg = NULL, + .bytes_left = RPC_MAX_MESSAGE_SIZE, /* max incoming message size */ + }; + + bool message_decode_failed = false; + + if(pb_decode_ex(&istream, &PB_Main_msg, session->decoded_message, PB_DECODE_DELIMITED)) { +#if SRV_RPC_DEBUG + FURI_LOG_I(TAG, "INPUT:"); + rpc_print_message(session->decoded_message); +#endif + RpcHandler* handler = + RpcHandlerDict_get(session->handlers, session->decoded_message->which_content); + + if(handler && handler->message_handler) { + furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK); + handler->message_handler(session->decoded_message, handler->context); + furi_check(osMutexRelease(rpc->busy_mutex) == osOK); + } else if(session->decoded_message->which_content == 0) { + /* Receiving zeroes means message is 0-length, which + * is valid for proto3: all fields are filled with default values. + * 0 - is default value for which_content field. + * Mark it as decode error, because there is no content message + * in Main message with tag 0. + */ + message_decode_failed = true; + } else if(!handler && !session->terminate) { + FURI_LOG_E( + TAG, + "Message(%d) decoded, but not implemented", + session->decoded_message->which_content); + rpc_send_and_release_empty( + session, + session->decoded_message->command_id, + PB_CommandStatus_ERROR_NOT_IMPLEMENTED); + } + } else { + message_decode_failed = true; + } + + if(message_decode_failed) { + xStreamBufferReset(session->stream); + if(!session->terminate) { + /* Protobuf can't determine start and end of message. + * Handle this by adding varint at beginning + * of a message (PB_ENCODE_DELIMITED). But decoding fail + * means we can't be sure next bytes are varint for next + * message, so the only way to close session. + * RPC itself can't make decision to close session. It has + * to notify: + * 1) down layer (transport) + * 2) other side (companion app) + * Who are responsible to handle RPC session lifecycle. + * Companion receives 2 messages: ERROR_DECODE and session_closed. + */ + FURI_LOG_E(TAG, "Decode failed, error: \'%.128s\'", PB_GET_ERROR(&istream)); + session->decode_error = true; + rpc_send_and_release_empty(session, 0, PB_CommandStatus_ERROR_DECODE); + osMutexAcquire(session->callbacks_mutex, osWaitForever); + if(session->closed_callback) { + session->closed_callback(session->context); + } + osMutexRelease(session->callbacks_mutex); + } + } + + pb_release(&PB_Main_msg, session->decoded_message); + + if(session->terminate) { + FURI_LOG_D(TAG, "Session terminated"); + break; + } + } + + return 0; +} + +static void rpc_session_free_callback(FuriThreadState thread_state, void* context) { + furi_assert(context); + + RpcSession* session = (RpcSession*)context; + + if(thread_state == FuriThreadStateStopped) { + for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + rpc_systems[i].free(session->system_contexts[i]); + } + } + free(session->system_contexts); + free(session->decoded_message); + RpcHandlerDict_clear(session->handlers); + vStreamBufferDelete(session->stream); + + osMutexAcquire(session->callbacks_mutex, osWaitForever); + if(session->terminated_callback) { + session->terminated_callback(session->context); + } + osMutexRelease(session->callbacks_mutex); + + osMutexDelete(session->callbacks_mutex); + furi_thread_free(session->thread); + free(session); + } +} + +RpcSession* rpc_session_open(Rpc* rpc) { furi_assert(rpc); + + RpcSession* session = malloc(sizeof(RpcSession)); + session->callbacks_mutex = osMutexNew(NULL); + session->stream = xStreamBufferCreate(RPC_BUFFER_SIZE, 1); + session->rpc = rpc; + session->terminate = false; + session->decode_error = false; + RpcHandlerDict_init(session->handlers); + + session->decoded_message = malloc(sizeof(PB_Main)); + session->decoded_message->cb_content.funcs.decode = content_callback; + session->decoded_message->cb_content.arg = session; + + session->system_contexts = malloc(COUNT_OF(rpc_systems) * sizeof(void*)); + for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { + session->system_contexts[i] = rpc_systems[i].alloc(session); + } + + RpcHandler rpc_handler = { + .message_handler = rpc_close_session_process, + .decode_submessage = NULL, + .context = session, + }; + rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); + + session->thread = furi_thread_alloc(); + furi_thread_set_name(session->thread, "RPC Session"); + furi_thread_set_stack_size(session->thread, 2048); + furi_thread_set_context(session->thread, session); + furi_thread_set_callback(session->thread, rpc_session_worker); + + furi_thread_set_state_context(session->thread, session); + furi_thread_set_state_callback(session->thread, rpc_session_free_callback); + + furi_thread_start(session->thread); + + return session; +} + +void rpc_session_close(RpcSession* session) { + furi_assert(session); + furi_assert(session->rpc); + + rpc_session_set_send_bytes_callback(session, NULL); + rpc_session_set_close_callback(session, NULL); + rpc_session_set_buffer_is_empty_callback(session, NULL); + osThreadFlagsSet(furi_thread_get_thread_id(session->thread), RpcEvtDisconnect); +} + +int32_t rpc_srv(void* p) { + Rpc* rpc = malloc(sizeof(Rpc)); + + rpc->busy_mutex = osMutexNew(NULL); + + Cli* cli = furi_record_open("cli"); + cli_add_command( + cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); + + furi_record_create("rpc", rpc); + + return 0; +} + +void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* handler) { + furi_assert(RpcHandlerDict_get(session->handlers, message_tag) == NULL); + + RpcHandlerDict_set_at(session->handlers, message_tag, *handler); +} + +void rpc_send_and_release(RpcSession* session, PB_Main* message) { + furi_assert(session); furi_assert(message); - RpcSession* session = &rpc->session; pb_ostream_t ostream = PB_OSTREAM_SIZING; #if SRV_RPC_DEBUG @@ -587,124 +698,13 @@ void rpc_send_and_release(Rpc* rpc, PB_Main* message) { pb_release(&PB_Main_msg, message); } -static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) { - furi_assert(stream); - Rpc* rpc = *arg; - - RpcHandler* handler = RpcHandlerDict_get(rpc->handlers, field->tag); - - if(handler && handler->decode_submessage) { - handler->decode_submessage(stream, field, arg); - } - - return true; -} - -int32_t rpc_srv(void* p) { - Rpc* rpc = rpc_alloc(); - furi_record_create("rpc", rpc); - - Cli* cli = furi_record_open("cli"); - - cli_add_command( - cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); - - while(1) { - pb_istream_t istream = { - .callback = rpc_pb_stream_read, - .state = rpc, - .errmsg = NULL, - .bytes_left = RPC_MAX_MESSAGE_SIZE, /* max incoming message size */ - }; - - bool message_decode_failed = false; - - if(pb_decode_ex(&istream, &PB_Main_msg, rpc->decoded_message, PB_DECODE_DELIMITED)) { -#if SRV_RPC_DEBUG - FURI_LOG_I(TAG, "INPUT:"); - rpc_print_message(rpc->decoded_message); -#endif - RpcHandler* handler = - RpcHandlerDict_get(rpc->handlers, rpc->decoded_message->which_content); - - if(handler && handler->message_handler) { - handler->message_handler(rpc->decoded_message, handler->context); - } else if(rpc->decoded_message->which_content == 0) { - /* Receiving zeroes means message is 0-length, which - * is valid for proto3: all fields are filled with default values. - * 0 - is default value for which_content field. - * Mark it as decode error, because there is no content message - * in Main message with tag 0. - */ - message_decode_failed = true; - } else if(!handler && !rpc->session.terminate) { - FURI_LOG_E( - TAG, - "Message(%d) decoded, but not implemented", - rpc->decoded_message->which_content); - rpc_send_and_release_empty( - rpc, rpc->decoded_message->command_id, PB_CommandStatus_ERROR_NOT_IMPLEMENTED); - } - } else { - message_decode_failed = true; - } - - if(message_decode_failed) { - xStreamBufferReset(rpc->stream); - if(!rpc->session.terminate) { - /* Protobuf can't determine start and end of message. - * Handle this by adding varint at beginning - * of a message (PB_ENCODE_DELIMITED). But decoding fail - * means we can't be sure next bytes are varint for next - * message, so the only way to close session. - * RPC itself can't make decision to close session. It has - * to notify: - * 1) down layer (transport) - * 2) other side (companion app) - * Who are responsible to handle RPC session lifecycle. - * Companion receives 2 messages: ERROR_DECODE and session_closed. - */ - FURI_LOG_E(TAG, "Decode failed, error: \'%.128s\'", PB_GET_ERROR(&istream)); - rpc->session.decode_error = true; - rpc_send_and_release_empty(rpc, 0, PB_CommandStatus_ERROR_DECODE); - osMutexAcquire(rpc->session.callbacks_mutex, osWaitForever); - if(rpc->session.closed_callback) { - rpc->session.closed_callback(rpc->session.context); - } - osMutexRelease(rpc->session.callbacks_mutex); - } - } - - pb_release(&PB_Main_msg, rpc->decoded_message); - - if(rpc->session.terminate) { - FURI_LOG_D(TAG, "Session terminated"); - osMutexAcquire(rpc->session.callbacks_mutex, osWaitForever); - if(rpc->session.terminated_callback) { - rpc->session.terminated_callback(rpc->session.context); - } - osMutexRelease(rpc->session.callbacks_mutex); - osEventFlagsClear(rpc->events, RPC_EVENTS_ALL); - rpc_free_session(&rpc->session); - rpc->busy = false; - } - } - return 0; -} - -void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler) { - furi_assert(RpcHandlerDict_get(rpc->handlers, message_tag) == NULL); - - RpcHandlerDict_set_at(rpc->handlers, message_tag, *handler); -} - -void rpc_send_and_release_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status) { +void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status) { PB_Main message = { .command_id = command_id, .command_status = status, .has_next = false, .which_content = PB_Main_empty_tag, }; - rpc_send_and_release(rpc, &message); + rpc_send_and_release(session, &message); pb_release(&PB_Main_msg, &message); } diff --git a/applications/rpc/rpc_app.c b/applications/rpc/rpc_app.c index 136edfc1..c35decf0 100644 --- a/applications/rpc/rpc_app.c +++ b/applications/rpc/rpc_app.c @@ -5,10 +5,11 @@ #include static void rpc_system_app_start_process(const PB_Main* request, void* context) { - Rpc* rpc = context; - furi_assert(rpc); furi_assert(request); furi_assert(request->which_content == PB_Main_app_start_request_tag); + RpcSession* session = (RpcSession*)context; + furi_assert(session); + PB_CommandStatus result = PB_CommandStatus_ERROR_APP_CANT_START; Loader* loader = furi_record_open("loader"); @@ -33,14 +34,14 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_record_close("loader"); - rpc_send_and_release_empty(rpc, request->command_id, result); + rpc_send_and_release_empty(session, request->command_id, result); } static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { - Rpc* rpc = context; - furi_assert(rpc); furi_assert(request); furi_assert(request->which_content == PB_Main_app_lock_status_request_tag); + RpcSession* session = (RpcSession*)context; + furi_assert(session); Loader* loader = furi_record_open("loader"); @@ -55,24 +56,24 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con furi_record_close("loader"); - rpc_send_and_release(rpc, &response); + rpc_send_and_release(session, &response); pb_release(&PB_Main_msg, &response); } -void* rpc_system_app_alloc(Rpc* rpc) { - furi_assert(rpc); +void* rpc_system_app_alloc(RpcSession* session) { + furi_assert(session); RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, - .context = rpc, + .context = session, }; rpc_handler.message_handler = rpc_system_app_start_process; - rpc_add_handler(rpc, PB_Main_app_start_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_app_start_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_app_lock_status_process; - rpc_add_handler(rpc, PB_Main_app_lock_status_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler); return NULL; } diff --git a/applications/rpc/rpc_cli.c b/applications/rpc/rpc_cli.c index 55cc3e49..36ae9ef3 100644 --- a/applications/rpc/rpc_cli.c +++ b/applications/rpc/rpc_cli.c @@ -31,7 +31,7 @@ void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { RpcSession* rpc_session = rpc_session_open(rpc); if(rpc_session == NULL) { - printf("Another session is in progress\r\n"); + printf("Session start error\r\n"); return; } diff --git a/applications/rpc/rpc_gui.c b/applications/rpc/rpc_gui.c index 7ee637a0..1e4b0fe2 100644 --- a/applications/rpc/rpc_gui.c +++ b/applications/rpc/rpc_gui.c @@ -6,11 +6,12 @@ #define TAG "RpcGui" typedef struct { - Rpc* rpc; + RpcSession* session; Gui* gui; ViewPort* virtual_display_view_port; uint8_t* virtual_display_buffer; bool virtual_display_not_empty; + bool is_streaming; } RpcGuiSystem; static void @@ -19,7 +20,8 @@ static void furi_assert(size == 1024); furi_assert(context); - RpcGuiSystem* rpc_gui = context; + RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context; + RpcSession* session = rpc_gui->session; PB_Main* frame = malloc(sizeof(PB_Main)); @@ -31,7 +33,7 @@ static void *frame_size_msg = size; memcpy(buffer, data, size); - rpc_send_and_release(rpc_gui->rpc, frame); + rpc_send_and_release(session, frame); free(frame); } @@ -41,20 +43,33 @@ static void rpc_system_gui_start_screen_stream_process(const PB_Main* request, v furi_assert(context); RpcGuiSystem* rpc_gui = context; - rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); + RpcSession* session = rpc_gui->session; + furi_assert(session); - gui_set_framebuffer_callback( - rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); + if(gui_get_framebuffer_callback(rpc_gui->gui) == NULL) { + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); + rpc_gui->is_streaming = true; + gui_set_framebuffer_callback( + rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); + } else { + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_ERROR_BUSY); + } } static void rpc_system_gui_stop_screen_stream_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + furi_assert(session); - gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + if(rpc_gui->is_streaming) { + rpc_gui->is_streaming = false; + gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + } - rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } static void @@ -62,7 +77,10 @@ static void furi_assert(request); furi_assert(request->which_content == PB_Main_gui_send_input_event_request_tag); furi_assert(context); + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + furi_assert(session); InputEvent event; @@ -117,7 +135,7 @@ static void if(invalid) { rpc_send_and_release_empty( - rpc_gui->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } @@ -125,12 +143,13 @@ static void furi_check(input_events); furi_pubsub_publish(input_events, &event); furi_record_close("input_events"); - rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* context) { furi_assert(canvas); furi_assert(context); + RpcGuiSystem* rpc_gui = context; if(!rpc_gui->virtual_display_not_empty) { @@ -146,13 +165,14 @@ static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + furi_assert(session); if(rpc_gui->virtual_display_view_port) { rpc_send_and_release_empty( - rpc_gui->rpc, - request->command_id, - PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED); + session, request->command_id, PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED); return; } @@ -178,17 +198,20 @@ static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, rpc_gui); gui_add_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port, GuiLayerFullscreen); - rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } static void rpc_system_gui_stop_virtual_display_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + furi_assert(session); if(!rpc_gui->virtual_display_view_port) { rpc_send_and_release_empty( - rpc_gui->rpc, request->command_id, PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED); + session, request->command_id, PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED); return; } @@ -198,13 +221,16 @@ static void rpc_system_gui_stop_virtual_display_process(const PB_Main* request, rpc_gui->virtual_display_view_port = NULL; rpc_gui->virtual_display_not_empty = false; - rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + furi_assert(session); if(!rpc_gui->virtual_display_view_port) { FURI_LOG_W(TAG, "Virtual display is not started, ignoring incoming frame packet"); @@ -218,14 +244,16 @@ static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, buffer_size); rpc_gui->virtual_display_not_empty = true; view_port_update(rpc_gui->virtual_display_view_port); + + (void)session; } -void* rpc_system_gui_alloc(Rpc* rpc) { - furi_assert(rpc); +void* rpc_system_gui_alloc(RpcSession* session) { + furi_assert(session); RpcGuiSystem* rpc_gui = malloc(sizeof(RpcGuiSystem)); rpc_gui->gui = furi_record_open("gui"); - rpc_gui->rpc = rpc; + rpc_gui->session = session; RpcHandler rpc_handler = { .message_handler = NULL, @@ -234,29 +262,29 @@ void* rpc_system_gui_alloc(Rpc* rpc) { }; rpc_handler.message_handler = rpc_system_gui_start_screen_stream_process; - rpc_add_handler(rpc, PB_Main_gui_start_screen_stream_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_gui_start_screen_stream_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_gui_stop_screen_stream_process; - rpc_add_handler(rpc, PB_Main_gui_stop_screen_stream_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_gui_stop_screen_stream_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_gui_send_input_event_request_process; - rpc_add_handler(rpc, PB_Main_gui_send_input_event_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_gui_send_input_event_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_gui_start_virtual_display_process; - rpc_add_handler(rpc, PB_Main_gui_start_virtual_display_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_gui_start_virtual_display_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_gui_stop_virtual_display_process; - rpc_add_handler(rpc, PB_Main_gui_stop_virtual_display_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_gui_stop_virtual_display_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_gui_virtual_display_frame_process; - rpc_add_handler(rpc, PB_Main_gui_screen_frame_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_gui_screen_frame_tag, &rpc_handler); return rpc_gui; } -void rpc_system_gui_free(void* ctx) { - furi_assert(ctx); - RpcGuiSystem* rpc_gui = ctx; +void rpc_system_gui_free(void* context) { + furi_assert(context); + RpcGuiSystem* rpc_gui = context; furi_assert(rpc_gui->gui); if(rpc_gui->virtual_display_view_port) { @@ -267,7 +295,9 @@ void rpc_system_gui_free(void* ctx) { rpc_gui->virtual_display_not_empty = false; } - gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + if(rpc_gui->is_streaming) { + gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + } furi_record_close("gui"); free(rpc_gui); } diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index 904129f5..a8ffff28 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -7,8 +7,8 @@ #include #include -typedef void* (*RpcSystemAlloc)(Rpc*); -typedef void (*RpcSystemFree)(void*); +typedef void* (*RpcSystemAlloc)(RpcSession* session); +typedef void (*RpcSystemFree)(void* context); typedef void (*PBMessageHandler)(const PB_Main* msg_request, void* context); typedef struct { @@ -17,18 +17,19 @@ typedef struct { void* context; } RpcHandler; -void rpc_send_and_release(Rpc* rpc, PB_Main* main_message); -void rpc_send_and_release_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status); -void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler); +void rpc_send_and_release(RpcSession* session, PB_Main* main_message); +void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status); -void* rpc_system_system_alloc(Rpc* rpc); -void* rpc_system_storage_alloc(Rpc* rpc); +void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* handler); + +void* rpc_system_system_alloc(RpcSession* session); +void* rpc_system_storage_alloc(RpcSession* session); void rpc_system_storage_free(void* ctx); -void* rpc_system_app_alloc(Rpc* rpc); -void* rpc_system_gui_alloc(Rpc* rpc); +void* rpc_system_app_alloc(RpcSession* session); +void* rpc_system_gui_alloc(RpcSession* session); void rpc_system_gui_free(void* ctx); void rpc_print_message(const PB_Main* message); void rpc_cli_command_start_session(Cli* cli, string_t args, void* context); -PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); \ No newline at end of file +PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); diff --git a/applications/rpc/rpc_storage.c b/applications/rpc/rpc_storage.c index ed43e48d..b70ec4cc 100644 --- a/applications/rpc/rpc_storage.c +++ b/applications/rpc/rpc_storage.c @@ -21,7 +21,7 @@ typedef enum { } RpcStorageState; typedef struct { - Rpc* rpc; + RpcSession* session; Storage* api; File* file; RpcStorageState state; @@ -30,13 +30,16 @@ typedef struct { void rpc_print_message(const PB_Main* message); -static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool send_error) { +static void rpc_system_storage_reset_state( + RpcStorageSystem* rpc_storage, + RpcSession* session, + bool send_error) { furi_assert(rpc_storage); if(rpc_storage->state != RpcStorageStateIdle) { if(send_error) { rpc_send_and_release_empty( - rpc_storage->rpc, + session, rpc_storage->current_command_id, PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED); } @@ -102,7 +105,10 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex furi_assert(request->which_content == PB_Main_storage_info_request_tag); RpcStorageSystem* rpc_storage = context; - rpc_system_storage_reset_state(rpc_storage, true); + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; @@ -122,7 +128,7 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex response->which_content = PB_Main_empty_tag; } - rpc_send_and_release(rpc_storage->rpc, response); + rpc_send_and_release(session, response); free(response); furi_record_close("storage"); } @@ -133,7 +139,10 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex furi_assert(request->which_content == PB_Main_storage_stat_request_tag); RpcStorageSystem* rpc_storage = context; - rpc_system_storage_reset_state(rpc_storage, true); + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; @@ -156,13 +165,16 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex response->content.storage_stat_response.file.size = fileinfo.size; } - rpc_send_and_release(rpc_storage->rpc, response); + rpc_send_and_release(session, response); free(response); furi_record_close("storage"); } static void rpc_system_storage_list_root(const PB_Main* request, void* context) { RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + const char* hard_coded_dirs[] = {"any", "int", "ext"}; PB_Main response = { @@ -183,7 +195,7 @@ static void rpc_system_storage_list_root(const PB_Main* request, void* context) response.content.storage_list_response.file[i].name = str; } - rpc_send_and_release(rpc_storage->rpc, &response); + rpc_send_and_release(session, &response); } static void rpc_system_storage_list_process(const PB_Main* request, void* context) { @@ -192,7 +204,10 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex furi_assert(request->which_content == PB_Main_storage_list_request_tag); RpcStorageSystem* rpc_storage = context; - rpc_system_storage_reset_state(rpc_storage, true); + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); if(!strcmp(request->content.storage_list_request.path, "/")) { rpc_system_storage_list_root(request, context); @@ -226,7 +241,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex if(i == COUNT_OF(list->file)) { list->file_count = i; response.has_next = true; - rpc_send_and_release(rpc_storage->rpc, &response); + rpc_send_and_release(session, &response); i = 0; } list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? PB_Storage_File_FileType_DIR : @@ -243,7 +258,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex } response.has_next = false; - rpc_send_and_release(rpc_storage->rpc, &response); + rpc_send_and_release(session, &response); storage_dir_close(dir); storage_file_free(dir); @@ -253,10 +268,14 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex static void rpc_system_storage_read_process(const PB_Main* request, void* context) { furi_assert(request); + furi_assert(context); furi_assert(request->which_content == PB_Main_storage_read_request_tag); RpcStorageSystem* rpc_storage = context; - rpc_system_storage_reset_state(rpc_storage, true); + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); /* use same message memory to send reponse */ PB_Main* response = malloc(sizeof(PB_Main)); @@ -284,17 +303,17 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex if(result) { response->has_next = (size_left > 0); - rpc_send_and_release(rpc_storage->rpc, response); + rpc_send_and_release(session, response); } } while((size_left != 0) && result); if(!result) { rpc_send_and_release_empty( - rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); + session, request->command_id, rpc_system_storage_get_file_error(file)); } } else { rpc_send_and_release_empty( - rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); + session, request->command_id, rpc_system_storage_get_file_error(file)); } free(response); @@ -306,14 +325,18 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex static void rpc_system_storage_write_process(const PB_Main* request, void* context) { furi_assert(request); + furi_assert(context); furi_assert(request->which_content == PB_Main_storage_write_request_tag); RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + bool result = true; if((request->command_id != rpc_storage->current_command_id) && (rpc_storage->state == RpcStorageStateWriting)) { - rpc_system_storage_reset_state(rpc_storage, true); + rpc_system_storage_reset_state(rpc_storage, session, true); } if(rpc_storage->state != RpcStorageStateWriting) { @@ -336,17 +359,15 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte if(result && !request->has_next) { rpc_send_and_release_empty( - rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_OK); - rpc_system_storage_reset_state(rpc_storage, false); + session, rpc_storage->current_command_id, PB_CommandStatus_OK); + rpc_system_storage_reset_state(rpc_storage, session, false); } } if(!result) { rpc_send_and_release_empty( - rpc_storage->rpc, - rpc_storage->current_command_id, - rpc_system_storage_get_file_error(file)); - rpc_system_storage_reset_state(rpc_storage, false); + session, rpc_storage->current_command_id, rpc_system_storage_get_file_error(file)); + rpc_system_storage_reset_state(rpc_storage, session, false); } } @@ -373,8 +394,11 @@ static void rpc_system_storage_delete_process(const PB_Main* request, void* cont furi_assert(request->which_content == PB_Main_storage_delete_request_tag); furi_assert(context); RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + PB_CommandStatus status = PB_CommandStatus_ERROR; - rpc_system_storage_reset_state(rpc_storage, true); + rpc_system_storage_reset_state(rpc_storage, session, true); Storage* fs_api = furi_record_open("storage"); @@ -400,7 +424,7 @@ static void rpc_system_storage_delete_process(const PB_Main* request, void* cont } furi_record_close("storage"); - rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); + rpc_send_and_release_empty(session, request->command_id, status); } static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) { @@ -408,8 +432,11 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte furi_assert(request->which_content == PB_Main_storage_mkdir_request_tag); furi_assert(context); RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + PB_CommandStatus status; - rpc_system_storage_reset_state(rpc_storage, true); + rpc_system_storage_reset_state(rpc_storage, session, true); Storage* fs_api = furi_record_open("storage"); char* path = request->content.storage_mkdir_request.path; @@ -420,7 +447,7 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; } furi_record_close("storage"); - rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); + rpc_send_and_release_empty(session, request->command_id, status); } static void rpc_system_storage_md5sum_process(const PB_Main* request, void* context) { @@ -428,12 +455,15 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont furi_assert(request->which_content == PB_Main_storage_md5sum_request_tag); furi_assert(context); RpcStorageSystem* rpc_storage = context; - rpc_system_storage_reset_state(rpc_storage, true); + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); const char* filename = request->content.storage_md5sum_request.path; if(!filename) { rpc_send_and_release_empty( - rpc_storage->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } @@ -474,10 +504,10 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont free(hash); free(data); storage_file_close(file); - rpc_send_and_release(rpc_storage->rpc, &response); + rpc_send_and_release(session, &response); } else { rpc_send_and_release_empty( - rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); + session, request->command_id, rpc_system_storage_get_file_error(file)); } storage_file_free(file); @@ -490,8 +520,11 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont furi_assert(request->which_content == PB_Main_storage_rename_request_tag); furi_assert(context); RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + PB_CommandStatus status; - rpc_system_storage_reset_state(rpc_storage, true); + rpc_system_storage_reset_state(rpc_storage, session, true); Storage* fs_api = furi_record_open("storage"); @@ -502,15 +535,15 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont status = rpc_system_storage_get_error(error); furi_record_close("storage"); - rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); + rpc_send_and_release_empty(session, request->command_id, status); } -void* rpc_system_storage_alloc(Rpc* rpc) { - furi_assert(rpc); +void* rpc_system_storage_alloc(RpcSession* session) { + furi_assert(session); RpcStorageSystem* rpc_storage = malloc(sizeof(RpcStorageSystem)); rpc_storage->api = furi_record_open("storage"); - rpc_storage->rpc = rpc; + rpc_storage->session = session; rpc_storage->state = RpcStorageStateIdle; RpcHandler rpc_handler = { @@ -520,37 +553,40 @@ void* rpc_system_storage_alloc(Rpc* rpc) { }; rpc_handler.message_handler = rpc_system_storage_info_process; - rpc_add_handler(rpc, PB_Main_storage_info_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_info_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_stat_process; - rpc_add_handler(rpc, PB_Main_storage_stat_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_stat_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_list_process; - rpc_add_handler(rpc, PB_Main_storage_list_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_list_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_read_process; - rpc_add_handler(rpc, PB_Main_storage_read_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_read_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_write_process; - rpc_add_handler(rpc, PB_Main_storage_write_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_write_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_delete_process; - rpc_add_handler(rpc, PB_Main_storage_delete_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_delete_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_mkdir_process; - rpc_add_handler(rpc, PB_Main_storage_mkdir_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_mkdir_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_md5sum_process; - rpc_add_handler(rpc, PB_Main_storage_md5sum_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_md5sum_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_storage_rename_process; - rpc_add_handler(rpc, PB_Main_storage_rename_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_storage_rename_request_tag, &rpc_handler); return rpc_storage; } -void rpc_system_storage_free(void* ctx) { - RpcStorageSystem* rpc_storage = ctx; - rpc_system_storage_reset_state(rpc_storage, false); +void rpc_system_storage_free(void* context) { + RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, false); free(rpc_storage); } diff --git a/applications/rpc/rpc_system.c b/applications/rpc/rpc_system.c index e718ad8a..9884e30a 100644 --- a/applications/rpc/rpc_system.c +++ b/applications/rpc/rpc_system.c @@ -6,40 +6,42 @@ #include "rpc_i.h" -static void rpc_system_system_ping_process(const PB_Main* msg_request, void* context) { - furi_assert(msg_request); - furi_assert(msg_request->which_content == PB_Main_system_ping_request_tag); - furi_assert(context); - Rpc* rpc = context; +static void rpc_system_system_ping_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_system_ping_request_tag); - if(msg_request->has_next) { + RpcSession* session = (RpcSession*)context; + furi_assert(session); + + if(request->has_next) { rpc_send_and_release_empty( - rpc, msg_request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } - PB_Main msg_response = PB_Main_init_default; - msg_response.has_next = false; - msg_response.command_status = PB_CommandStatus_OK; - msg_response.command_id = msg_request->command_id; - msg_response.which_content = PB_Main_system_ping_response_tag; + PB_Main response = PB_Main_init_default; + response.has_next = false; + response.command_status = PB_CommandStatus_OK; + response.command_id = request->command_id; + response.which_content = PB_Main_system_ping_response_tag; - const PB_System_PingRequest* request = &msg_request->content.system_ping_request; - PB_System_PingResponse* response = &msg_response.content.system_ping_response; - if(request->data && (request->data->size > 0)) { - response->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(request->data->size)); - memcpy(response->data->bytes, request->data->bytes, request->data->size); - response->data->size = request->data->size; + const PB_System_PingRequest* ping_request = &request->content.system_ping_request; + PB_System_PingResponse* ping_response = &response.content.system_ping_response; + if(ping_request->data && (ping_request->data->size > 0)) { + ping_response->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(ping_request->data->size)); + memcpy(ping_response->data->bytes, ping_request->data->bytes, ping_request->data->size); + ping_response->data->size = ping_request->data->size; } - rpc_send_and_release(rpc, &msg_response); + rpc_send_and_release(session, &response); } static void rpc_system_system_reboot_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_reboot_request_tag); - furi_assert(context); - Rpc* rpc = context; + + RpcSession* session = (RpcSession*)context; + furi_assert(session); const int mode = request->content.system_reboot_request.mode; @@ -49,12 +51,12 @@ static void rpc_system_system_reboot_process(const PB_Main* request, void* conte power_reboot(PowerBootModeDfu); } else { rpc_send_and_release_empty( - rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); } } typedef struct { - Rpc* rpc; + RpcSession* session; PB_Main* response; } RpcSystemSystemDeviceInfoContext; @@ -65,7 +67,6 @@ static void rpc_system_system_device_info_callback( void* context) { furi_assert(key); furi_assert(value); - furi_assert(context); RpcSystemSystemDeviceInfoContext* ctx = context; char* str_key = strdup(key); @@ -75,14 +76,15 @@ static void rpc_system_system_device_info_callback( ctx->response->content.system_device_info_response.key = str_key; ctx->response->content.system_device_info_response.value = str_value; - rpc_send_and_release(ctx->rpc, ctx->response); + rpc_send_and_release(ctx->session, ctx->response); } static void rpc_system_system_device_info_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_device_info_request_tag); - furi_assert(context); - Rpc* rpc = context; + + RpcSession* session = (RpcSession*)context; + furi_assert(session); PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; @@ -90,10 +92,9 @@ static void rpc_system_system_device_info_process(const PB_Main* request, void* response->command_status = PB_CommandStatus_OK; RpcSystemSystemDeviceInfoContext device_info_context = { - .rpc = rpc, + .session = session, .response = response, }; - furi_hal_info_get(rpc_system_system_device_info_callback, &device_info_context); free(response); @@ -102,8 +103,9 @@ static void rpc_system_system_device_info_process(const PB_Main* request, void* static void rpc_system_system_get_datetime_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_get_datetime_request_tag); - furi_assert(context); - Rpc* rpc = context; + + RpcSession* session = (RpcSession*)context; + furi_assert(session); FuriHalRtcDateTime datetime; furi_hal_rtc_get_datetime(&datetime); @@ -121,19 +123,20 @@ static void rpc_system_system_get_datetime_process(const PB_Main* request, void* response->content.system_get_datetime_response.datetime.year = datetime.year; response->content.system_get_datetime_response.datetime.weekday = datetime.weekday; - rpc_send_and_release(rpc, response); + rpc_send_and_release(session, response); free(response); } static void rpc_system_system_set_datetime_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_set_datetime_request_tag); - furi_assert(context); - Rpc* rpc = context; + + RpcSession* session = (RpcSession*)context; + furi_assert(session); if(!request->content.system_set_datetime_request.has_datetime) { rpc_send_and_release_empty( - rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } @@ -147,38 +150,43 @@ static void rpc_system_system_set_datetime_process(const PB_Main* request, void* datetime.weekday = request->content.system_set_datetime_request.datetime.weekday; furi_hal_rtc_set_datetime(&datetime); - rpc_send_and_release_empty(rpc, request->command_id, PB_CommandStatus_OK); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } static void rpc_system_system_factory_reset_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_factory_reset_request_tag); - furi_assert(context); + + RpcSession* session = (RpcSession*)context; + furi_assert(session); furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset); power_reboot(PowerBootModeNormal); + + (void)session; } static void rpc_system_system_play_audiovisual_alert_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_play_audiovisual_alert_request_tag); - furi_assert(context); - Rpc* rpc = context; + + RpcSession* session = (RpcSession*)context; + furi_assert(session); NotificationApp* notification = furi_record_open("notification"); notification_message(notification, &sequence_audiovisual_alert); furi_record_close("notification"); - rpc_send_and_release_empty(rpc, request->command_id, PB_CommandStatus_OK); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } static void rpc_system_system_protobuf_version_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_protobuf_version_request_tag); - furi_assert(context); - Rpc* rpc = context; + RpcSession* session = (RpcSession*)context; + furi_assert(session); PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; @@ -190,40 +198,40 @@ static void rpc_system_system_protobuf_version_process(const PB_Main* request, v response->content.system_protobuf_version_response.major = PROTOBUF_MAJOR_VERSION; response->content.system_protobuf_version_response.minor = PROTOBUF_MINOR_VERSION; - rpc_send_and_release(rpc, response); + rpc_send_and_release(session, response); free(response); } -void* rpc_system_system_alloc(Rpc* rpc) { +void* rpc_system_system_alloc(RpcSession* session) { RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, - .context = rpc, + .context = session, }; rpc_handler.message_handler = rpc_system_system_ping_process; - rpc_add_handler(rpc, PB_Main_system_ping_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_ping_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_reboot_process; - rpc_add_handler(rpc, PB_Main_system_reboot_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_reboot_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_device_info_process; - rpc_add_handler(rpc, PB_Main_system_device_info_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_device_info_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_factory_reset_process; - rpc_add_handler(rpc, PB_Main_system_factory_reset_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_factory_reset_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_get_datetime_process; - rpc_add_handler(rpc, PB_Main_system_get_datetime_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_get_datetime_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_set_datetime_process; - rpc_add_handler(rpc, PB_Main_system_set_datetime_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_set_datetime_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_play_audiovisual_alert_process; - rpc_add_handler(rpc, PB_Main_system_play_audiovisual_alert_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_play_audiovisual_alert_request_tag, &rpc_handler); rpc_handler.message_handler = rpc_system_system_protobuf_version_process; - rpc_add_handler(rpc, PB_Main_system_protobuf_version_request_tag, &rpc_handler); + rpc_add_handler(session, PB_Main_system_protobuf_version_request_tag, &rpc_handler); return NULL; } diff --git a/applications/tests/rpc/rpc_test.c b/applications/tests/rpc/rpc_test.c index e80d36cb..cdb63053 100644 --- a/applications/tests/rpc/rpc_test.c +++ b/applications/tests/rpc/rpc_test.c @@ -24,21 +24,23 @@ LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) #define M_OPL_MsgList_t() LIST_OPLIST(MsgList) +#define TEST_RPC_SESSIONS 2 + /* MinUnit test framework doesn't allow passing context into tests, * so we have to use global variables */ static Rpc* rpc = NULL; -static RpcSession* session = NULL; static uint32_t command_id = 0; typedef struct { + RpcSession* session; StreamBufferHandle_t output_stream; SemaphoreHandle_t close_session_semaphore; SemaphoreHandle_t terminate_semaphore; TickType_t timeout; } RpcSessionContext; -static RpcSessionContext rpc_session_context; +static RpcSessionContext rpc_session[TEST_RPC_SESSIONS]; #define TAG "UnitTestsRpc" #define MAX_RECEIVE_OUTPUT_TIMEOUT 3000 @@ -69,48 +71,83 @@ static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size static void clean_directory(Storage* fs_api, const char* clean_dir); static void test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id); -static void test_rpc_encode_and_feed(MsgList_t msg_list); -static void test_rpc_encode_and_feed_one(PB_Main* request); +static void test_rpc_encode_and_feed(MsgList_t msg_list, uint8_t session); +static void test_rpc_encode_and_feed_one(PB_Main* request, uint8_t session); static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected); -static void test_rpc_decode_and_compare(MsgList_t expected_msg_list); +static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t session); static void test_rpc_free_msg_list(MsgList_t msg_list); static void test_rpc_session_close_callback(void* context); static void test_rpc_session_terminated_callback(void* context); static void test_rpc_setup(void) { furi_check(!rpc); - furi_check(!session); + furi_check(!(rpc_session[0].session)); rpc = furi_record_open("rpc"); - for(int i = 0; !session && (i < 10000); ++i) { - session = rpc_session_open(rpc); + for(int i = 0; !(rpc_session[0].session) && (i < 10000); ++i) { + rpc_session[0].session = rpc_session_open(rpc); delay(1); } - furi_check(session); + furi_check(rpc_session[0].session); - rpc_session_context.output_stream = xStreamBufferCreate(1000, 1); - rpc_session_set_send_bytes_callback(session, output_bytes_callback); - rpc_session_context.close_session_semaphore = xSemaphoreCreateBinary(); - rpc_session_context.terminate_semaphore = xSemaphoreCreateBinary(); - rpc_session_set_close_callback(session, test_rpc_session_close_callback); - rpc_session_set_terminated_callback(session, test_rpc_session_terminated_callback); - rpc_session_set_context(session, &rpc_session_context); + rpc_session[0].output_stream = xStreamBufferCreate(1000, 1); + rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); + rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); + rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); + rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback); + rpc_session_set_terminated_callback( + rpc_session[0].session, test_rpc_session_terminated_callback); + rpc_session_set_context(rpc_session[0].session, &rpc_session[0]); +} + +static void test_rpc_setup_second_session(void) { + furi_check(rpc); + furi_check(!(rpc_session[1].session)); + + for(int i = 0; !(rpc_session[1].session) && (i < 10000); ++i) { + rpc_session[1].session = rpc_session_open(rpc); + delay(1); + } + furi_check(rpc_session[1].session); + + rpc_session[1].output_stream = xStreamBufferCreate(1000, 1); + rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback); + rpc_session[1].close_session_semaphore = xSemaphoreCreateBinary(); + rpc_session[1].terminate_semaphore = xSemaphoreCreateBinary(); + rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback); + rpc_session_set_terminated_callback( + rpc_session[1].session, test_rpc_session_terminated_callback); + rpc_session_set_context(rpc_session[1].session, &rpc_session[1]); } static void test_rpc_teardown(void) { - furi_check(rpc_session_context.close_session_semaphore); - xSemaphoreTake(rpc_session_context.terminate_semaphore, 0); - rpc_session_close(session); - furi_check(xSemaphoreTake(rpc_session_context.terminate_semaphore, portMAX_DELAY)); + furi_check(rpc_session[0].close_session_semaphore); + xSemaphoreTake(rpc_session[0].terminate_semaphore, 0); + rpc_session_close(rpc_session[0].session); + furi_check(xSemaphoreTake(rpc_session[0].terminate_semaphore, portMAX_DELAY)); furi_record_close("rpc"); - vStreamBufferDelete(rpc_session_context.output_stream); - vSemaphoreDelete(rpc_session_context.close_session_semaphore); - vSemaphoreDelete(rpc_session_context.terminate_semaphore); + vStreamBufferDelete(rpc_session[0].output_stream); + vSemaphoreDelete(rpc_session[0].close_session_semaphore); + vSemaphoreDelete(rpc_session[0].terminate_semaphore); ++command_id; - rpc_session_context.output_stream = NULL; - rpc_session_context.close_session_semaphore = NULL; + rpc_session[0].output_stream = NULL; + rpc_session[0].close_session_semaphore = NULL; rpc = NULL; - session = NULL; + rpc_session[0].session = NULL; +} + +static void test_rpc_teardown_second_session(void) { + furi_check(rpc_session[1].close_session_semaphore); + xSemaphoreTake(rpc_session[1].terminate_semaphore, 0); + rpc_session_close(rpc_session[1].session); + furi_check(xSemaphoreTake(rpc_session[1].terminate_semaphore, portMAX_DELAY)); + vStreamBufferDelete(rpc_session[1].output_stream); + vSemaphoreDelete(rpc_session[1].close_session_semaphore); + vSemaphoreDelete(rpc_session[1].terminate_semaphore); + ++command_id; + rpc_session[1].output_stream = NULL; + rpc_session[1].close_session_semaphore = NULL; + rpc_session[1].session = NULL; } static void test_rpc_storage_setup(void) { @@ -334,8 +371,9 @@ static void test_rpc_add_read_or_write_to_list( } while(pattern_repeats); } -static void test_rpc_encode_and_feed_one(PB_Main* request) { +static void test_rpc_encode_and_feed_one(PB_Main* request, uint8_t session) { furi_check(request); + furi_check(session < TEST_RPC_SESSIONS); pb_ostream_t ostream = PB_OSTREAM_SIZING; @@ -350,7 +388,8 @@ static void test_rpc_encode_and_feed_one(PB_Main* request) { size_t bytes_left = ostream.bytes_written; uint8_t* buffer_ptr = buffer; do { - size_t bytes_sent = rpc_session_feed(session, buffer_ptr, bytes_left, 1000); + size_t bytes_sent = + rpc_session_feed(rpc_session[session].session, buffer_ptr, bytes_left, 1000); mu_check(bytes_sent > 0); bytes_left -= bytes_sent; @@ -361,11 +400,11 @@ static void test_rpc_encode_and_feed_one(PB_Main* request) { pb_release(&PB_Main_msg, request); } -static void test_rpc_encode_and_feed(MsgList_t msg_list) { +static void test_rpc_encode_and_feed(MsgList_t msg_list, uint8_t session) { MsgList_reverse(msg_list); for M_EACH(request, msg_list, MsgList_t) { - test_rpc_encode_and_feed_one(request); + test_rpc_encode_and_feed_one(request, session); } MsgList_reverse(msg_list); } @@ -585,13 +624,14 @@ static void test_rpc_storage_list_create_expected_list( furi_record_close("storage"); } -static void test_rpc_decode_and_compare(MsgList_t expected_msg_list) { +static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t session) { furi_check(!MsgList_empty_p(expected_msg_list)); + furi_check(session < TEST_RPC_SESSIONS); - rpc_session_context.timeout = xTaskGetTickCount() + MAX_RECEIVE_OUTPUT_TIMEOUT; + rpc_session[session].timeout = xTaskGetTickCount() + MAX_RECEIVE_OUTPUT_TIMEOUT; pb_istream_t istream = { .callback = test_rpc_pb_stream_read, - .state = &rpc_session_context, + .state = &rpc_session[session], .errmsg = NULL, .bytes_left = 0x7FFFFFFF, }; @@ -612,7 +652,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list) { pb_release(&PB_Main_msg, &result); } - rpc_session_context.timeout = xTaskGetTickCount() + 50; + rpc_session[session].timeout = xTaskGetTickCount() + 50; if(pb_decode_ex(&istream, &PB_Main_msg, &result, PB_DECODE_DELIMITED)) { mu_fail("decoded more than expected"); } @@ -638,8 +678,8 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { } else { test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id); } - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -723,8 +763,8 @@ static void test_storage_read_run(const char* path, uint32_t command_id) { test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id); test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id); - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -796,8 +836,8 @@ static void test_rpc_storage_info_run(const char* path, uint32_t command_id) { response->which_content = PB_Main_empty_tag; } - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -830,8 +870,8 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) { response->content.storage_stat_response.file.size = fileinfo.size; } - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -895,8 +935,8 @@ static void test_storage_write_run( test_rpc_add_read_or_write_to_list( input_msg_list, WRITE_REQUEST, path, buf, write_size, write_count, command_id); test_rpc_add_empty_to_list(expected_msg_list, status, command_id); - test_rpc_encode_and_feed(input_msg_list); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed(input_msg_list, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); test_rpc_free_msg_list(input_msg_list); test_rpc_free_msg_list(expected_msg_list); @@ -933,8 +973,8 @@ static void test_storage_write_read_run( test_rpc_print_message_list(input_msg_list); test_rpc_print_message_list(expected_msg_list); - test_rpc_encode_and_feed(input_msg_list); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed(input_msg_list, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); test_rpc_free_msg_list(input_msg_list); test_rpc_free_msg_list(expected_msg_list); @@ -1007,8 +1047,8 @@ MU_TEST(test_storage_interrupt_continuous_same_system) { expected_msg_list, PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED, command_id); test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, command_id + 1); - test_rpc_encode_and_feed(input_msg_list); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed(input_msg_list, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); test_rpc_free_msg_list(input_msg_list); test_rpc_free_msg_list(expected_msg_list); @@ -1057,8 +1097,8 @@ MU_TEST(test_storage_interrupt_continuous_another_system) { test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, command_id); test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, command_id + 2); - test_rpc_encode_and_feed(input_msg_list); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed(input_msg_list, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); test_rpc_free_msg_list(input_msg_list); test_rpc_free_msg_list(expected_msg_list); @@ -1077,8 +1117,8 @@ static void test_storage_delete_run( request.content.storage_delete_request.recursive = recursive; test_rpc_add_empty_to_list(expected_msg_list, status, command_id); - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -1157,8 +1197,8 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id); test_rpc_add_empty_to_list(expected_msg_list, status, command_id); - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -1233,8 +1273,8 @@ static void test_storage_md5sum_run( test_rpc_add_empty_to_list(expected_msg_list, status, command_id); } - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -1292,8 +1332,8 @@ static void test_rpc_storage_rename_run( test_rpc_add_empty_to_list(expected_msg_list, status, command_id); - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -1340,8 +1380,8 @@ MU_TEST(test_ping) { test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 700); test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 1); - test_rpc_encode_and_feed(input_msg_list); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed(input_msg_list, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); test_rpc_free_msg_list(input_msg_list); test_rpc_free_msg_list(expected_msg_list); @@ -1367,8 +1407,8 @@ MU_TEST(test_system_protobuf_version) { response->content.system_protobuf_version_response.major = PROTOBUF_MAJOR_VERSION; response->content.system_protobuf_version_response.minor = PROTOBUF_MINOR_VERSION; - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); test_rpc_free_msg_list(expected_msg_list); } @@ -1394,7 +1434,7 @@ MU_TEST_SUITE(test_rpc_storage) { MU_RUN_TEST(test_storage_mkdir); MU_RUN_TEST(test_storage_md5sum); MU_RUN_TEST(test_storage_rename); - // TODO: repair test + DISABLE_TEST(MU_RUN_TEST(test_storage_interrupt_continuous_same_system);); MU_RUN_TEST(test_storage_interrupt_continuous_another_system); } @@ -1437,8 +1477,8 @@ static void test_app_start_run( test_app_create_request(&request, app_name, app_args, command_id); test_rpc_add_empty_to_list(expected_msg_list, status, command_id); - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -1461,8 +1501,8 @@ static void test_app_get_status_lock_run(bool locked_expected, uint32_t command_ response->has_next = false; response->content.app_lock_status_response.locked = locked_expected; - test_rpc_encode_and_feed_one(&request); - test_rpc_decode_and_compare(expected_msg_list); + test_rpc_encode_and_feed_one(&request, 0); + test_rpc_decode_and_compare(expected_msg_list, 0); pb_release(&PB_Main_msg, &request); test_rpc_free_msg_list(expected_msg_list); @@ -1516,7 +1556,7 @@ static void buf[i] = pattern[i % pattern_size]; } - size_t bytes_sent = rpc_session_feed(session, buf, size, 1000); + size_t bytes_sent = rpc_session_feed(rpc_session[0].session, buf, size, 1000); furi_check(bytes_sent == size); free(buf); } @@ -1532,12 +1572,12 @@ static void test_rpc_feed_rubbish_run( test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0); - furi_check(!xSemaphoreTake(rpc_session_context.close_session_semaphore, 0)); - test_rpc_encode_and_feed(input_before); - test_send_rubbish(session, pattern, pattern_size, size); - test_rpc_encode_and_feed(input_after); + furi_check(!xSemaphoreTake(rpc_session[0].close_session_semaphore, 0)); + test_rpc_encode_and_feed(input_before, 0); + test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size); + test_rpc_encode_and_feed(input_after, 0); - test_rpc_decode_and_compare(expected); + test_rpc_decode_and_compare(expected, 0); test_rpc_teardown(); } @@ -1619,8 +1659,112 @@ MU_TEST(test_rpc_feed_rubbish) { FREE_LISTS(); } +MU_TEST(test_rpc_multisession_ping) { + MsgList_t input_0; + MsgList_init(input_0); + MsgList_t input_1; + MsgList_init(input_1); + MsgList_t expected_0; + MsgList_init(expected_0); + MsgList_t expected_1; + MsgList_init(expected_1); + + test_rpc_setup(); + + test_rpc_setup_second_session(); + test_rpc_teardown_second_session(); + + test_rpc_setup_second_session(); + + test_rpc_add_ping_to_list(input_0, PING_REQUEST, 0); + test_rpc_add_ping_to_list(input_1, PING_REQUEST, 1); + test_rpc_add_ping_to_list(expected_0, PING_RESPONSE, 0); + test_rpc_add_ping_to_list(expected_1, PING_RESPONSE, 1); + + test_rpc_encode_and_feed(input_0, 0); + test_rpc_encode_and_feed(input_1, 1); + test_rpc_decode_and_compare(expected_0, 0); + test_rpc_decode_and_compare(expected_1, 1); + + test_rpc_free_msg_list(input_0); + test_rpc_free_msg_list(input_1); + test_rpc_free_msg_list(expected_0); + test_rpc_free_msg_list(expected_1); + + test_rpc_teardown_second_session(); + test_rpc_teardown(); +} + +MU_TEST(test_rpc_multisession_storage) { + MsgList_t input_0; + MsgList_init(input_0); + MsgList_t input_1; + MsgList_init(input_1); + MsgList_t expected_0; + MsgList_init(expected_0); + MsgList_t expected_1; + MsgList_init(expected_1); + + test_rpc_storage_setup(); + test_rpc_setup_second_session(); + + uint8_t pattern[16] = "0123456789abcdef"; + + test_rpc_add_read_or_write_to_list( + input_0, WRITE_REQUEST, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, ++command_id); + test_rpc_add_empty_to_list(expected_0, PB_CommandStatus_OK, command_id); + + test_rpc_add_read_or_write_to_list( + input_1, WRITE_REQUEST, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, ++command_id); + test_rpc_add_empty_to_list(expected_1, PB_CommandStatus_OK, command_id); + + test_rpc_create_simple_message( + MsgList_push_raw(input_0), + PB_Main_storage_read_request_tag, + TEST_DIR "file0.txt", + ++command_id); + test_rpc_add_read_or_write_to_list( + expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id); + + test_rpc_create_simple_message( + MsgList_push_raw(input_1), + PB_Main_storage_read_request_tag, + TEST_DIR "file1.txt", + ++command_id); + test_rpc_add_read_or_write_to_list( + expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id); + + test_rpc_print_message_list(input_0); + test_rpc_print_message_list(input_1); + test_rpc_print_message_list(expected_0); + test_rpc_print_message_list(expected_1); + + test_rpc_encode_and_feed(input_0, 0); + test_rpc_encode_and_feed(input_1, 1); + + test_rpc_decode_and_compare(expected_0, 0); + test_rpc_decode_and_compare(expected_1, 1); + + test_rpc_free_msg_list(input_0); + test_rpc_free_msg_list(input_1); + test_rpc_free_msg_list(expected_0); + test_rpc_free_msg_list(expected_1); + + test_rpc_teardown_second_session(); + test_rpc_storage_teardown(); +} + MU_TEST_SUITE(test_rpc_session) { MU_RUN_TEST(test_rpc_feed_rubbish); + MU_RUN_TEST(test_rpc_multisession_ping); + + Storage* storage = furi_record_open("storage"); + if(storage_sd_status(storage) != FSE_OK) { + FURI_LOG_E(TAG, "SD card not mounted - skip storage tests"); + } else { + MU_RUN_TEST(test_rpc_multisession_storage); + } + furi_record_close("storage"); } int run_minunit_test_rpc() { @@ -1631,7 +1775,6 @@ int run_minunit_test_rpc() { MU_RUN_SUITE(test_rpc_storage); } furi_record_close("storage"); - MU_RUN_SUITE(test_rpc_system); MU_RUN_SUITE(test_rpc_app); MU_RUN_SUITE(test_rpc_session); From 28888b0a2206596de0b13397fd24baada9e40f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 16 Mar 2022 15:57:13 +0700 Subject: [PATCH 09/13] Nfc: add field testing (#1027) * Nfc: add field cli command * Nfc: add field test in debug menu * Nfc: warning message for field tests * Format sources --- applications/nfc/nfc_cli.c | 31 +++++++++++ applications/nfc/scenes/nfc_scene_config.h | 2 + applications/nfc/scenes/nfc_scene_debug.c | 54 +++++++++++++++++++ .../scenes/nfc_scene_emulate_apdu_sequence.c | 4 +- applications/nfc/scenes/nfc_scene_field.c | 34 ++++++++++++ applications/nfc/scenes/nfc_scene_start.c | 2 +- 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 applications/nfc/scenes/nfc_scene_debug.c create mode 100644 applications/nfc/scenes/nfc_scene_field.c diff --git a/applications/nfc/nfc_cli.c b/applications/nfc/nfc_cli.c index a2f816e4..0dd212a6 100755 --- a/applications/nfc/nfc_cli.c +++ b/applications/nfc/nfc_cli.c @@ -11,6 +11,9 @@ static void nfc_cli_print_usage() { printf("Cmd list:\r\n"); printf("\tdetect\t - detect nfc device\r\n"); printf("\temulate\t - emulate predefined nfca card\r\n"); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + printf("\tfield\t - turn field on\r\n"); + } } void nfc_cli_detect(Cli* cli, string_t args) { @@ -76,6 +79,27 @@ void nfc_cli_emulate(Cli* cli, string_t args) { furi_hal_nfc_deactivate(); } +void nfc_cli_field(Cli* cli, string_t args) { + // Check if nfc worker is not busy + if(furi_hal_nfc_is_busy()) { + printf("Nfc is busy\r\n"); + return; + } + + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_field_on(); + + printf("Field is on. Don't leave device in this mode for too long.\r\n"); + printf("Press Ctrl+C to abort\r\n"); + + while(!cli_cmd_interrupt_received(cli)) { + osDelay(50); + } + + furi_hal_nfc_field_off(); + furi_hal_nfc_deactivate(); +} + static void nfc_cli(Cli* cli, string_t args, void* context) { string_t cmd; string_init(cmd); @@ -94,6 +118,13 @@ static void nfc_cli(Cli* cli, string_t args, void* context) { break; } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + if(string_cmp_str(cmd, "field") == 0) { + nfc_cli_field(cli, args); + break; + } + } + nfc_cli_print_usage(); } while(false); diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index 97072ead..f4ccdf0c 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -27,3 +27,5 @@ ADD_SCENE(nfc, read_emv_data, ReadEmvData) ADD_SCENE(nfc, read_emv_data_success, ReadEmvDataSuccess) ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) ADD_SCENE(nfc, restore_original, RestoreOriginal) +ADD_SCENE(nfc, debug, Debug) +ADD_SCENE(nfc, field, Field) diff --git a/applications/nfc/scenes/nfc_scene_debug.c b/applications/nfc/scenes/nfc_scene_debug.c new file mode 100644 index 00000000..a8f1e686 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_debug.c @@ -0,0 +1,54 @@ +#include "../nfc_i.h" + +enum SubmenuDebugIndex { + SubmenuDebugIndexField, + SubmenuDebugIndexApdu, +}; + +void nfc_scene_debug_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = (Nfc*)context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_debug_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, "Field", SubmenuDebugIndexField, nfc_scene_debug_submenu_callback, nfc); + submenu_add_item( + submenu, "Apdu", SubmenuDebugIndexApdu, nfc_scene_debug_submenu_callback, nfc); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDebug)); + + nfc_device_clear(nfc->dev); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = (Nfc*)context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuDebugIndexField) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexField); + scene_manager_next_scene(nfc->scene_manager, NfcSceneField); + consumed = true; + } else if(event.event == SubmenuDebugIndexApdu) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexApdu); + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateApduSequence); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_debug_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c index c9e822e2..1a87ea58 100644 --- a/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ b/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c @@ -32,7 +32,5 @@ void nfc_scene_emulate_apdu_sequence_on_exit(void* context) { // Clear view Popup* popup = nfc->popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); + popup_reset(popup); } diff --git a/applications/nfc/scenes/nfc_scene_field.c b/applications/nfc/scenes/nfc_scene_field.c new file mode 100644 index 00000000..36670387 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_field.c @@ -0,0 +1,34 @@ +#include "../nfc_i.h" + +void nfc_scene_field_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + + furi_hal_nfc_field_on(); + + Popup* popup = nfc->popup; + popup_set_header( + popup, + "Field is on\nDon't leave device\nin this mode for too long.", + 64, + 11, + AlignCenter, + AlignTop); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + + notification_internal_message(nfc->notifications, &sequence_set_blue_255); +} + +bool nfc_scene_field_on_event(void* context, SceneManagerEvent event) { + return false; +} + +void nfc_scene_field_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + notification_internal_message(nfc->notifications, &sequence_reset_blue); + + Popup* popup = nfc->popup; + popup_reset(popup); + + furi_hal_nfc_field_off(); +} diff --git a/applications/nfc/scenes/nfc_scene_start.c b/applications/nfc/scenes/nfc_scene_start.c index 40f3a12e..4c4f1e64 100644 --- a/applications/nfc/scenes/nfc_scene_start.c +++ b/applications/nfc/scenes/nfc_scene_start.c @@ -68,7 +68,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == SubmenuIndexDebug) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateApduSequence); + scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); consumed = true; } } From 94ba7d104cb4037d79cb26b26ea6354f2ef01ba5 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:18:48 +0400 Subject: [PATCH 10/13] SubGhz: refactoring add descriptions (#1012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add descriptions * SubGhz: fix syntax Co-authored-by: あく --- applications/subghz/subghz.c | 2 +- applications/subghz/subghz_cli.c | 2 +- lib/subghz/blocks/decoder.h | 11 +++ lib/subghz/blocks/generic.h | 20 +++++ lib/subghz/blocks/math.h | 6 ++ lib/subghz/environment.h | 38 ++++++++ lib/subghz/protocols/base.h | 30 +++++++ lib/subghz/protocols/came.c | 5 ++ lib/subghz/protocols/came.h | 78 ++++++++++++---- lib/subghz/protocols/came_atomo.c | 15 ++-- lib/subghz/protocols/came_atomo.h | 50 +++++++++++ lib/subghz/protocols/came_twee.c | 13 ++- lib/subghz/protocols/came_twee.h | 79 +++++++++++++++++ lib/subghz/protocols/faac_slh.c | 4 + lib/subghz/protocols/faac_slh.h | 50 +++++++++++ lib/subghz/protocols/gate_tx.c | 9 ++ lib/subghz/protocols/gate_tx.h | 79 +++++++++++++++++ lib/subghz/protocols/hormann.c | 9 ++ lib/subghz/protocols/hormann.h | 79 +++++++++++++++++ lib/subghz/protocols/ido.c | 4 + lib/subghz/protocols/ido.h | 50 +++++++++++ lib/subghz/protocols/keeloq.c | 44 ++++++--- lib/subghz/protocols/keeloq.h | 92 +++++++++++++++++++ lib/subghz/protocols/keeloq_common.h | 18 ++-- lib/subghz/protocols/kia.c | 6 +- lib/subghz/protocols/kia.h | 50 +++++++++++ lib/subghz/protocols/nero_radio.c | 5 ++ lib/subghz/protocols/nero_radio.h | 79 +++++++++++++++++ lib/subghz/protocols/nero_sketch.c | 5 ++ lib/subghz/protocols/nero_sketch.h | 79 +++++++++++++++++ lib/subghz/protocols/nice_flo.c | 5 ++ lib/subghz/protocols/nice_flo.h | 81 ++++++++++++++++- lib/subghz/protocols/nice_flor_s.c | 17 ++-- lib/subghz/protocols/nice_flor_s.h | 50 +++++++++++ lib/subghz/protocols/princeton.c | 5 ++ lib/subghz/protocols/princeton.h | 79 +++++++++++++++++ lib/subghz/protocols/princeton_for_testing.h | 38 +++++--- lib/subghz/protocols/raw.h | 93 ++++++++++++++++++++ lib/subghz/protocols/registry.h | 14 +++ lib/subghz/protocols/scher_khan.c | 7 +- lib/subghz/protocols/scher_khan.h | 50 +++++++++++ lib/subghz/protocols/somfy_keytis.c | 15 +++- lib/subghz/protocols/somfy_keytis.h | 50 +++++++++++ lib/subghz/protocols/somfy_telis.c | 15 +++- lib/subghz/protocols/somfy_telis.h | 50 +++++++++++ lib/subghz/protocols/star_line.c | 28 ++++-- lib/subghz/protocols/star_line.h | 50 +++++++++++ lib/subghz/receiver.c | 2 +- lib/subghz/receiver.h | 39 +++++++- lib/subghz/subghz_file_encoder_worker.h | 39 ++++---- lib/subghz/subghz_keystore.h | 57 ++++++------ lib/subghz/subghz_tx_rx_worker.h | 54 ++++++------ lib/subghz/subghz_worker.h | 44 ++++----- lib/subghz/transmitter.h | 24 +++++ 54 files changed, 1732 insertions(+), 185 deletions(-) diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index c4324e7f..7a140685 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -177,7 +177,7 @@ SubGhz* subghz_alloc() { subghz->txrx->environment, "/ext/subghz/assets/came_atomo"); subghz_environment_set_nice_flor_s_rainbow_table_file_name( subghz->txrx->environment, "/ext/subghz/assets/nice_flor_s"); - subghz->txrx->receiver = subghz_receiver_alloc(subghz->txrx->environment); + subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); subghz_worker_set_overrun_callback( diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index 7ac73869..f9a85445 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -246,7 +246,7 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, "/ext/subghz/assets/nice_flor_s"); - SubGhzReceiver* receiver = subghz_receiver_alloc(environment); + SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); diff --git a/lib/subghz/blocks/decoder.h b/lib/subghz/blocks/decoder.h index a2d14f95..339e27c1 100644 --- a/lib/subghz/blocks/decoder.h +++ b/lib/subghz/blocks/decoder.h @@ -13,5 +13,16 @@ struct SubGhzBlockDecoder { uint8_t decode_count_bit; }; +/** + * Add data bit when decoding. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @param bit data, 1bit + */ void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @return hash Hash sum + */ uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len); diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 29bb5f48..0d8fe0a2 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -19,12 +19,32 @@ struct SubGhzBlockGeneric { uint16_t cnt; }; +/** + * Get modulation name. + * @param preset modulation,FuriHalSubGhzPreset + * @param preset_str Output modulation name + * @return true On success + */ bool subghz_block_generic_get_preset_name(FuriHalSubGhzPreset preset, string_t preset_str); +/** + * Serialize data SubGhzBlockGeneric. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_block_generic_serialize( SubGhzBlockGeneric* instance, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); +/** + * Deserialize data SubGhzBlockGeneric. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format); diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index 48eb8a8f..fde5191b 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -10,4 +10,10 @@ #define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) #define DURATION_DIFF(x, y) ((x < y) ? (y - x) : (x - y)) +/** + * Flip the data bitwise. + * @param key In data + * @param count_bit number of data bits + * @return Reverse data + */ uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit); diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index 2a2d04ce..b8e73e33 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -6,23 +6,61 @@ typedef struct SubGhzEnvironment SubGhzEnvironment; +/** + * Allocate SubGhzEnvironment. + * @return SubGhzEnvironment* pointer to a SubGhzEnvironment instance + */ SubGhzEnvironment* subghz_environment_alloc(); +/** + * Free SubGhzEnvironment. + * @param instance Pointer to a SubGhzEnvironment instance + */ void subghz_environment_free(SubGhzEnvironment* instance); +/** + * Downloading the manufacture key file. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + * @return true On succes + */ bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename); +/** + * Get pointer to a SubGhzKeystore* instance. + * @return SubGhzEnvironment* pointer to a SubGhzEnvironment instance + */ SubGhzKeystore* subghz_environment_get_keystore(SubGhzEnvironment* instance); +/** + * Set filename to work with Came Atomo. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + */ void subghz_environment_set_came_atomo_rainbow_table_file_name( SubGhzEnvironment* instance, const char* filename); +/** + * Get filename to work with Came Atomo. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Full path to the file + */ const char* subghz_environment_get_came_atomo_rainbow_table_file_name(SubGhzEnvironment* instance); +/** + * Set filename to work with Nice Flor-S. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + */ void subghz_environment_set_nice_flor_s_rainbow_table_file_name( SubGhzEnvironment* instance, const char* filename); +/** + * Get filename to work with Nice Flor-S. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Full path to the file + */ const char* subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance); diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h index 4232bc14..19d9c414 100644 --- a/lib/subghz/protocols/base.h +++ b/lib/subghz/protocols/base.h @@ -19,25 +19,55 @@ struct SubGhzProtocolDecoderBase { void* context; }; +/** + * Set a callback upon completion of successful decoding of one of the protocols. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param callback Callback, SubGhzProtocolDecoderBaseRxCallback + * @param context Context + */ void subghz_protocol_decoder_base_set_decoder_callback( SubGhzProtocolDecoderBase* decoder_base, SubGhzProtocolDecoderBaseRxCallback callback, void* context); +/** + * Getting a textual representation of the received data. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param output Resulting text + */ bool subghz_protocol_decoder_base_get_string( SubGhzProtocolDecoderBase* decoder_base, string_t output); +/** + * Serialize data SubGhzProtocolDecoderBase. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_base_serialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); +/** + * Deserialize data SubGhzProtocolDecoderBase. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_base_deserialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format); +/** + * Getting the hash sum of the last randomly received parcel. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base); // Encoder Base diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 90220cd3..467e3154 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -95,6 +95,11 @@ void subghz_protocol_encoder_came_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderCame instance + * @return true On success + */ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* instance) { furi_assert(instance); size_t index = 0; diff --git a/lib/subghz/protocols/came.h b/lib/subghz/protocols/came.h index 9b0e3f80..2fd1b718 100644 --- a/lib/subghz/protocols/came.h +++ b/lib/subghz/protocols/came.h @@ -11,46 +11,81 @@ extern const SubGhzProtocolDecoder subghz_protocol_came_decoder; extern const SubGhzProtocolEncoder subghz_protocol_came_encoder; extern const SubGhzProtocol subghz_protocol_came; +/** + * Allocate SubGhzProtocolEncoderCame. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderCame* pointer to a SubGhzProtocolEncoderCame instance + */ void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment); +/** + * Free SubGhzProtocolEncoderCame. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + */ void subghz_protocol_encoder_came_free(void* context); +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format); +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + */ void subghz_protocol_encoder_came_stop(void* context); +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderCame instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_came_yield(void* context); -/** Allocate SubGhzProtocolCame - * - * @return SubGhzProtocolCame* +/** + * Allocate SubGhzProtocolDecoderCame. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderCame* pointer to a SubGhzProtocolDecoderCame instance */ void* subghz_protocol_decoder_came_alloc(SubGhzEnvironment* environment); -/** Free SubGhzProtocolCame - * - * @param instance +/** + * Free SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance */ void subghz_protocol_decoder_came_free(void* context); -/** Reset internal state - * @param instance - SubGhzProtocolCame instance +/** + * Reset decoder SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance */ void subghz_protocol_decoder_came_reset(void* context); -/** Parse accepted duration - * - * @param instance - SubGhzProtocolCame instance - * @param data - LevelDuration level_duration +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us */ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration); +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_came_get_hash_data(void* context); -/** Outputting information from the parser - * - * @param instance - SubGhzProtocolCame* instance - * @param output - output string +/** + * Serialize data SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success */ bool subghz_protocol_decoder_came_serialize( void* context, @@ -58,6 +93,17 @@ bool subghz_protocol_decoder_came_serialize( uint32_t frequency, FuriHalSubGhzPreset preset); +/** + * Deserialize data SubGhzProtocolDecoderCame. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format); +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderCame instance + * @param output Resulting text + */ void subghz_protocol_decoder_came_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 4cde0186..67d5398f 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -186,10 +186,10 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t } } -/** Read bytes from rainbow table - * - * @param file_name - file name rainbow table - * @param number_atomo_magic_xor +/** + * Read bytes from rainbow table + * @param file_name Full path to rainbow table the file + * @param number_atomo_magic_xor Сell number in the array * @return atomo_magic_xor */ static uint64_t subghz_protocol_came_atomo_get_magic_xor_in_file( @@ -211,9 +211,10 @@ static uint64_t subghz_protocol_came_atomo_get_magic_xor_in_file( return atomo_magic_xor; } -/** Analysis of received data - * - * @param instance SubGhzBlockGeneric instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file */ static void subghz_protocol_came_atomo_remote_controller( SubGhzBlockGeneric* instance, diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index ee816f61..d1c856e1 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -10,15 +10,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder; extern const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder; extern const SubGhzProtocol subghz_protocol_came_atomo; +/** + * Allocate SubGhzProtocolDecoderCameAtomo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderCameAtomo* pointer to a SubGhzProtocolDecoderCameAtomo instance + */ void* subghz_protocol_decoder_came_atomo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + */ void subghz_protocol_decoder_came_atomo_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + */ void subghz_protocol_decoder_came_atomo_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_came_atomo_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderCameAtomo. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance + * @param output Resulting text + */ void subghz_protocol_decoder_came_atomo_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 54c6585e..fe5f7877 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -22,6 +22,9 @@ (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ (dip & 0x0001 ? '1' : '0') +/** + * Rainbow table Came Twee. + */ static const uint32_t came_twee_magic_numbers_xor[15] = { 0x0E0E0E00, 0x1D1D1D11, @@ -148,6 +151,10 @@ static LevelDuration return level_duration_make(data.level, data.duration); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderCameTwee instance + */ static void subghz_protocol_encoder_came_twee_get_upload(SubGhzProtocolEncoderCameTwee* instance) { furi_assert(instance); size_t index = 0; @@ -182,9 +189,9 @@ static void subghz_protocol_encoder_came_twee_get_upload(SubGhzProtocolEncoderCa instance->encoder.size_upload = index; } -/** Analysis of received data - * - * @param instance SubGhzProtocolCameTwee instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_came_twee_remote_controller(SubGhzBlockGeneric* instance) { /* Came Twee 54 bit, rolling code 15 parcels with diff --git a/lib/subghz/protocols/came_twee.h b/lib/subghz/protocols/came_twee.h index 044839b9..225f3c04 100644 --- a/lib/subghz/protocols/came_twee.h +++ b/lib/subghz/protocols/came_twee.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder; extern const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder; extern const SubGhzProtocol subghz_protocol_came_twee; +/** + * Allocate SubGhzProtocolEncoderCameTwee. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderCameTwee* pointer to a SubGhzProtocolEncoderCameTwee instance + */ void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderCameTwee. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + */ void subghz_protocol_encoder_came_twee_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + */ void subghz_protocol_encoder_came_twee_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_came_twee_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderCameTwee. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderCameTwee* pointer to a SubGhzProtocolDecoderCameTwee instance + */ void* subghz_protocol_decoder_came_twee_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + */ void subghz_protocol_decoder_came_twee_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + */ void subghz_protocol_decoder_came_twee_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_came_twee_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderCameTwee. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance + * @param output Resulting text + */ void subghz_protocol_decoder_came_twee_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index aa337f00..d5006ae6 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -159,6 +159,10 @@ void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t d } } +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ static void subghz_protocol_faac_slh_check_remote_controller(SubGhzBlockGeneric* instance) { uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h index d3ef2cd2..2575a5ff 100644 --- a/lib/subghz/protocols/faac_slh.h +++ b/lib/subghz/protocols/faac_slh.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder; extern const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder; extern const SubGhzProtocol subghz_protocol_faac_slh; +/** + * Allocate SubGhzProtocolDecoderFaacSLH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderFaacSLH* pointer to a SubGhzProtocolDecoderFaacSLH instance + */ void* subghz_protocol_decoder_faac_slh_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + */ void subghz_protocol_decoder_faac_slh_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + */ void subghz_protocol_decoder_faac_slh_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_faac_slh_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderFaacSLH. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance + * @param output Resulting text + */ void subghz_protocol_decoder_faac_slh_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index d45c2257..dd6548a2 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -88,6 +88,11 @@ void subghz_protocol_encoder_gate_tx_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderGateTx instance + * @return true On success + */ static bool subghz_protocol_encoder_gate_tx_get_upload(SubGhzProtocolEncoderGateTx* instance) { furi_assert(instance); size_t index = 0; @@ -258,6 +263,10 @@ void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t du } } +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ static void subghz_protocol_gate_tx_check_remote_controller(SubGhzBlockGeneric* instance) { uint32_t code_found_reverse = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); diff --git a/lib/subghz/protocols/gate_tx.h b/lib/subghz/protocols/gate_tx.h index fea5422d..4b5f4d00 100644 --- a/lib/subghz/protocols/gate_tx.h +++ b/lib/subghz/protocols/gate_tx.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder; extern const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder; extern const SubGhzProtocol subghz_protocol_gate_tx; +/** + * Allocate SubGhzProtocolEncoderGateTx. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderGateTx* pointer to a SubGhzProtocolEncoderGateTx instance + */ void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderGateTx. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + */ void subghz_protocol_encoder_gate_tx_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + */ void subghz_protocol_encoder_gate_tx_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderGateTx instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderGateTx. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderGateTx* pointer to a SubGhzProtocolDecoderGateTx instance + */ void* subghz_protocol_decoder_gate_tx_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + */ void subghz_protocol_decoder_gate_tx_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + */ void subghz_protocol_decoder_gate_tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_gate_tx_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderGateTx. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderGateTx instance + * @param output Resulting text + */ void subghz_protocol_decoder_gate_tx_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 8c32fcf7..7a3fd233 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -91,6 +91,11 @@ void subghz_protocol_encoder_hormann_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHormann instance + * @return true On success + */ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHormann* instance) { furi_assert(instance); @@ -285,6 +290,10 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du } } +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ static void subghz_protocol_hormann_check_remote_controller(SubGhzBlockGeneric* instance) { instance->btn = (instance->data >> 4) & 0xF; } diff --git a/lib/subghz/protocols/hormann.h b/lib/subghz/protocols/hormann.h index c5748603..2b09a065 100644 --- a/lib/subghz/protocols/hormann.h +++ b/lib/subghz/protocols/hormann.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_hormann_decoder; extern const SubGhzProtocolEncoder subghz_protocol_hormann_encoder; extern const SubGhzProtocol subghz_protocol_hormann; +/** + * Allocate SubGhzProtocolEncoderHormann. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHormann* pointer to a SubGhzProtocolEncoderHormann instance + */ void* subghz_protocol_encoder_hormann_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHormann. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + */ void subghz_protocol_encoder_hormann_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + */ void subghz_protocol_encoder_hormann_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHormann instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_hormann_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHormann. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHormann* pointer to a SubGhzProtocolDecoderHormann instance + */ void* subghz_protocol_decoder_hormann_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + */ void subghz_protocol_decoder_hormann_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + */ void subghz_protocol_decoder_hormann_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_hormann_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderHormann. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHormann instance + * @param output Resulting text + */ void subghz_protocol_decoder_hormann_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index d97c90c4..d2962435 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -158,6 +158,10 @@ void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t durati } } +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ static void subghz_protocol_ido_check_remote_controller(SubGhzBlockGeneric* instance) { uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); diff --git a/lib/subghz/protocols/ido.h b/lib/subghz/protocols/ido.h index c357725c..c53a1593 100644 --- a/lib/subghz/protocols/ido.h +++ b/lib/subghz/protocols/ido.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_ido_decoder; extern const SubGhzProtocolEncoder subghz_protocol_ido_encoder; extern const SubGhzProtocol subghz_protocol_ido; +/** + * Allocate SubGhzProtocolDecoderIDo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderIDo* pointer to a SubGhzProtocolDecoderIDo instance + */ void* subghz_protocol_decoder_ido_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + */ void subghz_protocol_decoder_ido_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + */ void subghz_protocol_decoder_ido_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_ido_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderIDo. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderIDo instance + * @param output Resulting text + */ void subghz_protocol_decoder_ido_get_string(void* context, string_t output); \ No newline at end of file diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 1679af3f..975cd29c 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -81,6 +81,12 @@ const SubGhzProtocol subghz_protocol_keeloq = { .encoder = &subghz_protocol_keeloq_encoder, }; +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ static void subghz_protocol_keeloq_check_remote_controller( SubGhzBlockGeneric* instance, SubGhzKeystore* keystore, @@ -107,6 +113,11 @@ void subghz_protocol_encoder_keeloq_free(void* context) { free(instance); } +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq* instance + * @param btn Button number, 4 bit + */ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { instance->generic.cnt++; uint32_t fix = btn << 28 | instance->generic.serial; @@ -179,15 +190,18 @@ bool subghz_protocol_keeloq_create_data( return res; } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ static bool subghz_protocol_encoder_keeloq_get_upload(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { furi_assert(instance); //gen new key if(subghz_protocol_keeloq_gen_data(instance, btn)) { - //ToDo Update display data - // if(instance->common.callback) - // instance->common.callback((SubGhzProtocolCommon*)instance, instance->common.context); + //ToDo if you need to add a callback to automatically update the data on the display } else { return false; } @@ -419,6 +433,14 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur } } +/** + * Validation of decrypt data. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param decrypt Decrypd data + * @param btn Button number, 4 bit + * @param end_serial decrement the last 10 bits of the serial number + * @return true On success + */ static inline bool subghz_protocol_keeloq_check_decrypt( SubGhzBlockGeneric* instance, uint32_t decrypt, @@ -433,11 +455,13 @@ static inline bool subghz_protocol_keeloq_check_decrypt( return false; } -/** Checking the accepted code against the database manafacture key - * - * @param instance SubGhzProtocolKeeloq instance - * @param fix fix part of the parcel - * @param hop hop encrypted part of the parcel +/** + * Checking the accepted code against the database manafacture key + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param fix Fix part of the parcel + * @param hop Hop encrypted part of the parcel + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name * @return true on successful search */ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( @@ -575,10 +599,6 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( return 0; } -/** Analysis of received data - * - * @param instance SubGhzProtocolKeeloq instance - */ static void subghz_protocol_keeloq_check_remote_controller( SubGhzBlockGeneric* instance, SubGhzKeystore* keystore, diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index 61c9a0e4..72961ceb 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -11,8 +11,31 @@ extern const SubGhzProtocolDecoder subghz_protocol_keeloq_decoder; extern const SubGhzProtocolEncoder subghz_protocol_keeloq_encoder; extern const SubGhzProtocol subghz_protocol_keeloq; +/** + * Allocate SubGhzProtocolEncoderKeeloq. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderKeeloq* pointer to a SubGhzProtocolEncoderKeeloq instance + */ void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderKeeloq. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + */ void subghz_protocol_encoder_keeloq_free(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Container value, 16 bit + * @param manufacture_name Name of manufacturer's key + * @param frequency Transmission frequency, Hz + * @param preset Modulation, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_keeloq_create_data( void* context, FlipperFormat* flipper_format, @@ -22,18 +45,87 @@ bool subghz_protocol_keeloq_create_data( const char* manufacture_name, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + */ void subghz_protocol_encoder_keeloq_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_keeloq_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderKeeloq. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKeeloq* pointer to a SubGhzProtocolDecoderKeeloq instance + */ void* subghz_protocol_decoder_keeloq_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + */ void subghz_protocol_decoder_keeloq_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + */ void subghz_protocol_decoder_keeloq_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_keeloq_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderKeeloq. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance + * @param output Resulting text + */ void subghz_protocol_decoder_keeloq_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index b281666f..50d447ed 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -10,7 +10,6 @@ * https://phreakerclub.com/forum/showthread.php?t=1094 * */ - #define KEELOQ_NLF 0x3A5C742E #define bit(x, n) (((x) >> (n)) & 1) #define g5(x, a, b, c, d, e) \ @@ -26,41 +25,44 @@ #define KEELOQ_LEARNING_SECURE 3u #define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u -/** Simple Learning Encrypt +/** + * Simple Learning Encrypt * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter * @param key - manufacture (64bit) * @return keeloq encrypt data */ uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key); -/** Simple Learning Decrypt +/** + * Simple Learning Decrypt * @param data - keeloq encrypt data * @param key - manufacture (64bit) * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter */ uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key); -/** Normal Learning +/** + * Normal Learning * @param data - serial number (28bit) * @param key - manufacture (64bit) * @return manufacture for this serial number (64bit) */ uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key); -/** Secure Learning +/** + * Secure Learning * @param data - serial number (28bit) * @param seed - seed number (32bit) * @param key - manufacture (64bit) * @return manufacture for this serial number (64bit) */ - uint64_t subghz_protocol_keeloq_common_secure_learning(uint32_t data, uint32_t seed, const uint64_t key); -/** Magic_xor_type1 Learning +/** + * Magic_xor_type1 Learning * @param data - serial number (28bit) * @param xor - magic xor (64bit) * @return manufacture for this serial number (64bit) */ - uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor); diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 2a16bced..79e36ea7 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -203,9 +203,9 @@ uint8_t subghz_protocol_kia_crc8(uint8_t* data, size_t len) { return crc; } -/** Analysis of received data - * - * @param instance SubGhzProtocolKIA instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance) { /* diff --git a/lib/subghz/protocols/kia.h b/lib/subghz/protocols/kia.h index 2f2ccde3..40e49683 100644 --- a/lib/subghz/protocols/kia.h +++ b/lib/subghz/protocols/kia.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_kia_decoder; extern const SubGhzProtocolEncoder subghz_protocol_kia_encoder; extern const SubGhzProtocol subghz_protocol_kia; +/** + * Allocate SubGhzProtocolDecoderKIA. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKIA* pointer to a SubGhzProtocolDecoderKIA instance + */ void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + */ void subghz_protocol_decoder_kia_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + */ void subghz_protocol_decoder_kia_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_kia_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderKIA. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKIA instance + * @param output Resulting text + */ void subghz_protocol_decoder_kia_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index a9c19f38..d25c377a 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -90,6 +90,11 @@ void subghz_protocol_encoder_nero_radio_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNeroRadio instance + * @return true On success + */ static bool subghz_protocol_encoder_nero_radio_get_upload(SubGhzProtocolEncoderNeroRadio* instance) { furi_assert(instance); diff --git a/lib/subghz/protocols/nero_radio.h b/lib/subghz/protocols/nero_radio.h index 4cd6c506..46c80738 100644 --- a/lib/subghz/protocols/nero_radio.h +++ b/lib/subghz/protocols/nero_radio.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_nero_radio_decoder; extern const SubGhzProtocolEncoder subghz_protocol_nero_radio_encoder; extern const SubGhzProtocol subghz_protocol_nero_radio; +/** + * Allocate SubGhzProtocolEncoderNeroRadio. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNeroRadio* pointer to a SubGhzProtocolEncoderNeroRadio instance + */ void* subghz_protocol_encoder_nero_radio_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNeroRadio. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + */ void subghz_protocol_encoder_nero_radio_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + */ void subghz_protocol_encoder_nero_radio_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_nero_radio_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNeroRadio. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNeroRadio* pointer to a SubGhzProtocolDecoderNeroRadio instance + */ void* subghz_protocol_decoder_nero_radio_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + */ void subghz_protocol_decoder_nero_radio_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + */ void subghz_protocol_decoder_nero_radio_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_nero_radio_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderNeroRadio. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance + * @param output Resulting text + */ void subghz_protocol_decoder_nero_radio_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 408ef064..12a1a547 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -95,6 +95,11 @@ void subghz_protocol_encoder_nero_sketch_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNeroSketch instance + * @return true On success + */ static bool subghz_protocol_encoder_nero_sketch_get_upload(SubGhzProtocolEncoderNeroSketch* instance) { furi_assert(instance); diff --git a/lib/subghz/protocols/nero_sketch.h b/lib/subghz/protocols/nero_sketch.h index 5042dff5..e2b12f64 100644 --- a/lib/subghz/protocols/nero_sketch.h +++ b/lib/subghz/protocols/nero_sketch.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_nero_sketch_decoder; extern const SubGhzProtocolEncoder subghz_protocol_nero_sketch_encoder; extern const SubGhzProtocol subghz_protocol_nero_sketch; +/** + * Allocate SubGhzProtocolEncoderNeroSketch. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNeroSketch* pointer to a SubGhzProtocolEncoderNeroSketch instance + */ void* subghz_protocol_encoder_nero_sketch_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNeroSketch. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + */ void subghz_protocol_encoder_nero_sketch_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + */ void subghz_protocol_encoder_nero_sketch_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_nero_sketch_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNeroSketch. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNeroSketch* pointer to a SubGhzProtocolDecoderNeroSketch instance + */ void* subghz_protocol_decoder_nero_sketch_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + */ void subghz_protocol_decoder_nero_sketch_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + */ void subghz_protocol_decoder_nero_sketch_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_nero_sketch_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderNeroSketch. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance + * @param output Resulting text + */ void subghz_protocol_decoder_nero_sketch_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index 387c3477..01371b73 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -94,6 +94,11 @@ void subghz_protocol_encoder_nice_flo_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNiceFlo instance + * @return true On success + */ static bool subghz_protocol_encoder_nice_flo_get_upload(SubGhzProtocolEncoderNiceFlo* instance) { furi_assert(instance); size_t index = 0; diff --git a/lib/subghz/protocols/nice_flo.h b/lib/subghz/protocols/nice_flo.h index 785866a0..ca5cfe3f 100644 --- a/lib/subghz/protocols/nice_flo.h +++ b/lib/subghz/protocols/nice_flo.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_nice_flo_decoder; extern const SubGhzProtocolEncoder subghz_protocol_nice_flo_encoder; extern const SubGhzProtocol subghz_protocol_nice_flo; +/** + * Allocate SubGhzProtocolEncoderNiceFlo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNiceFlo* pointer to a SubGhzProtocolEncoderNiceFlo instance + */ void* subghz_protocol_encoder_nice_flo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNiceFlo. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + */ void subghz_protocol_encoder_nice_flo_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + */ void subghz_protocol_encoder_nice_flo_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_nice_flo_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNiceFlo. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNiceFlo* pointer to a SubGhzProtocolDecoderNiceFlo instance + */ void* subghz_protocol_decoder_nice_flo_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + */ void subghz_protocol_decoder_nice_flo_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + */ void subghz_protocol_decoder_nice_flo_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); -void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output); + +/** + * Serialize data SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_nice_flo_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderNiceFlo. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance + * @param output Resulting text + */ +void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output); \ No newline at end of file diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 21be4450..8a3e26a8 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -76,11 +76,11 @@ const SubGhzProtocol subghz_protocol_nice_flor_s = { .encoder = &subghz_protocol_nice_flor_s_encoder, }; -/** Read bytes from rainbow table - * - * @param instance - SubGhzProtocolNiceFlorS* instance - * @param address - address byte - * @return byte data +/** + * Read bytes from rainbow table + * @param file_name Full path to rainbow table the file + * @param address Byte address in file + * @return data */ static uint8_t subghz_protocol_nice_flor_s_get_byte_in_file(const char* file_name, uint32_t address) { @@ -277,9 +277,10 @@ void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_ } } -/** Decrypt protocol Nice Flor S - * - * @param instance - SubGhzProtocolNiceFlorS* instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file */ static void subghz_protocol_nice_flor_s_remote_controller( SubGhzBlockGeneric* instance, diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 57ff50bc..d11264bc 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder; extern const SubGhzProtocolEncoder subghz_protocol_nice_flor_s_encoder; extern const SubGhzProtocol subghz_protocol_nice_flor_s; +/** + * Allocate SubGhzProtocolDecoderNiceFlorS. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNiceFlorS* pointer to a SubGhzProtocolDecoderNiceFlorS instance + */ void* subghz_protocol_decoder_nice_flor_s_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + */ void subghz_protocol_decoder_nice_flor_s_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + */ void subghz_protocol_decoder_nice_flor_s_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_nice_flor_s_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderNiceFlorS. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance + * @param output Resulting text + */ void subghz_protocol_decoder_nice_flor_s_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 06d2a8d2..d3906465 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -98,6 +98,11 @@ void subghz_protocol_encoder_princeton_free(void* context) { free(instance); } +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderPrinceton instance + * @return true On success + */ static bool subghz_protocol_encoder_princeton_get_upload(SubGhzProtocolEncoderPrinceton* instance) { furi_assert(instance); diff --git a/lib/subghz/protocols/princeton.h b/lib/subghz/protocols/princeton.h index b5ac9849..129819c6 100644 --- a/lib/subghz/protocols/princeton.h +++ b/lib/subghz/protocols/princeton.h @@ -11,20 +11,99 @@ extern const SubGhzProtocolDecoder subghz_protocol_princeton_decoder; extern const SubGhzProtocolEncoder subghz_protocol_princeton_encoder; extern const SubGhzProtocol subghz_protocol_princeton; +/** + * Allocate SubGhzProtocolEncoderPrinceton. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderPrinceton* pointer to a SubGhzProtocolEncoderPrinceton instance + */ void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderPrinceton. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + */ void subghz_protocol_encoder_princeton_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + */ void subghz_protocol_encoder_princeton_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_princeton_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderPrinceton. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderPrinceton* pointer to a SubGhzProtocolDecoderPrinceton instance + */ void* subghz_protocol_decoder_princeton_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + */ void subghz_protocol_decoder_princeton_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + */ void subghz_protocol_decoder_princeton_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_princeton_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderPrinceton. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance + * @param output Resulting text + */ void subghz_protocol_decoder_princeton_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/princeton_for_testing.h b/lib/subghz/protocols/princeton_for_testing.h index 307a73c7..07a37ec5 100644 --- a/lib/subghz/protocols/princeton_for_testing.h +++ b/lib/subghz/protocols/princeton_for_testing.h @@ -9,21 +9,29 @@ typedef struct SubGhzEncoderPrinceton SubGhzEncoderPrinceton; typedef void (*SubGhzDecoderPrincetonCallback)(SubGhzDecoderPrinceton* parser, void* context); -/** Allocate SubGhzEncoderPrinceton +/** + * Allocate SubGhzEncoderPrinceton * @return pointer to SubGhzEncoderPrinceton instance */ SubGhzEncoderPrinceton* subghz_encoder_princeton_for_testing_alloc(); -/** Free SubGhzEncoderPrinceton instance +/** + * Free SubGhzEncoderPrinceton instance * @param instance - SubGhzEncoderPrinceton instance */ void subghz_encoder_princeton_for_testing_free(SubGhzEncoderPrinceton* instance); +/** + * Forced transmission stop. + * @param instance Pointer to a SubGhzEncoderPrinceton instance + * @param time_stop Transmission stop time, ms + */ void subghz_encoder_princeton_for_testing_stop( SubGhzEncoderPrinceton* instance, uint32_t time_stop); -/** Set new encoder params +/** + * Set new encoder params * @param instance - SubGhzEncoderPrinceton instance * @param key - 24bit key * @param repeat - how many times to repeat @@ -35,31 +43,34 @@ void subghz_encoder_princeton_for_testing_set( size_t repeat, uint32_t frequency); -/** Get repeat count left +/** + * Get repeat count left * @param instance - SubGhzEncoderPrinceton instance * @return repeat count left */ size_t subghz_encoder_princeton_for_testing_get_repeat_left(SubGhzEncoderPrinceton* instance); -/** Print encoder log +/** + * Print encoder log * @param instance - SubGhzEncoderPrinceton instance */ void subghz_encoder_princeton_for_testing_print_log(void* context); -/** Get level duration +/** + * Get level duration * @param instance - SubGhzEncoderPrinceton instance * @return level duration */ LevelDuration subghz_encoder_princeton_for_testing_yield(void* context); -/** Allocate SubGhzDecoderPrinceton - * +/** + * Allocate SubGhzDecoderPrinceton * @return SubGhzDecoderPrinceton* */ SubGhzDecoderPrinceton* subghz_decoder_princeton_for_testing_alloc(); -/** Free SubGhzDecoderPrinceton - * +/** + * Free SubGhzDecoderPrinceton * @param instance */ void subghz_decoder_princeton_for_testing_free(SubGhzDecoderPrinceton* instance); @@ -69,13 +80,14 @@ void subghz_decoder_princeton_for_testing_set_callback( SubGhzDecoderPrincetonCallback callback, void* context); -/** Reset internal state +/** + * Reset internal state * @param instance - SubGhzDecoderPrinceton instance */ void subghz_decoder_princeton_for_testing_reset(SubGhzDecoderPrinceton* instance); -/** Parse accepted duration - * +/** + * Parse accepted duration * @param instance - SubGhzDecoderPrinceton instance * @param data - LevelDuration level_duration */ diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index cb8dc237..febad6f7 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -13,28 +13,121 @@ extern const SubGhzProtocolDecoder subghz_protocol_raw_decoder; extern const SubGhzProtocolEncoder subghz_protocol_raw_encoder; extern const SubGhzProtocol subghz_protocol_raw; +/** + * Open file for writing + * @param instance Pointer to a SubGhzProtocolDecoderRAW instance + * @param dev_name File name + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_raw_save_to_file_init( SubGhzProtocolDecoderRAW* instance, const char* dev_name, uint32_t frequency, FuriHalSubGhzPreset preset); +/** + * Stop writing file to flash + * @param instance Pointer to a SubGhzProtocolDecoderRAW instance + */ void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance); + +/** + * Get the number of samples received SubGhzProtocolDecoderRAW. + * @param instance Pointer to a SubGhzProtocolDecoderRAW instance + * @return count of samples + */ size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance); + +/** + * Allocate SubGhzProtocolDecoderRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRAW* pointer to a SubGhzProtocolDecoderRAW instance + */ void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRAW. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + */ void subghz_protocol_decoder_raw_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRAW. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + */ void subghz_protocol_decoder_raw_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRAW instance + * @param output Resulting text + */ void subghz_protocol_decoder_raw_get_string(void* context, string_t output); +/** + * Allocate SubGhzProtocolEncoderRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRAW* pointer to a SubGhzProtocolEncoderRAW instance + */ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRAW. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + */ void subghz_protocol_encoder_raw_free(void* context); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + */ void subghz_protocol_encoder_raw_stop(void* context); + +/** + * Сallback on completion of file transfer. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + */ void subghz_protocol_raw_file_encoder_worker_callback_end(void* context); + +/** + * Set callback on completion of file transfer. + * @param instance Pointer to a SubGhzProtocolEncoderRAW instance + * @param callback_end Callback, SubGhzProtocolEncoderRAWCallbackEnd + * @param context_end Context + */ void subghz_protocol_raw_file_encoder_worker_set_callback_end( SubGhzProtocolEncoderRAW* instance, SubGhzProtocolEncoderRAWCallbackEnd callback_end, void* context_end); + +/** + * File generation for RAW work. + * @param flipper_format Pointer to a FlipperFormat instance + * @param file_name File name + */ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_name); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + * @return LevelDuration + */ LevelDuration subghz_protocol_encoder_raw_yield(void* context); diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index 30d2e336..e6e7f234 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -2,8 +2,22 @@ #include "../types.h" +/** + * Registration by name SubGhzProtocol. + * @param name Protocol name + * @return SubGhzProtocol* pointer to a SubGhzProtocol instance + */ const SubGhzProtocol* subghz_protocol_registry_get_by_name(const char* name); +/** + * Registration protocol by index in array SubGhzProtocol. + * @param index Protocol by index in array + * @return SubGhzProtocol* pointer to a SubGhzProtocol instance + */ const SubGhzProtocol* subghz_protocol_registry_get_by_index(size_t index); +/** + * Getting the number of registered protocols. + * @return Number of protocols + */ size_t subghz_protocol_registry_count(); diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 9d6dc6dd..68fc672b 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -200,9 +200,10 @@ void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t } } -/** Analysis of received data - * - * @param instance SubGhzProtocolScherKhan instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param protocol_name */ static void subghz_protocol_scher_khan_check_remote_controller( SubGhzBlockGeneric* instance, diff --git a/lib/subghz/protocols/scher_khan.h b/lib/subghz/protocols/scher_khan.h index cf54344e..fa3f5147 100644 --- a/lib/subghz/protocols/scher_khan.h +++ b/lib/subghz/protocols/scher_khan.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder; extern const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder; extern const SubGhzProtocol subghz_protocol_scher_khan; +/** + * Allocate SubGhzProtocolDecoderScherKhan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderScherKhan* pointer to a SubGhzProtocolDecoderScherKhan instance + */ void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + */ void subghz_protocol_decoder_scher_khan_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + */ void subghz_protocol_decoder_scher_khan_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_scher_khan_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param output Resulting text + */ void subghz_protocol_decoder_scher_khan_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 3ec0bbe0..9f58d734 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -99,6 +99,11 @@ void subghz_protocol_decoder_somfy_keytis_reset(void* context) { NULL); } +/** + * Сhecksum calculation. + * @param data Вata for checksum calculation + * @return CRC + */ static uint8_t subghz_protocol_somfy_keytis_crc(uint64_t data) { uint8_t crc = 0; data &= 0xFFF0FFFFFFFFFF; @@ -231,9 +236,9 @@ void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32 } } -/** Analysis of received data - * - * @param instance SubGhzProtocolSomfyKeytis instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_somfy_keytis_check_remote_controller(SubGhzBlockGeneric* instance) { //https://pushstack.wordpress.com/somfy-rts-protocol/ @@ -341,6 +346,10 @@ static void subghz_protocol_somfy_keytis_check_remote_controller(SubGhzBlockGene instance->serial = data & 0xFFFFFF; } +/** + * Get button name. + * @param btn Button number, 4 bit + */ static const char* subghz_protocol_somfy_keytis_get_name_button(uint8_t btn) { const char* name_btn[0x10] = { "Unknown", diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h index 1bc32210..eea12f36 100644 --- a/lib/subghz/protocols/somfy_keytis.h +++ b/lib/subghz/protocols/somfy_keytis.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_somfy_keytis_decoder; extern const SubGhzProtocolEncoder subghz_protocol_somfy_keytis_encoder; extern const SubGhzProtocol subghz_protocol_somfy_keytis; +/** + * Allocate SubGhzProtocolDecoderSomfyKeytis. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSomfyKeytis* pointer to a SubGhzProtocolDecoderSomfyKeytis instance + */ void* subghz_protocol_decoder_somfy_keytis_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + */ void subghz_protocol_decoder_somfy_keytis_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + */ void subghz_protocol_decoder_somfy_keytis_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_somfy_keytis_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderSomfyKeytis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance + * @param output Resulting text + */ void subghz_protocol_decoder_somfy_keytis_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 73815f2b..4f1212bc 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -98,6 +98,11 @@ void subghz_protocol_decoder_somfy_telis_reset(void* context) { NULL); } +/** + * Сhecksum calculation. + * @param data Вata for checksum calculation + * @return CRC + */ static uint8_t subghz_protocol_somfy_telis_crc(uint64_t data) { uint8_t crc = 0; data &= 0xFFF0FFFFFFFFFF; @@ -225,9 +230,9 @@ void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_ } } -/** Analysis of received data - * - * @param instance SubGhzProtocolSomfyTelis instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { //https://pushstack.wordpress.com/somfy-rts-protocol/ @@ -298,6 +303,10 @@ static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGener instance->serial = data & 0xFFFFFF; } +/** + * Get button name. + * @param btn Button number, 4 bit + */ static const char* subghz_protocol_somfy_telis_get_name_button(uint8_t btn) { const char* name_btn[0x10] = { "Unknown", diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index e28c2b51..3414de6f 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_somfy_telis_decoder; extern const SubGhzProtocolEncoder subghz_protocol_somfy_telis_encoder; extern const SubGhzProtocol subghz_protocol_somfy_telis; +/** + * Allocate SubGhzProtocolDecoderSomfyTelis. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSomfyTelis* pointer to a SubGhzProtocolDecoderSomfyTelis instance + */ void* subghz_protocol_decoder_somfy_telis_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + */ void subghz_protocol_decoder_somfy_telis_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + */ void subghz_protocol_decoder_somfy_telis_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_somfy_telis_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderSomfyTelis. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance + * @param output Resulting text + */ void subghz_protocol_decoder_somfy_telis_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index e253fae7..7db33570 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -181,6 +181,14 @@ void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t } } +/** + * Validation of decrypt data. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param decrypt Decrypd data + * @param btn Button number, 4 bit + * @param end_serial decrement the last 10 bits of the serial number + * @return true On success + */ static inline bool subghz_protocol_star_line_check_decrypt( SubGhzBlockGeneric* instance, uint32_t decrypt, @@ -194,11 +202,13 @@ static inline bool subghz_protocol_star_line_check_decrypt( return false; } -/** Checking the accepted code against the database manafacture key - * - * @param instance SubGhzProtocolStarLine instance - * @param fix fix part of the parcel - * @param hop hop encrypted part of the parcel +/** + * Checking the accepted code against the database manafacture key + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param fix Fix part of the parcel + * @param hop Hop encrypted part of the parcel + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name * @return true on successful search */ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( @@ -279,9 +289,11 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( return 0; } -/** Analysis of received data - * - * @param instance SubGhzProtocolStarLine instance +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name */ static void subghz_protocol_star_line_check_remote_controller( SubGhzBlockGeneric* instance, diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index 1e382c7d..9253ff52 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -11,15 +11,65 @@ extern const SubGhzProtocolDecoder subghz_protocol_star_line_decoder; extern const SubGhzProtocolEncoder subghz_protocol_star_line_encoder; extern const SubGhzProtocol subghz_protocol_star_line; +/** + * Allocate SubGhzProtocolDecoderStarLine. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderStarLine* pointer to a SubGhzProtocolDecoderStarLine instance + */ void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + */ void subghz_protocol_decoder_star_line_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + */ void subghz_protocol_decoder_star_line_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @return hash Hash sum + */ uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ bool subghz_protocol_decoder_star_line_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param output Resulting text + */ void subghz_protocol_decoder_star_line_get_string(void* context, string_t output); diff --git a/lib/subghz/receiver.c b/lib/subghz/receiver.c index 652a5f3c..cf4085df 100644 --- a/lib/subghz/receiver.c +++ b/lib/subghz/receiver.c @@ -19,7 +19,7 @@ struct SubGhzReceiver { void* context; }; -SubGhzReceiver* subghz_receiver_alloc(SubGhzEnvironment* environment) { +SubGhzReceiver* subghz_receiver_alloc_init(SubGhzEnvironment* environment) { SubGhzReceiver* instance = malloc(sizeof(SubGhzReceiver)); SubGhzReceiverSlotArray_init(instance->slots); diff --git a/lib/subghz/receiver.h b/lib/subghz/receiver.h index eb66bcee..1357ecbe 100644 --- a/lib/subghz/receiver.h +++ b/lib/subghz/receiver.h @@ -10,19 +10,56 @@ typedef void (*SubGhzReceiverCallback)( SubGhzProtocolDecoderBase* decoder_base, void* context); -SubGhzReceiver* subghz_receiver_alloc(SubGhzEnvironment* environment); +/** + * Allocate and init SubGhzReceiver. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzReceiver* pointer to a SubGhzReceiver instance + */ +SubGhzReceiver* subghz_receiver_alloc_init(SubGhzEnvironment* environment); +/** + * Free SubGhzReceiver. + * @param instance Pointer to a SubGhzReceiver instance + */ void subghz_receiver_free(SubGhzReceiver* instance); +/** + * Parse a raw sequence of levels and durations received from the air. + * @param instance Pointer to a SubGhzReceiver instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ void subghz_receiver_decode(SubGhzReceiver* instance, bool level, uint32_t duration); +/** + * Reset decoder SubGhzReceiver. + * @param instance Pointer to a SubGhzReceiver instance + */ void subghz_receiver_reset(SubGhzReceiver* instance); +/** + * Set a callback upon completion of successful decoding of one of the protocols. + * @param instance Pointer to a SubGhzReceiver instance + * @param callback Callback, SubGhzReceiverCallback + * @param context Context + */ void subghz_receiver_set_rx_callback( SubGhzReceiver* instance, SubGhzReceiverCallback callback, void* context); +/** + * Set the filter of receivers that will work at the moment. + * @param instance Pointer to a SubGhzReceiver instance + * @param filter Filter, SubGhzProtocolFlag + */ void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag filter); + +/** + * Search for a cattery by his name. + * @param instance Pointer to a SubGhzReceiver instance + * @param decoder_name Receiver name + * @return SubGhzProtocolDecoderBase* pointer to a SubGhzProtocolDecoderBase instance + */ SubGhzProtocolDecoderBase* subghz_receiver_search_decoder_base_by_name(SubGhzReceiver* instance, const char* decoder_name); diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index 6b9c3882..a87be5cd 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -6,8 +6,8 @@ typedef void (*SubGhzFileEncoderWorkerCallbackEnd)(void* context); typedef struct SubGhzFileEncoderWorker SubGhzFileEncoderWorker; -/** End callback SubGhzWorker - * +/** + * End callback SubGhzWorker. * @param instance SubGhzFileEncoderWorker instance * @param callback SubGhzFileEncoderWorkerCallbackEnd callback */ @@ -16,36 +16,41 @@ void subghz_file_encoder_worker_callback_end( SubGhzFileEncoderWorkerCallbackEnd callback_end, void* context_end); -/** Allocate SubGhzFileEncoderWorker - * - * @return SubGhzFileEncoderWorker* +/** + * Allocate SubGhzFileEncoderWorker. + * @return SubGhzFileEncoderWorker* pointer to a SubGhzFileEncoderWorker instance */ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc(); -/** Free SubGhzFileEncoderWorker - * - * @param instance SubGhzFileEncoderWorker instance +/** + * Free SubGhzFileEncoderWorker. + * @param instance Pointer to a SubGhzFileEncoderWorker instance */ void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance); +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzFileEncoderWorker instance + * @return LevelDuration + */ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context); -/** Start SubGhzFileEncoderWorker - * - * @param instance SubGhzFileEncoderWorker instance +/** + * Start SubGhzFileEncoderWorker. + * @param instance Pointer to a SubGhzFileEncoderWorker instance * @return bool - true if ok */ bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path); -/** Stop SubGhzFileEncoderWorker - * - * @param instance SubGhzFileEncoderWorker instance +/** + * Stop SubGhzFileEncoderWorker + * @param instance Pointer to a SubGhzFileEncoderWorker instance */ void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance); -/** Check if worker is running - * - * @param instance SubGhzFileEncoderWorker instance +/** + * Check if worker is running + * @param instance Pointer to a SubGhzFileEncoderWorker instance * @return bool - true if running */ bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance); diff --git a/lib/subghz/subghz_keystore.h b/lib/subghz/subghz_keystore.h index 58af35e5..be9a19df 100644 --- a/lib/subghz/subghz_keystore.h +++ b/lib/subghz/subghz_keystore.h @@ -16,54 +16,57 @@ ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST) typedef struct SubGhzKeystore SubGhzKeystore; -/** Allocate SubGhzKeystore - * - * @return SubGhzKeystore* +/** + * Allocate SubGhzKeystore. + * @return SubGhzKeystore* pointer to a SubGhzKeystore instance */ SubGhzKeystore* subghz_keystore_alloc(); -/** Free SubGhzKeystore - * - * @param instance +/** + * Free SubGhzKeystore. + * @param instance Pointer to a SubGhzKeystore instance */ void subghz_keystore_free(SubGhzKeystore* instance); -/** Loading manufacture key from file - * - * @param instance - SubGhzKeystore instance - * @param filename - const char* full path to the file +/** + * Loading manufacture key from file + * @param instance Pointer to a SubGhzKeystore instance + * @param filename Full path to the file */ bool subghz_keystore_load(SubGhzKeystore* instance, const char* filename); -/** Save manufacture key to file - * - * @param instance - SubGhzKeystore instance - * @param filename - const char* full path to the file +/** + * Save manufacture key to file + * @param instance Pointer to a SubGhzKeystore instance + * @param filename Full path to the file + * @return true On success */ bool subghz_keystore_save(SubGhzKeystore* instance, const char* filename, uint8_t* iv); -/** Get array of keys and names manufacture - * - * @param instance - SubGhzKeystore instance +/** + * Get array of keys and names manufacture + * @param instance Pointer to a SubGhzKeystore instance * @return SubGhzKeyArray_t* */ SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance); -/** Save RAW encrypted to file - * - * @param input_file_name - const char* full path to the input file - * @param output_file_name - const char* full path to the output file +/** + * Save RAW encrypted to file + * @param input_file_name Full path to the input file + * @param output_file_name Full path to the output file + * @param iv IV, 16 bytes in hex */ bool subghz_keystore_raw_encrypted_save( const char* input_file_name, const char* output_file_name, uint8_t* iv); -/** Get decrypt RAW data to file - * - * @param file_name - const char* full path to the input file - * @param offset - offset from the start of the RAW data - * @param data - returned array - * @param len - required data length +/** + * Get decrypt RAW data to file + * @param file_name Full path to the input file + * @param offset Offset from the start of the RAW data + * @param data Returned array + * @param len Required data length + * @return true On success */ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* data, size_t len); diff --git a/lib/subghz/subghz_tx_rx_worker.h b/lib/subghz/subghz_tx_rx_worker.h index 3b3dd711..3f3f8276 100644 --- a/lib/subghz/subghz_tx_rx_worker.h +++ b/lib/subghz/subghz_tx_rx_worker.h @@ -12,34 +12,34 @@ typedef enum { SubGhzTxRxWorkerStatusRx, } SubGhzTxRxWorkerStatus; -/** SubGhzTxRxWorker, add data to transfer - * - * @param instance SubGhzTxRxWorker instance +/** + * SubGhzTxRxWorker, add data to transfer + * @param instance Pointer to a SubGhzTxRxWorker instance * @param data *data * @param size data size * @return bool true if ok */ bool subghz_tx_rx_worker_write(SubGhzTxRxWorker* instance, uint8_t* data, size_t size); -/** SubGhzTxRxWorker, get available data - * - * @param instance SubGhzTxRxWorker instance +/** + * SubGhzTxRxWorker, get available data + * @param instance Pointer to a SubGhzTxRxWorker instance * @return size_t data size */ size_t subghz_tx_rx_worker_available(SubGhzTxRxWorker* instance); -/** SubGhzTxRxWorker, read data - * - * @param instance SubGhzTxRxWorker instance +/** + * SubGhzTxRxWorker, read data + * @param instance Pointer to a SubGhzTxRxWorker instance * @param data *data * @param size max data size, which can be read * @return size_t data size, how much is actually read */ size_t subghz_tx_rx_worker_read(SubGhzTxRxWorker* instance, uint8_t* data, size_t size); -/** Сallback SubGhzTxRxWorker when there is data to read in an empty buffer - * - * @param instance SubGhzTxRxWorker instance +/** + * Сallback SubGhzTxRxWorker when there is data to read in an empty buffer + * @param instance Pointer to a SubGhzTxRxWorker instance * @param callback SubGhzTxRxWorkerCallbackHaveRead callback * @param context */ @@ -48,34 +48,34 @@ void subghz_tx_rx_worker_set_callback_have_read( SubGhzTxRxWorkerCallbackHaveRead callback, void* context); -/** Allocate SubGhzTxRxWorker - * - * @return SubGhzTxRxWorker* +/** + * Allocate SubGhzTxRxWorker + * @return SubGhzTxRxWorker* Pointer to a SubGhzTxRxWorker instance */ SubGhzTxRxWorker* subghz_tx_rx_worker_alloc(); -/** Free SubGhzTxRxWorker - * - * @param instance SubGhzTxRxWorker instance +/** + * Free SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance */ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance); -/** Start SubGhzTxRxWorker - * - * @param instance SubGhzTxRxWorker instance +/** + * Start SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance * @return bool - true if ok */ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency); -/** Stop SubGhzTxRxWorker - * - * @param instance SubGhzTxRxWorker instance +/** + * Stop SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance */ void subghz_tx_rx_worker_stop(SubGhzTxRxWorker* instance); -/** Check if worker is running - * - * @param instance SubGhzTxRxWorker instance +/** + * Check if worker is running + * @param instance Pointer to a SubGhzTxRxWorker instance * @return bool - true if running */ bool subghz_tx_rx_worker_is_running(SubGhzTxRxWorker* instance); diff --git a/lib/subghz/subghz_worker.h b/lib/subghz/subghz_worker.h index cc8569fc..bb303165 100644 --- a/lib/subghz/subghz_worker.h +++ b/lib/subghz/subghz_worker.h @@ -10,55 +10,55 @@ typedef void (*SubGhzWorkerPairCallback)(void* context, bool level, uint32_t dur void subghz_worker_rx_callback(bool level, uint32_t duration, void* context); -/** Allocate SubGhzWorker - * - * @return SubGhzWorker* +/** + * Allocate SubGhzWorker. + * @return SubGhzWorker* Pointer to a SubGhzWorker instance */ SubGhzWorker* subghz_worker_alloc(); -/** Free SubGhzWorker - * - * @param instance SubGhzWorker instance +/** + * Free SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance */ void subghz_worker_free(SubGhzWorker* instance); -/** Overrun callback SubGhzWorker - * - * @param instance SubGhzWorker instance +/** + * Overrun callback SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance * @param callback SubGhzWorkerOverrunCallback callback */ void subghz_worker_set_overrun_callback( SubGhzWorker* instance, SubGhzWorkerOverrunCallback callback); -/** Pair callback SubGhzWorker - * - * @param instance SubGhzWorker instance +/** + * Pair callback SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance * @param callback SubGhzWorkerOverrunCallback callback */ void subghz_worker_set_pair_callback(SubGhzWorker* instance, SubGhzWorkerPairCallback callback); -/** Context callback SubGhzWorker - * - * @param instance SubGhzWorker instance +/** + * Context callback SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance * @param context */ void subghz_worker_set_context(SubGhzWorker* instance, void* context); -/** Start SubGhzWorker - * - * @param instance SubGhzWorker instance +/** + * Start SubGhzWorker. + * @param instance Pointer to a SubGhzWorker instance */ void subghz_worker_start(SubGhzWorker* instance); /** Stop SubGhzWorker - * - * @param instance SubGhzWorker instance + * @param instance Pointer to a SubGhzWorker instance */ void subghz_worker_stop(SubGhzWorker* instance); -/** Check if worker is running - * @param instance SubGhzWorker instance +/** + * Check if worker is running. + * @param instance Pointer to a SubGhzWorker instance * @return bool - true if running */ bool subghz_worker_is_running(SubGhzWorker* instance); diff --git a/lib/subghz/transmitter.h b/lib/subghz/transmitter.h index 80ecc40b..205a3eee 100644 --- a/lib/subghz/transmitter.h +++ b/lib/subghz/transmitter.h @@ -11,13 +11,37 @@ struct SubGhzTransmitter { SubGhzProtocolEncoderBase* protocol_instance; }; +/** + * Allocate and init SubGhzTransmitter. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzTransmitter* pointer to a SubGhzTransmitter instance + */ SubGhzTransmitter* subghz_transmitter_alloc_init(SubGhzEnvironment* environment, const char* protocol_name); +/** + * Free SubGhzTransmitter. + * @param instance Pointer to a SubGhzTransmitter instance + */ void subghz_transmitter_free(SubGhzTransmitter* instance); +/** + * Forced transmission stop. + * @param instance Pointer to a SubGhzTransmitter instance + */ bool subghz_transmitter_stop(SubGhzTransmitter* instance); +/** + * Deserialize and generating an upload to send. + * @param instance Pointer to a SubGhzTransmitter instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format); +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzTransmitter instance + * @return LevelDuration + */ LevelDuration subghz_transmitter_yield(void* context); From eed49bf863db42257c7f23615da6a821e55fb207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 17 Mar 2022 16:44:54 +0700 Subject: [PATCH 11/13] [FL-2335] Gui, Rpc: multisession, asynchronous screen streaming with adaptive frame rate (#1033) * Gui,Rpc: multisession, asynchronous screen streaming with adaptive frame rate * Fix compact build, add missing aray initialization. --- applications/gui/gui.c | 48 +++++++++++++------- applications/gui/gui.h | 20 +++++--- applications/gui/gui_i.h | 15 +++++- applications/rpc/rpc.c | 9 +++- applications/rpc/rpc_gui.c | 93 +++++++++++++++++++++++++++++--------- applications/rpc/rpc_i.h | 3 ++ 6 files changed, 139 insertions(+), 49 deletions(-) diff --git a/applications/gui/gui.c b/applications/gui/gui.c index 06733543..69f0e248 100644 --- a/applications/gui/gui.c +++ b/applications/gui/gui.c @@ -211,12 +211,11 @@ void gui_redraw(Gui* gui) { } canvas_commit(gui->canvas); - if(gui->canvas_callback) { - gui->canvas_callback( - canvas_get_buffer(gui->canvas), - canvas_get_buffer_size(gui->canvas), - gui->canvas_callback_context); - } + for + M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) { + p->callback( + canvas_get_buffer(gui->canvas), canvas_get_buffer_size(gui->canvas), p->context); + } gui_unlock(gui); } @@ -396,24 +395,36 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { gui_unlock(gui); } -void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { +void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - gui_lock(gui); - gui->canvas_callback = callback; - gui->canvas_callback_context = context; - gui_unlock(gui); - if(callback != NULL) { - gui_update(gui); - } + const CanvasCallbackPair p = {callback, context}; + + gui_lock(gui); + + furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 0); + CanvasCallbackPairArray_push_back(gui->canvas_callback_pair, p); + + gui_unlock(gui); + gui_update(gui); } -GuiCanvasCommitCallback gui_get_framebuffer_callback(Gui* gui) { +void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); + + const CanvasCallbackPair p = {callback, context}; + gui_lock(gui); - GuiCanvasCommitCallback callback = gui->canvas_callback; + + furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 1); + CanvasCallbackPairArray_remove_val(gui->canvas_callback_pair, p); + gui_unlock(gui); - return callback; +} + +size_t gui_get_framebuffer_size(Gui* gui) { + furi_assert(gui); + return canvas_get_buffer_size(gui->canvas); } void gui_set_lockdown(Gui* gui, bool lockdown) { @@ -437,9 +448,12 @@ Gui* gui_alloc() { } // Drawing canvas gui->canvas = canvas_init(); + CanvasCallbackPairArray_init(gui->canvas_callback_pair); + // Input gui->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); gui->input_events = furi_record_open("input_events"); + furi_check(gui->input_events); furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); diff --git a/applications/gui/gui.h b/applications/gui/gui.h index 39d81c79..e3235242 100644 --- a/applications/gui/gui.h +++ b/applications/gui/gui.h @@ -68,7 +68,7 @@ void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port); */ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); -/** Set gui canvas commit callback +/** Add gui canvas commit callback * * This callback will be called upon Canvas commit Callback dispatched from GUI * thread and is time critical @@ -77,16 +77,22 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); * @param callback GuiCanvasCommitCallback * @param context GuiCanvasCommitCallback context */ -void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); +void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); -/** Get gui canvas commit callback - * - * Can be used to check if some application is using framebufer +/** Remove gui canvas commit callback * * @param gui Gui instance - * @return GuiCanvasCommitCallback + * @param callback GuiCanvasCommitCallback + * @param context GuiCanvasCommitCallback context */ -GuiCanvasCommitCallback gui_get_framebuffer_callback(Gui* gui); +void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); + +/** Get gui canvas frame buffer size + * * + * @param gui Gui instance + * @return size_t size of frame buffer in bytes + */ +size_t gui_get_framebuffer_size(Gui* gui); /** Set lockdown mode * diff --git a/applications/gui/gui_i.h b/applications/gui/gui_i.h index 479b5592..ea26339d 100644 --- a/applications/gui/gui_i.h +++ b/applications/gui/gui_i.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "canvas.h" @@ -42,6 +43,17 @@ ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); +typedef struct { + GuiCanvasCommitCallback callback; + void* context; +} CanvasCallbackPair; + +ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); + +#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) + +ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); + /** Gui structure */ struct Gui { // Thread and lock @@ -52,8 +64,7 @@ struct Gui { bool lockdown; ViewPortArray_t layers[GuiLayerMAX]; Canvas* canvas; - GuiCanvasCommitCallback canvas_callback; - void* canvas_callback_context; + CanvasCallbackPairArray_t canvas_callback_pair; // Input osMessageQueueId_t input_queue; diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index b67d036b..efd5ed87 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -623,7 +623,7 @@ RpcSession* rpc_session_open(Rpc* rpc) { rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); session->thread = furi_thread_alloc(); - furi_thread_set_name(session->thread, "RPC Session"); + furi_thread_set_name(session->thread, "RpcSessionWorker"); furi_thread_set_stack_size(session->thread, 2048); furi_thread_set_context(session->thread, session); furi_thread_set_callback(session->thread, rpc_session_worker); @@ -666,9 +666,10 @@ void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* han RpcHandlerDict_set_at(session->handlers, message_tag, *handler); } -void rpc_send_and_release(RpcSession* session, PB_Main* message) { +void rpc_send(RpcSession* session, PB_Main* message) { furi_assert(session); furi_assert(message); + pb_ostream_t ostream = PB_OSTREAM_SIZING; #if SRV_RPC_DEBUG @@ -695,6 +696,10 @@ void rpc_send_and_release(RpcSession* session, PB_Main* message) { osMutexRelease(session->callbacks_mutex); free(buffer); +} + +void rpc_send_and_release(RpcSession* session, PB_Main* message) { + rpc_send(session, message); pb_release(&PB_Main_msg, message); } diff --git a/applications/rpc/rpc_gui.c b/applications/rpc/rpc_gui.c index 1e4b0fe2..3a4e21f0 100644 --- a/applications/rpc/rpc_gui.c +++ b/applications/rpc/rpc_gui.c @@ -5,11 +5,25 @@ #define TAG "RpcGui" +typedef enum { + RpcGuiWorkerFlagTransmit = (1 << 0), + RpcGuiWorkerFlagExit = (1 << 1), +} RpcGuiWorkerFlag; + +#define RpcGuiWorkerFlagAny (RpcGuiWorkerFlagTransmit | RpcGuiWorkerFlagExit) + typedef struct { RpcSession* session; Gui* gui; + + // Receive part ViewPort* virtual_display_view_port; uint8_t* virtual_display_buffer; + + // Transmit + PB_Main* transmit_frame; + FuriThread* transmit_thread; + bool virtual_display_not_empty; bool is_streaming; } RpcGuiSystem; @@ -17,25 +31,35 @@ typedef struct { static void rpc_system_gui_screen_stream_frame_callback(uint8_t* data, size_t size, void* context) { furi_assert(data); - furi_assert(size == 1024); furi_assert(context); RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context; - RpcSession* session = rpc_gui->session; + uint8_t* buffer = rpc_gui->transmit_frame->content.gui_screen_frame.data->bytes; - PB_Main* frame = malloc(sizeof(PB_Main)); + furi_assert(size == rpc_gui->transmit_frame->content.gui_screen_frame.data->size); - frame->which_content = PB_Main_gui_screen_frame_tag; - frame->command_status = PB_CommandStatus_OK; - frame->content.gui_screen_frame.data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size)); - uint8_t* buffer = frame->content.gui_screen_frame.data->bytes; - uint16_t* frame_size_msg = &frame->content.gui_screen_frame.data->size; - *frame_size_msg = size; memcpy(buffer, data, size); - rpc_send_and_release(session, frame); + osThreadFlagsSet( + furi_thread_get_thread_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagTransmit); +} - free(frame); +static int32_t rpc_system_gui_screen_stream_frame_transmit_thread(void* context) { + furi_assert(context); + + RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context; + + while(true) { + uint32_t flags = osThreadFlagsWait(RpcGuiWorkerFlagAny, osFlagsWaitAny, osWaitForever); + if(flags & RpcGuiWorkerFlagTransmit) { + rpc_send(rpc_gui->session, rpc_gui->transmit_frame); + } + if(flags & RpcGuiWorkerFlagExit) { + break; + } + } + + return 0; } static void rpc_system_gui_start_screen_stream_process(const PB_Main* request, void* context) { @@ -45,15 +69,30 @@ static void rpc_system_gui_start_screen_stream_process(const PB_Main* request, v RpcSession* session = rpc_gui->session; furi_assert(session); + furi_assert(!rpc_gui->is_streaming); - if(gui_get_framebuffer_callback(rpc_gui->gui) == NULL) { - rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); - rpc_gui->is_streaming = true; - gui_set_framebuffer_callback( - rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); - } else { - rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_ERROR_BUSY); - } + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); + + rpc_gui->is_streaming = true; + size_t framebuffer_size = gui_get_framebuffer_size(rpc_gui->gui); + // Reusable Frame + rpc_gui->transmit_frame = malloc(sizeof(PB_Main)); + rpc_gui->transmit_frame->which_content = PB_Main_gui_screen_frame_tag; + rpc_gui->transmit_frame->command_status = PB_CommandStatus_OK; + rpc_gui->transmit_frame->content.gui_screen_frame.data = + malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(framebuffer_size)); + rpc_gui->transmit_frame->content.gui_screen_frame.data->size = framebuffer_size; + // Transmission thread for async TX + rpc_gui->transmit_thread = furi_thread_alloc(); + furi_thread_set_name(rpc_gui->transmit_thread, "GuiRpcWorker"); + furi_thread_set_callback( + rpc_gui->transmit_thread, rpc_system_gui_screen_stream_frame_transmit_thread); + furi_thread_set_context(rpc_gui->transmit_thread, rpc_gui); + furi_thread_set_stack_size(rpc_gui->transmit_thread, 1024); + furi_thread_start(rpc_gui->transmit_thread); + // GUI framebuffer callback + gui_add_framebuffer_callback( + rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); } static void rpc_system_gui_stop_screen_stream_process(const PB_Main* request, void* context) { @@ -66,7 +105,18 @@ static void rpc_system_gui_stop_screen_stream_process(const PB_Main* request, vo if(rpc_gui->is_streaming) { rpc_gui->is_streaming = false; - gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + // Remove GUI framebuffer callback + gui_remove_framebuffer_callback( + rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); + // Stop and release worker thread + osThreadFlagsSet( + furi_thread_get_thread_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagExit); + furi_thread_join(rpc_gui->transmit_thread); + furi_thread_free(rpc_gui->transmit_thread); + // Release frame + pb_release(&PB_Main_msg, rpc_gui->transmit_frame); + free(rpc_gui->transmit_frame); + rpc_gui->transmit_frame = NULL; } rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); @@ -296,7 +346,8 @@ void rpc_system_gui_free(void* context) { } if(rpc_gui->is_streaming) { - gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + gui_remove_framebuffer_callback( + rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); } furi_record_close("gui"); free(rpc_gui); diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index a8ffff28..f84cc991 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -17,7 +17,10 @@ typedef struct { void* context; } RpcHandler; +void rpc_send(RpcSession* session, PB_Main* main_message); + void rpc_send_and_release(RpcSession* session, PB_Main* main_message); + void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status); void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* handler); From c098292a53e795b393708dcde2a4e2468c1da3c1 Mon Sep 17 00:00:00 2001 From: MuddledBox <101580720+MuddledBox@users.noreply.github.com> Date: Thu, 17 Mar 2022 04:49:36 -0500 Subject: [PATCH 12/13] CLI Enhancement: Added Debug (#1030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit for Debug in CLI Added Debug as option in CLI * Update cli_commands.c Whoops, swapped the printf, now working as expected! * Fixing Menu Update Now menu should update Co-authored-by: SG Co-authored-by: あく --- applications/cli/cli_commands.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/applications/cli/cli_commands.c b/applications/cli/cli_commands.c index 2e6078f2..53595a08 100644 --- a/applications/cli/cli_commands.c +++ b/applications/cli/cli_commands.c @@ -5,6 +5,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -146,6 +147,20 @@ void cli_command_vibro(Cli* cli, string_t args, void* context) { } } +void cli_command_debug(Cli* cli, string_t args, void* context) { + if(!string_cmp(args, "0")) { + furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); + loader_update_menu(); + printf("Debug disabled."); + } else if(!string_cmp(args, "1")) { + furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); + loader_update_menu(); + printf("Debug enabled."); + } else { + cli_print_usage("debug", "<1|0>", string_get_cstr(args)); + } +} + void cli_command_led(Cli* cli, string_t args, void* context) { // Get first word as light name NotificationMessage notification_led_message; @@ -348,6 +363,7 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); + cli_add_command(cli, "debug", CliCommandFlagDefault, cli_command_debug, NULL); cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL); cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); From b86a400a82d0703e464a29bc5e3dd25ae46140fa Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 17 Mar 2022 13:55:53 +0400 Subject: [PATCH 13/13] SubGhz: add frequencies and add DoorHan 315.00 (#1028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add frequencies and add DoorHan 315.00 * SubGhz: fix syntax Co-authored-by: あく --- .../subghz/scenes/subghz_scene_set_type.c | 37 +++++++++++++++++-- applications/subghz/subghz.c | 31 ++++++++++++++-- applications/subghz/subghz_i.h | 1 + 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index 79d6bcf6..32aecc14 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -17,7 +17,8 @@ enum SubmenuIndex { SubmenuIndexNeroSketch, SubmenuIndexNeroRadio, SubmenuIndexGateTX, - SubmenuIndexDoorHan, + SubmenuIndexDoorHan_315_00, + SubmenuIndexDoorHan_433_92, }; bool subghz_scene_set_type_submenu_gen_data_protocol( @@ -122,10 +123,16 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexGateTX, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "DoorHan_315", + SubmenuIndexDoorHan_315_00, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "DoorHan_433", - SubmenuIndexDoorHan, + SubmenuIndexDoorHan_433_92, subghz_scene_set_type_submenu_callback, subghz); @@ -195,7 +202,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; - case SubmenuIndexDoorHan: + case SubmenuIndexDoorHan_433_92: subghz->txrx->transmitter = subghz_transmitter_alloc_init(subghz->txrx->environment, "KeeLoq"); if(subghz->txrx->transmitter) { @@ -219,6 +226,30 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexDoorHan_315_00: + subghz->txrx->transmitter = + subghz_transmitter_alloc_init(subghz->txrx->environment, "KeeLoq"); + if(subghz->txrx->transmitter) { + subghz_protocol_keeloq_create_data( + subghz->txrx->transmitter->protocol_instance, + subghz->txrx->fff_data, + key & 0x0FFFFFFF, + 0x2, + 0x0003, + "DoorHan", + subghz_frequencies[subghz_frequencies_315_00], + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + if(!generated_protocol) { + string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; default: return false; break; diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index 7a140685..0fb2f6d9 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -4,33 +4,57 @@ #include const char* const subghz_frequencies_text[] = { + + "300.00", + "303.88", + "304.25", "315.00", + "318.00", + + "390.00", + "418.00", "433.08", "433.42", "433.92", "434.42", + "434.78", + "438.90", + "868.35", "915.00", + "925.00", }; const uint32_t subghz_frequencies[] = { + /* 300 - 348 */ + 300000000, + 303875000, + 304250000, 315000000, + 318000000, /* 387 - 464 */ - + 390000000, + 418000000, 433075000, /* LPD433 first */ 433420000, 433920000, /* LPD433 mid */ 434420000, - /* 779 - 928 */ + 434775000, /* LPD433 last channels */ + 438900000, + /* 779 - 928 */ 868350000, 915000000, + 925000000, + }; const uint32_t subghz_hopper_frequencies[] = { 315000000, + 318000000, + 390000000, 433920000, 868350000, }; @@ -38,7 +62,8 @@ const uint32_t subghz_hopper_frequencies[] = { const uint32_t subghz_frequencies_count = sizeof(subghz_frequencies) / sizeof(uint32_t); const uint32_t subghz_hopper_frequencies_count = sizeof(subghz_hopper_frequencies) / sizeof(uint32_t); -const uint32_t subghz_frequencies_433_92 = 3; +const uint32_t subghz_frequencies_433_92 = 9; +const uint32_t subghz_frequencies_315_00 = 3; bool subghz_custom_event_callback(void* context, uint32_t event) { furi_assert(context); diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 98dced1c..efb3eafa 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -41,6 +41,7 @@ extern const uint32_t subghz_hopper_frequencies[]; extern const uint32_t subghz_frequencies_count; extern const uint32_t subghz_hopper_frequencies_count; extern const uint32_t subghz_frequencies_433_92; +extern const uint32_t subghz_frequencies_315_00; /** SubGhzNotification state */ typedef enum {