diff --git a/.gitattributes b/.gitattributes index 032c1bd1..176a458f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ * text=auto - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a448c0ab..e5c59cfa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,6 +103,32 @@ jobs: -o firmware/.obj/${TARGET}/full.hex -Intel done + - name: 'Generate full dfu file' + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/docker + with: + run: | + for TARGET in ${TARGETS} + do + hex2dfu \ + -i firmware/.obj/${TARGET}/full.hex \ + -o artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.dfu \ + -l "Flipper Zero $(echo $TARGET | tr a-z A-Z)" + done + + - name: 'Generate full json file' + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/docker + with: + run: | + for TARGET in ${TARGETS} + do + jq -s '.[0] * .[1]' \ + bootloader/.obj/${TARGET}/bootloader.json \ + firmware/.obj/${TARGET}/firmware.json \ + > artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.json + done + - name: 'Move upload files' if: ${{ !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/docker @@ -116,25 +142,16 @@ jobs: artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.bin mv bootloader/.obj/${TARGET}/bootloader.elf \ artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.elf + mv bootloader/.obj/${TARGET}/bootloader.json \ + artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.json mv firmware/.obj/${TARGET}/firmware.dfu \ artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.dfu mv firmware/.obj/${TARGET}/firmware.bin \ artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.bin mv firmware/.obj/${TARGET}/firmware.elf \ artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.elf - done - - - name: 'Generate full dfu file' - if: ${{ !github.event.pull_request.head.repo.fork }} - uses: ./.github/actions/docker - with: - run: | - for TARGET in ${TARGETS} - do - hex2dfu \ - -i firmware/.obj/${TARGET}/full.hex \ - -o artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.dfu \ - -l "Flipper Zero $(echo $TARGET | tr a-z A-Z)" + mv firmware/.obj/${TARGET}/firmware.json \ + artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.json done - name: 'Full flash asssembly: bootloader as base' diff --git a/Makefile b/Makefile index 0091c292..c077407c 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,10 @@ flash: bootloader_flash firmware_flash debug: $(MAKE) -C firmware -j$(NPROCS) debug +.PHONY: blackmagic +blackmagic: + $(MAKE) -C firmware -j$(NPROCS) blackmagic + .PHONY: wipe wipe: $(PROJECT_ROOT)/scripts/flash.py wipe @@ -49,12 +53,16 @@ firmware_clean: .PHONY: bootloader_flash bootloader_flash: +ifeq ($(FORCE), 1) rm $(PROJECT_ROOT)/bootloader/.obj/f*/flash || true +endif $(MAKE) -C $(PROJECT_ROOT)/bootloader -j$(NPROCS) flash .PHONY: firmware_flash firmware_flash: +ifeq ($(FORCE), 1) rm $(PROJECT_ROOT)/firmware/.obj/f*/flash || true +endif $(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) flash .PHONY: flash_radio @@ -73,8 +81,16 @@ flash_radio_fus: @echo "================ JUST DON'T ================" @echo -.PHONY: +.PHONY: flash_radio_fus_please_i_m_not_going_to_complain flash_radio_fus_please_i_m_not_going_to_complain: $(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin $(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin $(PROJECT_ROOT)/scripts/ob.py set + +FORMAT_SOURCES = $(shell find applications bootloader core -iname "*.h" -o -iname "*.c" -o -iname "*.cpp") + +.PHONY: format +format: + @echo "Formatting sources with clang-format" + @clang-format -style=file -i $(FORMAT_SOURCES) + diff --git a/applications/about/about.c b/applications/about/about.c index a24d8ee9..8d739b79 100644 --- a/applications/about/about.c +++ b/applications/about/about.c @@ -129,11 +129,11 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* return result; } -static DialogMessageButton boot_version_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton bootloader_version_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; string_t buffer; string_init(buffer); - const Version* ver = furi_hal_version_get_boot_version(); + const Version* ver = furi_hal_version_get_bootloader_version(); if(!ver) { string_cat_printf(buffer, "No info\n"); @@ -167,7 +167,7 @@ const AboutDialogScreen about_screens[] = { icon2_screen, hw_version_screen, fw_version_screen, - boot_version_screen}; + bootloader_version_screen}; const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen); diff --git a/applications/applications.c b/applications/applications.c index 9b269461..7cc105bb 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -65,55 +65,55 @@ extern int32_t power_settings_app(void* p); const FlipperApplication FLIPPER_SERVICES[] = { /* Services */ #ifdef SRV_RPC - {.app = rpc_srv, .name = "RPC", .stack_size = 1024 * 4, .icon = NULL}, + {.app = rpc_srv, .name = "RpcSrv", .stack_size = 1024 * 4, .icon = NULL}, #endif #ifdef SRV_BT - {.app = bt_srv, .name = "BT", .stack_size = 1024, .icon = NULL}, + {.app = bt_srv, .name = "BtSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_CLI - {.app = cli_srv, .name = "Cli", .stack_size = 4096, .icon = NULL}, + {.app = cli_srv, .name = "CliSrv", .stack_size = 4096, .icon = NULL}, #endif #ifdef SRV_DIALOGS - {.app = dialogs_srv, .name = "Dialogs", .stack_size = 1024, .icon = NULL}, + {.app = dialogs_srv, .name = "DialogsSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_DOLPHIN - {.app = dolphin_srv, .name = "Dolphin", .stack_size = 1024, .icon = NULL}, + {.app = dolphin_srv, .name = "DolphinSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_DESKTOP - {.app = desktop_srv, .name = "Desktop", .stack_size = 1024, .icon = NULL}, + {.app = desktop_srv, .name = "DesktopSrv", .stack_size = 2048, .icon = NULL}, #endif #ifdef SRV_GUI - {.app = gui_srv, .name = "Gui", .stack_size = 8192, .icon = NULL}, + {.app = gui_srv, .name = "GuiSrv", .stack_size = 2048, .icon = NULL}, #endif #ifdef SRV_INPUT - {.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL}, + {.app = input_srv, .name = "InputSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_LOADER - {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, + {.app = loader_srv, .name = "LoaderSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_NOTIFICATION - {.app = notification_srv, .name = "Notification", .stack_size = 1024, .icon = NULL}, + {.app = notification_srv, .name = "NotificationSrv", .stack_size = 1536, .icon = NULL}, #endif #ifdef SRV_POWER - {.app = power_srv, .name = "Power", .stack_size = 1024, .icon = NULL}, + {.app = power_srv, .name = "PowerSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_POWER_OBSERVER - {.app = power_observer_srv, .name = "PowerObserver", .stack_size = 1024, .icon = NULL}, + {.app = power_observer_srv, .name = "PowerAuditSrv", .stack_size = 1024, .icon = NULL}, #endif #ifdef SRV_STORAGE - {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, + {.app = storage_srv, .name = "StorageSrv", .stack_size = 3072, .icon = NULL}, #endif }; diff --git a/applications/applications.mk b/applications/applications.mk index 1168ee82..4f0a5a67 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -231,6 +231,10 @@ endif SRV_RPC ?= 0 ifeq ($(SRV_RPC), 1) CFLAGS += -DSRV_RPC +ifeq ($(SRV_RPC_DEBUG), 1) +CFLAGS += -DSRV_RPC_DEBUG +endif +SRV_CLI = 1 endif SRV_LOADER ?= 0 diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index 7e302c4e..d52475cc 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -104,7 +104,7 @@ void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d) { void archive_file_array_rm_all(ArchiveBrowserView* browser) { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - files_array_clean(model->files); + files_array_reset(model->files); return false; }); } diff --git a/applications/archive/helpers/archive_favorites.c b/applications/archive/helpers/archive_favorites.c index c4608480..1e581502 100644 --- a/applications/archive/helpers/archive_favorites.c +++ b/applications/archive/helpers/archive_favorites.c @@ -53,7 +53,7 @@ bool archive_favorites_read(void* context) { } archive_add_item(browser, &file_info, string_get_cstr(buffer)); - string_clean(buffer); + string_reset(buffer); } } string_clear(buffer); diff --git a/applications/archive/helpers/archive_files.c b/applications/archive/helpers/archive_files.c index fc3508e8..83b67a5b 100644 --- a/applications/archive/helpers/archive_files.c +++ b/applications/archive/helpers/archive_files.c @@ -1,6 +1,8 @@ #include "archive_files.h" #include "archive_browser.h" +#define TAG "Archive" + bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { furi_assert(file_info); furi_assert(tab_ext); @@ -147,11 +149,11 @@ void archive_file_append(const char* path, const char* format, ...) { FileWorker* file_worker = file_worker_alloc(false); if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { - FURI_LOG_E("Archive", "Append open error"); + FURI_LOG_E(TAG, "Append open error"); } if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { - FURI_LOG_E("Archive", "Append write error"); + FURI_LOG_E(TAG, "Append write error"); } file_worker_close(file_worker); diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index b85139fd..64966570 100755 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -1,7 +1,8 @@ #include "bt_i.h" #include "battery_service.h" +#include "bt_keys_storage.h" -#define BT_SERVICE_TAG "BT" +#define TAG "BtSrv" static void bt_draw_statusbar_callback(Canvas* canvas, void* context) { furi_assert(context); @@ -69,8 +70,8 @@ Bt* bt_alloc() { // Power bt->power = furi_record_open("power"); - PubSub* power_pubsub = power_get_pubsub(bt->power); - subscribe_pubsub(power_pubsub, bt_battery_level_changed_callback, bt); + FuriPubSub* power_pubsub = power_get_pubsub(bt->power); + furi_pubsub_subscribe(power_pubsub, bt_battery_level_changed_callback, bt); // RPC bt->rpc = furi_record_open("rpc"); @@ -80,14 +81,15 @@ Bt* bt_alloc() { } // Called from GAP thread from Serial service -static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) { +static uint16_t bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) { furi_assert(context); Bt* bt = context; - size_t bytes_processed = rpc_feed_bytes(bt->rpc_session, data, size, 1000); + size_t bytes_processed = rpc_session_feed(bt->rpc_session, data, size, 1000); if(bytes_processed != size) { - FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); + FURI_LOG_E(TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); } + return rpc_session_get_available_size(bt->rpc_session); } // Called from GAP thread from Serial service @@ -117,6 +119,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt } } +static void bt_rpc_buffer_is_empty_callback(void* context) { + furi_assert(context); + furi_hal_bt_notify_buffer_is_empty(); +} + // Called from GAP thread static void bt_on_gap_event_callback(BleEvent event, void* context) { furi_assert(context); @@ -128,11 +135,13 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); // Open RPC session - FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection"); - bt->rpc_session = rpc_open_session(bt->rpc); - rpc_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback, bt); + FURI_LOG_I(TAG, "Open RPC connection"); + bt->rpc_session = rpc_session_open(bt->rpc); + rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); + rpc_session_set_buffer_is_empty_callback(bt->rpc_session, bt_rpc_buffer_is_empty_callback); + rpc_session_set_context(bt->rpc_session, bt); furi_hal_bt_set_data_event_callbacks( - bt_on_data_received_callback, bt_on_data_sent_callback, bt); + RPC_BUFFER_SIZE, bt_on_data_received_callback, bt_on_data_sent_callback, bt); // Update battery level PowerInfo info; power_get_info(bt->power, &info); @@ -140,9 +149,9 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { message.data.battery_level = info.charge; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); } else if(event.type == BleEventTypeDisconnected) { - FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection"); + FURI_LOG_I(TAG, "Close RPC connection"); if(bt->rpc_session) { - rpc_close_session(bt->rpc_session); + rpc_session_close(bt->rpc_session); bt->rpc_session = NULL; } } else if(event.type == BleEventTypeStartAdvertising) { @@ -160,6 +169,14 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { } } +static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { + furi_assert(context); + Bt* bt = context; + FURI_LOG_I(TAG, "Changed addr start: %08lX, size changed: %d", addr, size); + BtMessage message = {.type = BtMessageTypeKeysStorageUpdated}; + furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); +} + static void bt_statusbar_update(Bt* bt) { if(bt->status == BtStatusAdvertising) { view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_5x8)); @@ -176,19 +193,26 @@ int32_t bt_srv() { Bt* bt = bt_alloc(); furi_record_create("bt", bt); - if(!furi_hal_bt_wait_startup()) { - FURI_LOG_E(BT_SERVICE_TAG, "Core2 startup failed"); + // Read keys + if(!bt_load_key_storage(bt)) { + FURI_LOG_W(TAG, "Failed to load saved bonding keys"); + } + // Start 2nd core + if(!furi_hal_bt_start_core2()) { + FURI_LOG_E(TAG, "Core2 startup failed"); } else { view_port_enabled_set(bt->statusbar_view_port, true); if(furi_hal_bt_init_app(bt_on_gap_event_callback, bt)) { - FURI_LOG_I(BT_SERVICE_TAG, "BLE stack started"); + FURI_LOG_I(TAG, "BLE stack started"); if(bt->bt_settings.enabled) { furi_hal_bt_start_advertising(); } } else { - FURI_LOG_E(BT_SERVICE_TAG, "BT App start failed"); + FURI_LOG_E(TAG, "BT App start failed"); } } + furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); + // Update statusbar bt_statusbar_update(bt); @@ -206,6 +230,8 @@ int32_t bt_srv() { } else if(message.type == BtMessageTypePinCodeShow) { // Display PIN code bt_pin_code_show_event_handler(bt, message.data.pin_code); + } else if(message.type == BtMessageTypeKeysStorageUpdated) { + bt_save_key_storage(bt); } } return 0; diff --git a/applications/bt/bt_service/bt_i.h b/applications/bt/bt_service/bt_i.h index 0588378e..3921a4c5 100644 --- a/applications/bt/bt_service/bt_i.h +++ b/applications/bt/bt_service/bt_i.h @@ -25,6 +25,7 @@ typedef enum { BtMessageTypeUpdateStatusbar, BtMessageTypeUpdateBatteryLevel, BtMessageTypePinCodeShow, + BtMessageTypeKeysStorageUpdated, } BtMessageType; typedef union { @@ -38,6 +39,8 @@ typedef struct { } BtMessage; struct Bt { + uint8_t* bt_keys_addr_start; + uint16_t bt_keys_size; BtSettings bt_settings; BtStatus status; osMessageQueueId_t message_queue; diff --git a/applications/bt/bt_service/bt_keys_storage.c b/applications/bt/bt_service/bt_keys_storage.c new file mode 100644 index 00000000..25c74882 --- /dev/null +++ b/applications/bt/bt_service/bt_keys_storage.c @@ -0,0 +1,41 @@ +#include "bt_keys_storage.h" +#include +#include + +#define BT_KEYS_STORAGE_TAG "bt keys storage" +#define BT_KEYS_STORAGE_PATH "/int/bt.keys" + +bool bt_load_key_storage(Bt* bt) { + furi_assert(bt); + + bool file_loaded = false; + furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size); + + FileWorker* file_worker = file_worker_alloc(true); + if(file_worker_open(file_worker, BT_KEYS_STORAGE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + furi_hal_bt_nvm_sram_sem_acquire(); + if(file_worker_read(file_worker, bt->bt_keys_addr_start, bt->bt_keys_size)) { + file_loaded = true; + } + furi_hal_bt_nvm_sram_sem_release(); + } + file_worker_free(file_worker); + return file_loaded; +} + +bool bt_save_key_storage(Bt* bt) { + furi_assert(bt); + furi_assert(bt->bt_keys_addr_start); + + bool file_saved = false; + FileWorker* file_worker = file_worker_alloc(true); + if(file_worker_open(file_worker, BT_KEYS_STORAGE_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { + furi_hal_bt_nvm_sram_sem_acquire(); + if(file_worker_write(file_worker, bt->bt_keys_addr_start, bt->bt_keys_size)) { + file_saved = true; + } + furi_hal_bt_nvm_sram_sem_release(); + } + file_worker_free(file_worker); + return file_saved; +} diff --git a/applications/bt/bt_service/bt_keys_storage.h b/applications/bt/bt_service/bt_keys_storage.h new file mode 100644 index 00000000..4b09d7f2 --- /dev/null +++ b/applications/bt/bt_service/bt_keys_storage.h @@ -0,0 +1,7 @@ +#pragma once + +#include "bt_i.h" + +bool bt_load_key_storage(Bt* bt); + +bool bt_save_key_storage(Bt* bt); diff --git a/applications/bt/bt_settings.c b/applications/bt/bt_settings.c index bd6399a9..9c154c47 100644 --- a/applications/bt/bt_settings.c +++ b/applications/bt/bt_settings.c @@ -2,7 +2,7 @@ #include #include -#define BT_SETTINGS_TAG "bt settings" +#define TAG "BtSettings" #define BT_SETTINGS_PATH "/int/bt.settings" bool bt_settings_load(BtSettings* bt_settings) { @@ -10,7 +10,7 @@ bool bt_settings_load(BtSettings* bt_settings) { bool file_loaded = false; BtSettings settings = {}; - FURI_LOG_I(BT_SETTINGS_TAG, "Loading settings from \"%s\"", BT_SETTINGS_PATH); + FURI_LOG_I(TAG, "Loading settings from \"%s\"", BT_SETTINGS_PATH); FileWorker* file_worker = file_worker_alloc(true); if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { if(file_worker_read(file_worker, &settings, sizeof(settings))) { @@ -20,16 +20,16 @@ bool bt_settings_load(BtSettings* bt_settings) { file_worker_free(file_worker); if(file_loaded) { - FURI_LOG_I(BT_SETTINGS_TAG, "Settings load success"); + FURI_LOG_I(TAG, "Settings load success"); if(settings.version != BT_SETTINGS_VERSION) { - FURI_LOG_E(BT_SETTINGS_TAG, "Settings version mismatch"); + FURI_LOG_E(TAG, "Settings version mismatch"); } else { osKernelLock(); *bt_settings = settings; osKernelUnlock(); } } else { - FURI_LOG_E(BT_SETTINGS_TAG, "Settings load failed"); + FURI_LOG_E(TAG, "Settings load failed"); } return file_loaded; } @@ -41,7 +41,7 @@ bool bt_settings_save(BtSettings* bt_settings) { FileWorker* file_worker = file_worker_alloc(true); if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { if(file_worker_write(file_worker, bt_settings, sizeof(BtSettings))) { - FURI_LOG_I(BT_SETTINGS_TAG, "Settings saved to \"%s\"", BT_SETTINGS_PATH); + FURI_LOG_I(TAG, "Settings saved to \"%s\"", BT_SETTINGS_PATH); result = true; } } diff --git a/applications/cli/cli.c b/applications/cli/cli.c index d9fde866..7d60f7d1 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -263,7 +263,7 @@ static void cli_handle_autocomplete(Cli* cli) { cli->cursor_position = string_size(cli->line); } // Cleanup - string_clean(common); + string_clear(common); // Show prompt cli_prompt(cli); } diff --git a/applications/cli/cli_commands.c b/applications/cli/cli_commands.c index fd87625b..1a9f4a6d 100644 --- a/applications/cli/cli_commands.c +++ b/applications/cli/cli_commands.c @@ -56,13 +56,13 @@ static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_KEY_SLOTS][ENC */ void cli_command_device_info(Cli* cli, string_t args, void* context) { // Device Info version - printf("device_info_major : %d\r\n", 1); - printf("device_info_minor : %d\r\n", 0); + printf("device_info_major : %d\r\n", 2); + printf("device_info_minor : %d\r\n", 0); // Model name - printf("hardware_model : %s\r\n", furi_hal_version_get_model_name()); + printf("hardware_model : %s\r\n", furi_hal_version_get_model_name()); // Unique ID - printf("hardware_uid : "); + printf("hardware_uid : "); const uint8_t* uid = furi_hal_version_uid(); for(size_t i = 0; i < furi_hal_version_uid_size(); i++) { printf("%02X", uid[i]); @@ -70,69 +70,69 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) { printf("\r\n"); // OTP Revision - printf("hardware_otp_ver : %d\r\n", furi_hal_version_get_otp_version()); - printf("hardware_timestamp : %lu\r\n", furi_hal_version_get_hw_timestamp()); + printf("hardware_otp_ver : %d\r\n", furi_hal_version_get_otp_version()); + printf("hardware_timestamp : %lu\r\n", furi_hal_version_get_hw_timestamp()); // Board Revision - printf("hardware_ver : %d\r\n", furi_hal_version_get_hw_version()); - printf("hardware_target : %d\r\n", furi_hal_version_get_hw_target()); - printf("hardware_body : %d\r\n", furi_hal_version_get_hw_body()); - printf("hardware_connect : %d\r\n", furi_hal_version_get_hw_connect()); - printf("hardware_display : %d\r\n", furi_hal_version_get_hw_display()); + printf("hardware_ver : %d\r\n", furi_hal_version_get_hw_version()); + printf("hardware_target : %d\r\n", furi_hal_version_get_hw_target()); + printf("hardware_body : %d\r\n", furi_hal_version_get_hw_body()); + printf("hardware_connect : %d\r\n", furi_hal_version_get_hw_connect()); + printf("hardware_display : %d\r\n", furi_hal_version_get_hw_display()); // Board Personification - printf("hardware_color : %d\r\n", furi_hal_version_get_hw_color()); - printf("hardware_region : %d\r\n", furi_hal_version_get_hw_region()); + printf("hardware_color : %d\r\n", furi_hal_version_get_hw_color()); + printf("hardware_region : %d\r\n", furi_hal_version_get_hw_region()); const char* name = furi_hal_version_get_name_ptr(); if(name) { - printf("hardware_name : %s\r\n", name); + printf("hardware_name : %s\r\n", name); } // Bootloader Version - const Version* boot_version = furi_hal_version_get_boot_version(); - if(boot_version) { - printf("boot_commit : %s\r\n", version_get_githash(boot_version)); - printf("boot_branch : %s\r\n", version_get_gitbranch(boot_version)); - printf("boot_branch_num : %s\r\n", version_get_gitbranchnum(boot_version)); - printf("boot_version : %s\r\n", version_get_version(boot_version)); - printf("boot_build_date : %s\r\n", version_get_builddate(boot_version)); - printf("boot_target : %d\r\n", version_get_target(boot_version)); + const Version* bootloader_version = furi_hal_version_get_bootloader_version(); + if(bootloader_version) { + printf("bootloader_commit : %s\r\n", version_get_githash(bootloader_version)); + printf("bootloader_branch : %s\r\n", version_get_gitbranch(bootloader_version)); + printf("bootloader_branch_num : %s\r\n", version_get_gitbranchnum(bootloader_version)); + printf("bootloader_version : %s\r\n", version_get_version(bootloader_version)); + printf("bootloader_build_date : %s\r\n", version_get_builddate(bootloader_version)); + printf("bootloader_target : %d\r\n", version_get_target(bootloader_version)); } // Firmware version const Version* firmware_version = furi_hal_version_get_firmware_version(); if(firmware_version) { - printf("firmware_commit : %s\r\n", version_get_githash(firmware_version)); - printf("firmware_branch : %s\r\n", version_get_gitbranch(firmware_version)); - printf("firmware_branch_num : %s\r\n", version_get_gitbranchnum(firmware_version)); - printf("firmware_version : %s\r\n", version_get_version(firmware_version)); - printf("firmware_build_date : %s\r\n", version_get_builddate(firmware_version)); - printf("firmware_target : %d\r\n", version_get_target(firmware_version)); + printf("firmware_commit : %s\r\n", version_get_githash(firmware_version)); + printf("firmware_branch : %s\r\n", version_get_gitbranch(firmware_version)); + printf("firmware_branch_num : %s\r\n", version_get_gitbranchnum(firmware_version)); + printf("firmware_version : %s\r\n", version_get_version(firmware_version)); + printf("firmware_build_date : %s\r\n", version_get_builddate(firmware_version)); + printf("firmware_target : %d\r\n", version_get_target(firmware_version)); } WirelessFwInfo_t pWirelessInfo; if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) { - printf("radio_alive : true\r\n"); + printf("radio_alive : true\r\n"); // FUS Info - printf("radio_fus_major : %d\r\n", pWirelessInfo.FusVersionMajor); - printf("radio_fus_minor : %d\r\n", pWirelessInfo.FusVersionMinor); - printf("radio_fus_sub : %d\r\n", pWirelessInfo.FusVersionSub); - printf("radio_fus_sram2b : %dK\r\n", pWirelessInfo.FusMemorySizeSram2B); - printf("radio_fus_sram2a : %dK\r\n", pWirelessInfo.FusMemorySizeSram2A); - printf("radio_fus_flash : %dK\r\n", pWirelessInfo.FusMemorySizeFlash * 4); + printf("radio_fus_major : %d\r\n", pWirelessInfo.FusVersionMajor); + printf("radio_fus_minor : %d\r\n", pWirelessInfo.FusVersionMinor); + printf("radio_fus_sub : %d\r\n", pWirelessInfo.FusVersionSub); + printf("radio_fus_sram2b : %dK\r\n", pWirelessInfo.FusMemorySizeSram2B); + printf("radio_fus_sram2a : %dK\r\n", pWirelessInfo.FusMemorySizeSram2A); + printf("radio_fus_flash : %dK\r\n", pWirelessInfo.FusMemorySizeFlash * 4); // Stack Info - printf("radio_stack_type : %d\r\n", pWirelessInfo.StackType); - printf("radio_stack_major : %d\r\n", pWirelessInfo.VersionMajor); - printf("radio_stack_minor : %d\r\n", pWirelessInfo.VersionMinor); - printf("radio_stack_sub : %d\r\n", pWirelessInfo.VersionSub); - printf("radio_stack_branch : %d\r\n", pWirelessInfo.VersionBranch); - printf("radio_stack_release : %d\r\n", pWirelessInfo.VersionReleaseType); - printf("radio_stack_sram2b : %dK\r\n", pWirelessInfo.MemorySizeSram2B); - printf("radio_stack_sram2a : %dK\r\n", pWirelessInfo.MemorySizeSram2A); - printf("radio_stack_sram1 : %dK\r\n", pWirelessInfo.MemorySizeSram1); - printf("radio_stack_flash : %dK\r\n", pWirelessInfo.MemorySizeFlash * 4); + printf("radio_stack_type : %d\r\n", pWirelessInfo.StackType); + printf("radio_stack_major : %d\r\n", pWirelessInfo.VersionMajor); + printf("radio_stack_minor : %d\r\n", pWirelessInfo.VersionMinor); + printf("radio_stack_sub : %d\r\n", pWirelessInfo.VersionSub); + printf("radio_stack_branch : %d\r\n", pWirelessInfo.VersionBranch); + printf("radio_stack_release : %d\r\n", pWirelessInfo.VersionReleaseType); + printf("radio_stack_sram2b : %dK\r\n", pWirelessInfo.MemorySizeSram2B); + printf("radio_stack_sram2a : %dK\r\n", pWirelessInfo.MemorySizeSram2A); + printf("radio_stack_sram1 : %dK\r\n", pWirelessInfo.MemorySizeSram1); + printf("radio_stack_flash : %dK\r\n", pWirelessInfo.MemorySizeFlash * 4); // Mac address - printf("radio_ble_mac : "); + printf("radio_ble_mac : "); const uint8_t* ble_mac = furi_hal_version_get_ble_mac(); for(size_t i = 0; i < 6; i++) { printf("%02X", ble_mac[i]); @@ -154,12 +154,12 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) { furi_hal_crypto_store_unload_key(key_slot + 1); } } - printf("enclave_valid_keys : %d\r\n", enclave_valid_keys); + printf("enclave_valid_keys : %d\r\n", enclave_valid_keys); printf( - "enclave_valid : %s\r\n", + "enclave_valid : %s\r\n", (enclave_valid_keys == ENCLAVE_SIGNATURE_KEY_SLOTS) ? "true" : "false"); } else { - printf("radio_alive : false\r\n"); + printf("radio_alive : false\r\n"); } } @@ -350,7 +350,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { "PA4", "PA6", "PA7", -#ifdef DEBUG +#ifdef FURI_DEBUG "PA0", "PB7", "PB8", @@ -366,7 +366,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { {.port = GPIOA, .pin = LL_GPIO_PIN_4}, {.port = GPIOA, .pin = LL_GPIO_PIN_6}, {.port = GPIOA, .pin = LL_GPIO_PIN_7}, -#ifdef DEBUG +#ifdef FURI_DEBUG {.port = GPIOA, .pin = LL_GPIO_PIN_0}, // IR_RX (PA0) {.port = GPIOB, .pin = LL_GPIO_PIN_7}, // UART RX (PB7) {.port = GPIOB, .pin = LL_GPIO_PIN_8}, // SPEAKER (PB8) @@ -411,7 +411,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { LL_GPIO_SetPinOutputType(gpio[num].port, gpio[num].pin, LL_GPIO_OUTPUT_PUSHPULL); LL_GPIO_ResetOutputPin(gpio[num].port, gpio[num].pin); } else if(!string_cmp(args, "1")) { -#ifdef DEBUG +#ifdef FURI_DEBUG if(num == 8) { // PA0 printf( "Setting PA0 pin HIGH with TSOP connected can damage IR receiver. Are you sure you want to continue? (y/n)?\r\n"); diff --git a/applications/debug_tools/bad_usb.c b/applications/debug_tools/bad_usb.c index ad20e26e..c86ffbac 100644 --- a/applications/debug_tools/bad_usb.c +++ b/applications/debug_tools/bad_usb.c @@ -6,6 +6,9 @@ #include #include +#define TAG "BadUsb" +#define WORKER_TAG TAG "Worker" + typedef enum { EventTypeInput, EventTypeWorkerState, @@ -191,7 +194,7 @@ static bool ducky_parse_line(string_t line, BadUsbParams* app) { static void badusb_worker(void* context) { BadUsbParams* app = context; - FURI_LOG_I("BadUSB worker", "Init"); + FURI_LOG_I(WORKER_TAG, "Init"); File* script_file = storage_file_alloc(furi_record_open("storage")); BadUsbEvent evt; string_t line; @@ -203,7 +206,7 @@ static void badusb_worker(void* context) { uint32_t flags = osThreadFlagsWait(WorkerCmdStart | WorkerCmdStop, osFlagsWaitAny, osWaitForever); if(flags & WorkerCmdStart) { - FURI_LOG_I("BadUSB worker", "Start"); + FURI_LOG_I(WORKER_TAG, "Start"); do { ret = storage_file_read(script_file, buffer, 16); for(uint16_t i = 0; i < ret; i++) { @@ -211,7 +214,7 @@ static void badusb_worker(void* context) { line_cnt++; if(ducky_parse_line(line, app) == false) { ret = 0; - FURI_LOG_E("BadUSB worker", "Unknown command at line %lu", line_cnt); + FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", line_cnt); evt.type = EventTypeWorkerState; evt.worker.state = WorkerStateScriptError; evt.worker.line = line_cnt; @@ -223,7 +226,7 @@ static void badusb_worker(void* context) { ret = 0; break; } - string_clean(line); + string_reset(line); } else { string_push_back(line, buffer[i]); } @@ -231,19 +234,19 @@ static void badusb_worker(void* context) { } while(ret > 0); } } else { - FURI_LOG_E("BadUSB worker", "Script file open error"); + FURI_LOG_E(WORKER_TAG, "Script file open error"); evt.type = EventTypeWorkerState; evt.worker.state = WorkerStateNoFile; osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); } - string_clean(line); + string_reset(line); string_clear(line); furi_hal_hid_kb_release_all(); storage_file_close(script_file); storage_file_free(script_file); - FURI_LOG_I("BadUSB worker", "End"); + FURI_LOG_I(WORKER_TAG, "End"); evt.type = EventTypeWorkerState; evt.worker.state = WorkerStateDone; osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); @@ -324,7 +327,7 @@ int32_t bad_usb_app(void* p) { } } } else if(event.type == EventTypeWorkerState) { - FURI_LOG_I("BadUSB app", "ev: %d", event.worker.state); + FURI_LOG_I(TAG, "ev: %d", event.worker.state); if(event.worker.state == WorkerStateDone) { worker_running = false; if(app_state == AppStateExit) diff --git a/applications/debug_tools/display_test/display_test.c b/applications/debug_tools/display_test/display_test.c index f2685a82..5a53562d 100644 --- a/applications/debug_tools/display_test/display_test.c +++ b/applications/debug_tools/display_test/display_test.c @@ -14,6 +14,8 @@ #include "view_display_test.h" +#define TAG "DisplayTest" + typedef struct { Gui* gui; ViewDispatcher* view_dispatcher; @@ -77,7 +79,7 @@ static uint32_t display_test_exit_callback(void* context) { static void display_test_reload_config(DisplayTest* instance) { FURI_LOG_I( - "DisplayTest", + TAG, "contrast: %d, regulation_ratio: %d, bias: %d", instance->config_contrast, instance->config_regulation_ratio, diff --git a/applications/debug_tools/keypad_test.c b/applications/debug_tools/keypad_test.c index e390ccd5..20f259aa 100644 --- a/applications/debug_tools/keypad_test.c +++ b/applications/debug_tools/keypad_test.c @@ -2,6 +2,8 @@ #include #include +#define TAG "KeypadTest" + typedef struct { bool press[5]; uint16_t up; @@ -80,7 +82,7 @@ int32_t keypad_test_app(void* p) { ValueMutex state_mutex; if(!init_mutex(&state_mutex, &_state, sizeof(KeypadTestState))) { - FURI_LOG_E("KeypadTest", "cannot create mutex"); + FURI_LOG_E(TAG, "cannot create mutex"); return 0; } @@ -101,7 +103,7 @@ int32_t keypad_test_app(void* p) { if(event_status == osOK) { if(event.type == EventTypeInput) { FURI_LOG_I( - "KeypadTest", + TAG, "key: %s type: %s", input_get_key_name(event.input.key), input_get_type_name(event.input.type)); diff --git a/applications/desktop/desktop.c b/applications/desktop/desktop.c index 4aa21453..5ae97951 100644 --- a/applications/desktop/desktop.c +++ b/applications/desktop/desktop.c @@ -1,4 +1,5 @@ #include "desktop_i.h" +#include static void desktop_lock_icon_callback(Canvas* canvas, void* context) { furi_assert(canvas); @@ -41,6 +42,7 @@ Desktop* desktop_alloc() { desktop->debug_view = desktop_debug_alloc(); desktop->first_start_view = desktop_first_start_alloc(); desktop->hw_mismatch_popup = popup_alloc(); + desktop->code_input = code_input_alloc(); view_dispatcher_add_view( desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); @@ -62,7 +64,8 @@ Desktop* desktop_alloc() { desktop->view_dispatcher, DesktopViewHwMismatch, popup_get_view(desktop->hw_mismatch_popup)); - + view_dispatcher_add_view( + desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input)); // Lock icon desktop->lock_viewport = view_port_alloc(); view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); @@ -82,6 +85,7 @@ void desktop_free(Desktop* desktop) { view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewPinSetup); view_dispatcher_free(desktop->view_dispatcher); scene_manager_free(desktop->scene_manager); @@ -92,6 +96,7 @@ void desktop_free(Desktop* desktop) { desktop_debug_free(desktop->debug_view); desktop_first_start_free(desktop->first_start_view); popup_free(desktop->hw_mismatch_popup); + code_input_free(desktop->code_input); furi_record_close("gui"); desktop->gui = NULL; @@ -113,9 +118,22 @@ static bool desktop_is_first_start() { int32_t desktop_srv(void* p) { Desktop* desktop = desktop_alloc(); + bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); + if(!loaded) { + furi_hal_lock_set(false); + memset(&desktop->settings, 0, sizeof(desktop->settings)); + SAVE_DESKTOP_SETTINGS(&desktop->settings); + } scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); + if(furi_hal_lock_get()) { + furi_hal_usb_disable(); + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); + } + if(desktop_is_first_start()) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); } diff --git a/applications/desktop/desktop_i.h b/applications/desktop/desktop_i.h index 62caff0f..0bf5f3da 100644 --- a/applications/desktop/desktop_i.h +++ b/applications/desktop/desktop_i.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ typedef enum { DesktopViewDebug, DesktopViewFirstStart, DesktopViewHwMismatch, + DesktopViewPinSetup, DesktopViewTotal, } DesktopViewEnum; @@ -46,7 +48,10 @@ struct Desktop { DesktopLockMenuView* lock_menu; DesktopLockedView* locked_view; DesktopDebugView* debug_view; + CodeInput* code_input; + DesktopSettings settings; + PinCode pincode_buffer; ViewPort* lock_viewport; }; diff --git a/applications/desktop/desktop_settings/desktop_settings.c b/applications/desktop/desktop_settings/desktop_settings.c deleted file mode 100644 index 5e170a09..00000000 --- a/applications/desktop/desktop_settings/desktop_settings.c +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include -#include "desktop_settings.h" - -#define DESKTOP_SETTINGS_TAG "Desktop settings" -#define DESKTOP_SETTINGS_PATH "/int/desktop.settings" - -bool desktop_settings_load(DesktopSettings* desktop_settings) { - furi_assert(desktop_settings); - bool file_loaded = false; - DesktopSettings settings = {}; - - FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Loading settings from \"%s\"", DESKTOP_SETTINGS_PATH); - FileWorker* file_worker = file_worker_alloc(true); - if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { - if(file_worker_read(file_worker, &settings, sizeof(settings))) { - file_loaded = true; - } - } - file_worker_free(file_worker); - - if(file_loaded) { - if(settings.version != DESKTOP_SETTINGS_VER) { - FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings version mismatch"); - } else { - osKernelLock(); - *desktop_settings = settings; - osKernelUnlock(); - } - } else { - FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings load failed"); - } - return file_loaded; -} - -bool desktop_settings_save(DesktopSettings* desktop_settings) { - furi_assert(desktop_settings); - bool result = false; - - FileWorker* file_worker = file_worker_alloc(true); - if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { - if(file_worker_write(file_worker, desktop_settings, sizeof(DesktopSettings))) { - FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Settings saved to \"%s\"", DESKTOP_SETTINGS_PATH); - result = true; - } - } - file_worker_free(file_worker); - return result; -} diff --git a/applications/desktop/desktop_settings/desktop_settings.h b/applications/desktop/desktop_settings/desktop_settings.h index a2aead82..27ded715 100644 --- a/applications/desktop/desktop_settings/desktop_settings.h +++ b/applications/desktop/desktop_settings/desktop_settings.h @@ -2,14 +2,35 @@ #include #include +#include -#define DESKTOP_SETTINGS_VER (0) +#define DESKTOP_SETTINGS_VER (1) +#define DESKTOP_SETTINGS_PATH "/int/desktop.settings" +#define DESKTOP_SETTINGS_MAGIC (0x17) +#define PIN_MAX_LENGTH 12 + +#define SAVE_DESKTOP_SETTINGS(x) \ + saved_struct_save( \ + DESKTOP_SETTINGS_PATH, \ + (x), \ + sizeof(DesktopSettings), \ + DESKTOP_SETTINGS_MAGIC, \ + DESKTOP_SETTINGS_VER) + +#define LOAD_DESKTOP_SETTINGS(x) \ + saved_struct_load( \ + DESKTOP_SETTINGS_PATH, \ + (x), \ + sizeof(DesktopSettings), \ + DESKTOP_SETTINGS_MAGIC, \ + DESKTOP_SETTINGS_VER) + +typedef struct { + uint8_t length; + uint8_t data[PIN_MAX_LENGTH]; +} PinCode; typedef struct { - uint8_t version; uint16_t favorite; + PinCode pincode; } DesktopSettings; - -bool desktop_settings_load(DesktopSettings* desktop_settings); - -bool desktop_settings_save(DesktopSettings* desktop_settings); diff --git a/applications/desktop/desktop_settings/desktop_settings_app.c b/applications/desktop/desktop_settings/desktop_settings_app.c index 3c7610f5..c8a5f2b7 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.c +++ b/applications/desktop/desktop_settings/desktop_settings_app.c @@ -15,9 +15,6 @@ static bool desktop_settings_back_event_callback(void* context) { DesktopSettingsApp* desktop_settings_app_alloc() { DesktopSettingsApp* app = furi_alloc(sizeof(DesktopSettingsApp)); - app->settings.version = DESKTOP_SETTINGS_VER; - desktop_settings_load(&app->settings); - app->gui = furi_record_open("gui"); app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app); @@ -33,10 +30,13 @@ DesktopSettingsApp* desktop_settings_app_alloc() { app->submenu = submenu_alloc(); view_dispatcher_add_view( - app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu)); + app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu)); + app->code_input = code_input_alloc(); view_dispatcher_add_view( - app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu)); + app->view_dispatcher, + DesktopSettingsAppViewPincodeInput, + code_input_get_view(app->code_input)); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); return app; @@ -45,9 +45,10 @@ DesktopSettingsApp* desktop_settings_app_alloc() { void desktop_settings_app_free(DesktopSettingsApp* app) { furi_assert(app); // Variable item list - view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain); - view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu); submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); + code_input_free(app->code_input); // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); @@ -58,8 +59,8 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { extern int32_t desktop_settings_app(void* p) { DesktopSettingsApp* app = desktop_settings_app_alloc(); + LOAD_DESKTOP_SETTINGS(&app->settings); view_dispatcher_run(app->view_dispatcher); - desktop_settings_save(&app->settings); desktop_settings_app_free(app); return 0; } diff --git a/applications/desktop/desktop_settings/desktop_settings_app.h b/applications/desktop/desktop_settings/desktop_settings_app.h index ba381cde..8ec8e892 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.h +++ b/applications/desktop/desktop_settings/desktop_settings_app.h @@ -6,20 +6,32 @@ #include #include #include +#include #include "desktop_settings.h" + #include "scenes/desktop_settings_scene.h" typedef enum { - DesktopSettingsAppViewMain, - DesktopSettingsAppViewFavorite, + CodeEventsSetPin, + CodeEventsChangePin, + CodeEventsDisablePin, +} CodeEventsEnum; + +typedef enum { + DesktopSettingsAppViewMenu, + DesktopSettingsAppViewPincodeInput, } DesktopSettingsAppView; typedef struct { DesktopSettings settings; + Gui* gui; SceneManager* scene_manager; ViewDispatcher* view_dispatcher; Submenu* submenu; + CodeInput* code_input; + + uint8_t menu_idx; } DesktopSettingsApp; diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h index a2abec0a..126873db 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h @@ -1,2 +1,4 @@ ADD_SCENE(desktop_settings, start, Start) ADD_SCENE(desktop_settings, favorite, Favorite) +ADD_SCENE(desktop_settings, pincode_menu, PinCodeMenu) +ADD_SCENE(desktop_settings, pincode_input, PinCodeInput) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c index e8559017..0b9bb580 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -1,5 +1,6 @@ #include "../desktop_settings_app.h" #include "applications.h" +#include "desktop/desktop_settings/desktop_settings.h" static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; @@ -22,7 +23,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) { submenu_set_header(app->submenu, "Quick access app:"); submenu_set_selected_item(app->submenu, app->settings.favorite); - view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { @@ -43,5 +44,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e void desktop_settings_scene_favorite_on_exit(void* context) { DesktopSettingsApp* app = context; + SAVE_DESKTOP_SETTINGS(&app->settings); submenu_clean(app->submenu); } diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c new file mode 100644 index 00000000..70b059a4 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c @@ -0,0 +1,64 @@ +#include "../desktop_settings_app.h" +#include "desktop/desktop_settings/desktop_settings.h" + +#define SCENE_EXIT_EVENT (0U) + +void desktop_settings_scene_ok_callback(void* context) { + DesktopSettingsApp* app = context; + uint32_t state = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + + if(state == CodeEventsDisablePin) { + memset(app->settings.pincode.data, 0, app->settings.pincode.length * sizeof(uint8_t)); + app->settings.pincode.length = 0; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); +} + +void desktop_settings_scene_pincode_input_on_enter(void* context) { + DesktopSettingsApp* app = context; + CodeInput* code_input = app->code_input; + + uint32_t state = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + bool update = state != CodeEventsDisablePin; + + code_input_set_header_text(code_input, "PIN Code Setup"); + code_input_set_result_callback( + code_input, + desktop_settings_scene_ok_callback, + NULL, + app, + app->settings.pincode.data, + &app->settings.pincode.length, + update); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); +} + +bool desktop_settings_scene_pincode_input_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EXIT_EVENT: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pincode_input_on_exit(void* context) { + DesktopSettingsApp* app = context; + SAVE_DESKTOP_SETTINGS(&app->settings); + code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0); + code_input_set_header_text(app->code_input, ""); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c new file mode 100644 index 00000000..78c2eee9 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c @@ -0,0 +1,78 @@ +#include "../desktop_settings_app.h" +#include "applications.h" + +static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void desktop_settings_scene_pincode_menu_on_enter(void* context) { + DesktopSettingsApp* app = context; + Submenu* submenu = app->submenu; + submenu_clean(submenu); + + if(!app->settings.pincode.length) { + submenu_add_item( + submenu, + "Set Pin", + CodeEventsSetPin, + desktop_settings_scene_pincode_menu_submenu_callback, + app); + + } else { + submenu_add_item( + submenu, + "Change Pin", + CodeEventsChangePin, + desktop_settings_scene_pincode_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Disable", + CodeEventsDisablePin, + desktop_settings_scene_pincode_menu_submenu_callback, + app); + } + + submenu_set_header(app->submenu, "Pin code settings:"); + submenu_set_selected_item(app->submenu, app->menu_idx); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); +} + +bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case CodeEventsSetPin: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + consumed = true; + break; + case CodeEventsChangePin: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + consumed = true; + break; + case CodeEventsDisablePin: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + consumed = true; + break; + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pincode_menu_on_exit(void* context) { + DesktopSettingsApp* app = context; + submenu_clean(app->submenu); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c index 33d66d0c..43b541ac 100755 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c @@ -29,7 +29,7 @@ void desktop_settings_scene_start_on_enter(void* context) { desktop_settings_scene_start_submenu_callback, app); - view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) { @@ -39,7 +39,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopSettingsStartSubmenuIndexFavorite: - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case DesktopSettingsStartSubmenuIndexPinSetup: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeMenu); consumed = true; break; } diff --git a/applications/desktop/helpers/desktop_animation.c b/applications/desktop/helpers/desktop_animation.c index 07eaf631..f570b171 100644 --- a/applications/desktop/helpers/desktop_animation.c +++ b/applications/desktop/helpers/desktop_animation.c @@ -1,5 +1,7 @@ #include "desktop_animation.h" +#define TAG "DesktopAnimation" + static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; const Icon* desktop_get_icon() { @@ -12,10 +14,10 @@ const Icon* desktop_get_icon() { DolphinStats stats = dolphin_stats(dolphin); float timediff = fabs(difftime(stats.timestamp, dolphin_state_timestamp())); - FURI_LOG_I("desktop-animation", "background change"); - FURI_LOG_I("desktop-animation", "icounter: %d", stats.icounter); - FURI_LOG_I("desktop-animation", "butthurt: %d", stats.butthurt); - FURI_LOG_I("desktop-animation", "time since deeed: %.0f", timediff); + FURI_LOG_I(TAG, "background change"); + FURI_LOG_I(TAG, "icounter: %d", stats.icounter); + FURI_LOG_I(TAG, "butthurt: %d", stats.butthurt); + FURI_LOG_I(TAG, "time since deeed: %.0f", timediff); #endif if((random() % 100) > 50) { // temp rnd selection diff --git a/applications/desktop/scenes/desktop_scene_config.h b/applications/desktop/scenes/desktop_scene_config.h index 067de7c4..e84db67d 100644 --- a/applications/desktop/scenes/desktop_scene_config.h +++ b/applications/desktop/scenes/desktop_scene_config.h @@ -4,3 +4,4 @@ ADD_SCENE(desktop, locked, Locked) ADD_SCENE(desktop, debug, Debug) ADD_SCENE(desktop, first_start, FirstStart) ADD_SCENE(desktop, hw_mismatch, HwMismatch) +ADD_SCENE(desktop, pinsetup, PinSetup) diff --git a/applications/desktop/scenes/desktop_scene_lock_menu.c b/applications/desktop/scenes/desktop_scene_lock_menu.c index 537469b5..8a73739f 100644 --- a/applications/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/desktop/scenes/desktop_scene_lock_menu.c @@ -1,5 +1,8 @@ #include "../desktop_i.h" #include "../views/desktop_lock_menu.h" +#include +#include +#include void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) { Desktop* desktop = (Desktop*)context; @@ -9,7 +12,10 @@ void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) void desktop_scene_lock_menu_on_enter(void* context) { Desktop* desktop = (Desktop*)context; + LOAD_DESKTOP_SETTINGS(&desktop->settings); + desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); + desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); } @@ -20,15 +26,29 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopLockMenuEventLock: + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin); scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); consumed = true; break; + case DesktopLockMenuEventPinLock: + if(desktop->settings.pincode.length > 0) { + furi_hal_lock_set(true); + furi_hal_usb_disable(); + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); + } else { + scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); + } - case DesktopLockMenuEventExit: - scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); consumed = true; break; - + case DesktopLockMenuEventExit: + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); + consumed = true; + break; default: break; } diff --git a/applications/desktop/scenes/desktop_scene_locked.c b/applications/desktop/scenes/desktop_scene_locked.c index 6e7064ad..b8111ed7 100644 --- a/applications/desktop/scenes/desktop_scene_locked.c +++ b/applications/desktop/scenes/desktop_scene_locked.c @@ -1,5 +1,6 @@ #include "../desktop_i.h" #include "../views/desktop_locked.h" +#include void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) { Desktop* desktop = (Desktop*)context; @@ -15,12 +16,39 @@ void desktop_scene_locked_on_enter(void* context) { desktop_locked_update_hint_timeout(locked_view); desktop_locked_set_dolphin_animation(locked_view); + uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); + + desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); + view_port_enabled_set(desktop->lock_viewport, true); osTimerStart(locked_view->timer, 63); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); } +static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) { + bool match = false; + + size_t length = desktop->pincode_buffer.length; + length = code_input_push(desktop->pincode_buffer.data, length, event); + desktop->pincode_buffer.length = length; + + match = code_input_compare( + desktop->pincode_buffer.data, + length, + desktop->settings.pincode.data, + desktop->settings.pincode.length); + + if(match) { + desktop->pincode_buffer.length = 0; + furi_hal_usb_enable(); + furi_hal_lock_set(false); + desktop_main_unlocked(desktop->main_view); + } + + return match; +} + bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { Desktop* desktop = (Desktop*)context; @@ -36,7 +64,17 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { case DesktopLockedEventUpdate: desktop_locked_manage_redraw(desktop->locked_view); consumed = true; + break; + case DesktopLockedEventInputReset: + desktop->pincode_buffer.length = 0; + break; default: + if(desktop_scene_locked_check_pin(desktop, event.event)) { + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); + consumed = true; + } break; } } diff --git a/applications/desktop/scenes/desktop_scene_main.c b/applications/desktop/scenes/desktop_scene_main.c index 4826c2cd..36c8bdee 100644 --- a/applications/desktop/scenes/desktop_scene_main.c +++ b/applications/desktop/scenes/desktop_scene_main.c @@ -70,7 +70,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenFavorite: - desktop_settings_load(&desktop->settings); + LOAD_DESKTOP_SETTINGS(&desktop->settings); desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); consumed = true; break; diff --git a/applications/desktop/scenes/desktop_scene_pinsetup.c b/applications/desktop/scenes/desktop_scene_pinsetup.c new file mode 100644 index 00000000..6b1c9686 --- /dev/null +++ b/applications/desktop/scenes/desktop_scene_pinsetup.c @@ -0,0 +1,50 @@ +#include "../desktop_i.h" + +#define SCENE_EXIT_EVENT (0U) + +void desktop_scene_ok_callback(void* context) { + Desktop* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); +} + +void desktop_scene_pinsetup_on_enter(void* context) { + Desktop* app = context; + CodeInput* code_input = app->code_input; + + code_input_set_result_callback( + code_input, + desktop_scene_ok_callback, + NULL, + app, + app->settings.pincode.data, + &app->settings.pincode.length, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopViewPinSetup); +} + +bool desktop_scene_pinsetup_on_event(void* context, SceneManagerEvent event) { + Desktop* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EXIT_EVENT: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_scene_pinsetup_on_exit(void* context) { + Desktop* app = context; + SAVE_DESKTOP_SETTINGS(&app->settings); + code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0); + code_input_set_header_text(app->code_input, ""); +} diff --git a/applications/desktop/views/desktop_debug.c b/applications/desktop/views/desktop_debug.c index cd3dfd15..ae5f1f8d 100644 --- a/applications/desktop/views/desktop_debug.c +++ b/applications/desktop/views/desktop_debug.c @@ -42,7 +42,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { my_name ? my_name : "Unknown"); canvas_draw_str(canvas, 5, 23, buffer); - ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_boot_version() : + ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_bootloader_version() : furi_hal_version_get_firmware_version(); if(!ver) { diff --git a/applications/desktop/views/desktop_lock_menu.c b/applications/desktop/views/desktop_lock_menu.c index 11392cd8..52a8df56 100644 --- a/applications/desktop/views/desktop_lock_menu.c +++ b/applications/desktop/views/desktop_lock_menu.c @@ -12,6 +12,14 @@ void desktop_lock_menu_set_callback( lock_menu->context = context; } +void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) { + with_view_model( + lock_menu->view, (DesktopLockMenuViewModel * model) { + model->pin_set = pin_is_set; + return true; + }); +} + void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) { with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { @@ -26,6 +34,10 @@ static void lock_menu_callback(void* context, uint8_t index) { switch(index) { case 0: // lock lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + break; + case 1: // lock + lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); + break; default: // wip message with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { @@ -37,7 +49,7 @@ static void lock_menu_callback(void* context, uint8_t index) { } void desktop_lock_menu_render(Canvas* canvas, void* model) { - const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"}; + const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"}; DesktopLockMenuViewModel* m = model; canvas_clear(canvas); @@ -47,13 +59,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) { canvas_set_font(canvas, FontSecondary); for(uint8_t i = 0; i < 3; ++i) { - canvas_draw_str_aligned( - canvas, - 64, - 13 + (i * 17), - AlignCenter, - AlignCenter, - (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]); + const char* str = Lockmenu_Items[i]; + + if(i == 1 && !m->pin_set) str = "Set PIN"; + if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented"; + + canvas_draw_str_aligned(canvas, 64, 13 + (i * 17), AlignCenter, AlignCenter, str); + if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15); } } diff --git a/applications/desktop/views/desktop_lock_menu.h b/applications/desktop/views/desktop_lock_menu.h index 714069cb..73f4f203 100644 --- a/applications/desktop/views/desktop_lock_menu.h +++ b/applications/desktop/views/desktop_lock_menu.h @@ -10,7 +10,7 @@ typedef enum { DesktopLockMenuEventLock, - DesktopLockMenuEventUnlock, + DesktopLockMenuEventPinLock, DesktopLockMenuEventExit, } DesktopLockMenuEvent; @@ -27,6 +27,7 @@ struct DesktopLockMenuView { typedef struct { uint8_t idx; uint8_t hint_timeout; + bool pin_set; } DesktopLockMenuViewModel; void desktop_lock_menu_set_callback( @@ -35,6 +36,7 @@ void desktop_lock_menu_set_callback( void* context); View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); +void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set); void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu); DesktopLockMenuView* desktop_lock_menu_alloc(); void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); diff --git a/applications/desktop/views/desktop_locked.c b/applications/desktop/views/desktop_locked.c index b4942506..f4373dfb 100644 --- a/applications/desktop/views/desktop_locked.c +++ b/applications/desktop/views/desktop_locked.c @@ -80,6 +80,14 @@ void desktop_locked_reset_counter(DesktopLockedView* locked_view) { }); } +void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) { + with_view_model( + locked_view->view, (DesktopLockedViewModel * model) { + model->pin_lock = locked; + return true; + }); +} + void desktop_locked_render(Canvas* canvas, void* model) { DesktopLockedViewModel* m = model; uint32_t now = osKernelGetTickCount(); @@ -100,7 +108,7 @@ void desktop_locked_render(Canvas* canvas, void* model) { canvas_set_font(canvas, FontPrimary); elements_multiline_text_framed(canvas, 42, 30, "Locked"); - } else { + } else if(!m->pin_lock) { canvas_set_font(canvas, FontSecondary); canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); elements_multiline_text(canvas, 65, 20, "To unlock\npress:"); @@ -116,27 +124,49 @@ View* desktop_locked_get_view(DesktopLockedView* locked_view) { bool desktop_locked_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); - DesktopLockedView* locked_view = context; + + uint32_t press_time = 0; + bool locked_with_pin = false; + + with_view_model( + locked_view->view, (DesktopLockedViewModel * model) { + locked_with_pin = model->pin_lock; + return false; + }); + if(event->type == InputTypeShort) { - desktop_locked_update_hint_timeout(locked_view); + if(locked_with_pin) { + press_time = osKernelGetTickCount(); - if(event->key == InputKeyBack) { - uint32_t press_time = osKernelGetTickCount(); - // check if pressed sequentially - if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { + if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) { locked_view->lock_lastpress = press_time; - locked_view->lock_count = 0; - } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { - locked_view->lock_lastpress = press_time; - locked_view->lock_count++; + locked_view->callback(DesktopLockedEventInputReset, locked_view->context); } - if(locked_view->lock_count == UNLOCK_CNT) { - locked_view->lock_count = 0; - locked_view->callback(DesktopLockedEventUnlock, locked_view->context); + locked_view->callback(event->key, locked_view->context); + } else { + desktop_locked_update_hint_timeout(locked_view); + + if(event->key == InputKeyBack) { + press_time = osKernelGetTickCount(); + // check if pressed sequentially + if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count++; + } + + if(locked_view->lock_count == UNLOCK_CNT) { + locked_view->lock_count = 0; + locked_view->callback(DesktopLockedEventUnlock, locked_view->context); + } } } + + if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count = 0; + } } // All events consumed return true; diff --git a/applications/desktop/views/desktop_locked.h b/applications/desktop/views/desktop_locked.h index c85705a8..f7d635b3 100644 --- a/applications/desktop/views/desktop_locked.h +++ b/applications/desktop/views/desktop_locked.h @@ -15,10 +15,16 @@ #define DOOR_R_POS_MIN 60 typedef enum { - DesktopLockedEventUnlock, - DesktopLockedEventUpdate, + DesktopLockedEventUnlock = 10U, + DesktopLockedEventUpdate = 11U, + DesktopLockedEventInputReset = 12U, } DesktopLockedEvent; +typedef enum { + DesktopLockedWithPin, + DesktopLockedNoPin, +} DesktopLockedSceneState; + typedef struct DesktopLockedView DesktopLockedView; typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); @@ -42,6 +48,7 @@ typedef struct { int8_t door_right_x; bool animation_seq_end; + bool pin_lock; } DesktopLockedViewModel; void desktop_locked_set_callback( @@ -58,5 +65,4 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view); View* desktop_locked_get_view(DesktopLockedView* locked_view); DesktopLockedView* desktop_locked_alloc(); void desktop_locked_free(DesktopLockedView* locked_view); -void desktop_main_unlocked(DesktopMainView* main_view); -void desktop_main_reset_hint(DesktopMainView* main_view); \ No newline at end of file +void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked); \ No newline at end of file diff --git a/applications/desktop/views/desktop_main.c b/applications/desktop/views/desktop_main.c index a35ba008..00a606e8 100644 --- a/applications/desktop/views/desktop_main.c +++ b/applications/desktop/views/desktop_main.c @@ -67,6 +67,7 @@ bool desktop_main_input(InputEvent* event, void* context) { } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { main_view->callback(DesktopMainEventOpenFavorite, main_view->context); } + desktop_main_reset_hint(main_view); return true; diff --git a/applications/desktop/views/desktop_main.h b/applications/desktop/views/desktop_main.h index 78678b8c..1b631f6f 100644 --- a/applications/desktop/views/desktop_main.h +++ b/applications/desktop/views/desktop_main.h @@ -7,12 +7,12 @@ #include typedef enum { - DesktopMainEventOpenMenu, DesktopMainEventOpenLockMenu, - DesktopMainEventOpenDebug, - DesktopMainEventUnlocked, DesktopMainEventOpenArchive, DesktopMainEventOpenFavorite, + DesktopMainEventOpenMenu, + DesktopMainEventOpenDebug, + DesktopMainEventUnlocked, } DesktopMainEvent; typedef struct DesktopMainView DesktopMainView; @@ -37,9 +37,8 @@ void desktop_main_set_callback( void* context); View* desktop_main_get_view(DesktopMainView* main_view); - DesktopMainView* desktop_main_alloc(); - void desktop_main_free(DesktopMainView* main_view); - void desktop_main_switch_dolphin_animation(DesktopMainView* main_view); +void desktop_main_unlocked(DesktopMainView* main_view); +void desktop_main_reset_hint(DesktopMainView* main_view); diff --git a/applications/dialogs/view_holder.c b/applications/dialogs/view_holder.c index 2307bcd8..56d88001 100644 --- a/applications/dialogs/view_holder.c +++ b/applications/dialogs/view_holder.c @@ -1,6 +1,8 @@ #include "view-holder.h" #include +#define TAG "ViewHolder" + struct ViewHolder { View* view; ViewPort* view_port; @@ -125,7 +127,7 @@ static void view_holder_input_callback(InputEvent* event, void* context) { view_holder->ongoing_input &= ~key_bit; } else if(!(view_holder->ongoing_input & key_bit)) { FURI_LOG_W( - "ViewHolder", + TAG, "non-complementary input, discarding key: %s, type: %s", input_get_key_name(event->key), input_get_type_name(event->type)); diff --git a/applications/dolphin/dolphin.c b/applications/dolphin/dolphin.c index f1f1e5e5..09bdb982 100644 --- a/applications/dolphin/dolphin.c +++ b/applications/dolphin/dolphin.c @@ -81,13 +81,8 @@ static void dolphin_check_butthurt(DolphinState* state) { furi_assert(state); float diff_time = difftime(dolphin_state_get_timestamp(state), dolphin_state_timestamp()); -#if 0 - FURI_LOG_I("dolphin-state", "Butthurt check, time since deed %.0f", fabs(diff_time)); -#endif - if((fabs(diff_time)) > DOLPHIN_TIMEGATE) { - // increase butthurt - FURI_LOG_I("dolphin-state", "Increasing butthurt"); + FURI_LOG_I("DolphinState", "Increasing butthurt"); dolphin_state_butthurted(state); } } diff --git a/applications/dolphin/helpers/dolphin_state.c b/applications/dolphin/helpers/dolphin_state.c index 240b7d95..31066534 100644 --- a/applications/dolphin/helpers/dolphin_state.c +++ b/applications/dolphin/helpers/dolphin_state.c @@ -2,20 +2,14 @@ #include #include #include +#include -#define DOLPHIN_STORE_KEY "/int/dolphin.state" -#define DOLPHIN_STORE_HEADER_MAGIC 0xD0 -#define DOLPHIN_STORE_HEADER_VERSION 0x01 +#define TAG "DolphinState" +#define DOLPHIN_STATE_PATH "/int/dolphin.state" +#define DOLPHIN_STATE_HEADER_MAGIC 0xD0 +#define DOLPHIN_STATE_HEADER_VERSION 0x01 #define DOLPHIN_LVL_THRESHOLD 20.0f -typedef struct { - uint8_t magic; - uint8_t version; - uint8_t checksum; - uint8_t flags; - uint32_t timestamp; -} DolphinStoreHeader; - typedef struct { uint32_t limit_ibutton; uint32_t limit_nfc; @@ -28,25 +22,16 @@ typedef struct { uint64_t timestamp; } DolphinStoreData; -typedef struct { - DolphinStoreHeader header; - DolphinStoreData data; -} DolphinStore; - struct DolphinState { - Storage* fs_api; DolphinStoreData data; bool dirty; }; DolphinState* dolphin_state_alloc() { - DolphinState* dolphin_state = furi_alloc(sizeof(DolphinState)); - dolphin_state->fs_api = furi_record_open("storage"); - return dolphin_state; + return furi_alloc(sizeof(DolphinState)); } void dolphin_state_free(DolphinState* dolphin_state) { - furi_record_close("storage"); free(dolphin_state); } @@ -55,121 +40,38 @@ bool dolphin_state_save(DolphinState* dolphin_state) { return true; } - FURI_LOG_I("dolphin-state", "State is dirty, saving to \"%s\"", DOLPHIN_STORE_KEY); - DolphinStore store; - // Calculate checksum - uint8_t* source = (uint8_t*)&dolphin_state->data; - uint8_t checksum = 0; - for(size_t i = 0; i < sizeof(DolphinStoreData); i++) { - checksum += source[i]; - } - // Set header - store.header.magic = DOLPHIN_STORE_HEADER_MAGIC; - store.header.version = DOLPHIN_STORE_HEADER_VERSION; - store.header.checksum = checksum; - store.header.flags = 0; - store.header.timestamp = 0; - // Set data - store.data = dolphin_state->data; + bool result = saved_struct_save( + DOLPHIN_STATE_PATH, + &dolphin_state->data, + sizeof(DolphinStoreData), + DOLPHIN_STATE_HEADER_MAGIC, + DOLPHIN_STATE_HEADER_VERSION); - // Store - File* file = storage_file_alloc(dolphin_state->fs_api); - bool save_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_WRITE, FSOM_CREATE_ALWAYS); - - if(save_result) { - uint16_t bytes_count = storage_file_write(file, &store, sizeof(DolphinStore)); - - if(bytes_count != sizeof(DolphinStore)) { - save_result = false; - } + if(result) { + FURI_LOG_I(TAG, "State saved"); + dolphin_state->dirty = false; + } else { + FURI_LOG_E(TAG, "Failed to save state"); } - if(!save_result) { - FURI_LOG_E( - "dolphin-state", - "Save failed. Storage returned: %s", - storage_file_get_error_desc(file)); - } - - storage_file_close(file); - storage_file_free(file); - - dolphin_state->dirty = !save_result; - - FURI_LOG_I("dolphin-state", "Saved"); - - return save_result; + return result; } bool dolphin_state_load(DolphinState* dolphin_state) { - DolphinStore store; - // Read Dolphin State Store - FURI_LOG_I("dolphin-state", "Loading state from \"%s\"", DOLPHIN_STORE_KEY); + bool loaded = saved_struct_load( + DOLPHIN_STATE_PATH, + &dolphin_state->data, + sizeof(DolphinStoreData), + DOLPHIN_STATE_HEADER_MAGIC, + DOLPHIN_STATE_HEADER_VERSION); - File* file = storage_file_alloc(dolphin_state->fs_api); - bool load_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_READ, FSOM_OPEN_EXISTING); - if(!load_result) { - FURI_LOG_E( - "dolphin-state", - "Load failed. Storage returned: %s", - storage_file_get_error_desc(file)); - } else { - uint16_t bytes_count = storage_file_read(file, &store, sizeof(DolphinStore)); - - if(bytes_count != sizeof(DolphinStore)) { - load_result = false; - } + if(!loaded) { + FURI_LOG_W(TAG, "Reset dolphin-state"); + memset(dolphin_state, 0, sizeof(*dolphin_state)); + dolphin_state->dirty = true; } - if(!load_result) { - FURI_LOG_E("dolphin-state", "DolphinStore size mismatch"); - } else { - if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC && - store.header.version == DOLPHIN_STORE_HEADER_VERSION) { - FURI_LOG_I( - "dolphin-state", - "Magic(%d) and Version(%d) match", - store.header.magic, - store.header.version); - uint8_t checksum = 0; - const uint8_t* source = (const uint8_t*)&store.data; - for(size_t i = 0; i < sizeof(DolphinStoreData); i++) { - checksum += source[i]; - } - - if(store.header.checksum == checksum) { - FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum); - dolphin_state->data = store.data; - } else { - FURI_LOG_E( - "dolphin-state", - "Checksum(%d != %d) mismatch", - store.header.checksum, - checksum); - load_result = false; - } - } else { - FURI_LOG_E( - "dolphin-state", - "Magic(%d != %d) or Version(%d != %d) mismatch", - store.header.magic, - DOLPHIN_STORE_HEADER_MAGIC, - store.header.version, - DOLPHIN_STORE_HEADER_VERSION); - load_result = false; - } - } - - storage_file_close(file); - storage_file_free(file); - - dolphin_state->dirty = !load_result; - - return load_result; -} - -void dolphin_state_clear(DolphinState* dolphin_state) { - memset(&dolphin_state->data, 0, sizeof(DolphinStoreData)); + return loaded; } uint64_t dolphin_state_timestamp() { @@ -229,4 +131,4 @@ uint32_t dolphin_state_get_level(uint32_t icounter) { uint32_t dolphin_state_xp_to_levelup(uint32_t icounter, uint32_t level, bool remaining) { return (DOLPHIN_LVL_THRESHOLD * level * (level + 1) / 2) - (remaining ? icounter : 0); -} \ No newline at end of file +} diff --git a/applications/gpio/gpio_app_i.h b/applications/gpio/gpio_app_i.h index bfeab404..226fa06e 100644 --- a/applications/gpio/gpio_app_i.h +++ b/applications/gpio/gpio_app_i.h @@ -12,6 +12,14 @@ #include #include "views/gpio_test.h" +#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL) +#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL) +#define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL) +#define GPIO_SCENE_START_CUSTOM_EVENT_USB_UART (3UL) + +#define GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE (4UL) +#define GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE (5UL) + struct GpioApp { Gui* gui; ViewDispatcher* view_dispatcher; diff --git a/applications/gpio/scenes/gpio_scene_start.c b/applications/gpio/scenes/gpio_scene_start.c index b3ed40fd..df210882 100644 --- a/applications/gpio/scenes/gpio_scene_start.c +++ b/applications/gpio/scenes/gpio_scene_start.c @@ -1,11 +1,6 @@ #include "../gpio_app_i.h" #include "furi-hal-power.h" -#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL) -#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL) -#define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL) -#define GPIO_SCENE_START_CUSTOM_EVENT_USB_UART (3UL) - enum GpioItem { GpioItemOtg, GpioItemTest, @@ -72,6 +67,9 @@ void gpio_scene_start_on_enter(void* context) { variable_item_list_add(var_item_list, "GPIO tester", 0, NULL, NULL); variable_item_list_add(var_item_list, "USB-UART bridge", 0, NULL, NULL); + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneStart)); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); } @@ -85,8 +83,10 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF) { furi_hal_power_disable_otg(); } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_TEST) { + scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, 1); scene_manager_next_scene(app->scene_manager, GpioSceneTest); } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_USB_UART) { + scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, 2); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); } consumed = true; diff --git a/applications/gpio/scenes/gpio_scene_usb_uart.c b/applications/gpio/scenes/gpio_scene_usb_uart.c index 05de8e7b..b24e4b3e 100644 --- a/applications/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/gpio/scenes/gpio_scene_usb_uart.c @@ -1,17 +1,6 @@ +#include "../usb_uart_bridge.h" #include "../gpio_app_i.h" #include "furi-hal.h" -#include -#include -#include "usb_cdc.h" - -#define USB_PKT_LEN CDC_DATA_SZ -#define USB_UART_RX_BUF_SIZE (USB_PKT_LEN * 3) -#define USB_UART_TX_BUF_SIZE (USB_PKT_LEN * 3) - -typedef enum { - WorkerCmdStop = (1 << 0), - -} WorkerCommandFlags; typedef enum { UsbUartLineIndexVcp, @@ -21,42 +10,7 @@ typedef enum { UsbUartLineIndexDisable, } LineIndex; -typedef enum { - UsbUartPortUSART1 = 0, - UsbUartPortLPUART1 = 1, -} PortIdx; - -typedef struct { - uint8_t vcp_ch; - PortIdx uart_ch; - uint32_t baudrate; -} UsbUartConfig; - -typedef struct { - UsbUartConfig cfg_cur; - UsbUartConfig cfg_set; - char br_text[8]; - - bool running; - osThreadId_t parent_thread; - - osThreadAttr_t thread_attr; - osThreadId_t thread; - - osThreadAttr_t tx_thread_attr; - osThreadId_t tx_thread; - - StreamBufferHandle_t rx_stream; - osSemaphoreId_t rx_done_sem; - osSemaphoreId_t usb_sof_sem; - - StreamBufferHandle_t tx_stream; - - uint8_t rx_buf[USB_PKT_LEN]; - uint8_t tx_buf[USB_PKT_LEN]; -} UsbUartParams; - -static UsbUartParams* usb_uart; +static UsbUartConfig* cfg_set; static const char* vcp_ch[] = {"0 (CLI)", "1"}; static const char* uart_ch[] = {"USART1", "LPUART1"}; @@ -73,197 +27,14 @@ static const uint32_t baudrate_list[] = { 921600, }; -static void vcp_on_cdc_tx_complete(); -static void vcp_on_cdc_rx(); -static void vcp_state_callback(uint8_t state); -static void vcp_on_cdc_control_line(uint8_t state); -static void vcp_on_line_config(struct usb_cdc_line_coding* config); - -static CdcCallbacks cdc_cb = { - vcp_on_cdc_tx_complete, - vcp_on_cdc_rx, - vcp_state_callback, - vcp_on_cdc_control_line, - vcp_on_line_config, -}; - -/* USB UART worker */ - -static void usb_uart_tx_thread(void* context); - -static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - - if(ev == UartIrqEventRXNE) { - size_t ret = - xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); - furi_check(ret == 1); - ret = xStreamBufferBytesAvailable(usb_uart->rx_stream); - if(ret > USB_PKT_LEN) osSemaphoreRelease(usb_uart->rx_done_sem); - } else if(ev == UartIrqEventIDLE) { - osSemaphoreRelease(usb_uart->rx_done_sem); - } - - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -} - -static void usb_uart_worker(void* context) { - memcpy(&usb_uart->cfg_cur, &usb_uart->cfg_set, sizeof(UsbUartConfig)); - - usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); - usb_uart->rx_done_sem = osSemaphoreNew(1, 1, NULL); - usb_uart->usb_sof_sem = osSemaphoreNew(1, 1, NULL); - - usb_uart->tx_stream = xStreamBufferCreate(USB_UART_TX_BUF_SIZE, 1); - - usb_uart->tx_thread = NULL; - usb_uart->tx_thread_attr.name = "usb_uart_tx"; - usb_uart->tx_thread_attr.stack_size = 512; - - UsbMode usb_mode_prev = furi_hal_usb_get_config(); - if(usb_uart->cfg_cur.vcp_ch == 0) { - furi_hal_usb_set_config(UsbModeVcpSingle); - furi_hal_vcp_disable(); - } else { - furi_hal_usb_set_config(UsbModeVcpDual); - } - - if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) { - furi_hal_usart_init(); - furi_hal_usart_set_irq_cb(usb_uart_on_irq_cb); - if(usb_uart->cfg_cur.baudrate != 0) - furi_hal_usart_set_br(usb_uart->cfg_cur.baudrate); - else - vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg_cur.vcp_ch)); - } else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) { - furi_hal_lpuart_init(); - furi_hal_lpuart_set_irq_cb(usb_uart_on_irq_cb); - if(usb_uart->cfg_cur.baudrate != 0) - furi_hal_lpuart_set_br(usb_uart->cfg_cur.baudrate); - else - vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg_cur.vcp_ch)); - } - - furi_hal_cdc_set_callbacks(usb_uart->cfg_cur.vcp_ch, &cdc_cb); - usb_uart->tx_thread = osThreadNew(usb_uart_tx_thread, NULL, &usb_uart->tx_thread_attr); - - while(1) { - furi_check(osSemaphoreAcquire(usb_uart->rx_done_sem, osWaitForever) == osOK); - if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny, 0) == WorkerCmdStop) break; - size_t len = 0; - do { - len = xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_PKT_LEN, 0); - if(len > 0) { - if(osSemaphoreAcquire(usb_uart->usb_sof_sem, 100) == osOK) - furi_hal_cdc_send(usb_uart->cfg_cur.vcp_ch, usb_uart->rx_buf, len); - else - xStreamBufferReset(usb_uart->rx_stream); - } - } while(len > 0); - } - - osThreadTerminate(usb_uart->tx_thread); - - if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) - furi_hal_usart_deinit(); - else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) - furi_hal_lpuart_deinit(); - - furi_hal_cdc_set_callbacks(usb_uart->cfg_cur.vcp_ch, NULL); - furi_hal_usb_set_config(usb_mode_prev); - if(usb_uart->cfg_cur.vcp_ch == 0) furi_hal_vcp_enable(); - - vStreamBufferDelete(usb_uart->rx_stream); - osSemaphoreDelete(usb_uart->rx_done_sem); - osSemaphoreDelete(usb_uart->usb_sof_sem); - - vStreamBufferDelete(usb_uart->tx_stream); - osThreadFlagsSet(usb_uart->parent_thread, WorkerCmdStop); - osThreadExit(); -} - -static void usb_uart_tx_thread(void* context) { - uint8_t data = 0; - while(1) { - size_t len = xStreamBufferReceive(usb_uart->tx_stream, &data, 1, osWaitForever); - if(len > 0) { - if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) - furi_hal_usart_tx(&data, len); - else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) - furi_hal_lpuart_tx(&data, len); - } - } - osThreadExit(); -} - -/* VCP callbacks */ - -static void vcp_on_cdc_tx_complete() { - osSemaphoreRelease(usb_uart->usb_sof_sem); -} - -static void vcp_on_cdc_rx() { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - - uint16_t max_len = xStreamBufferSpacesAvailable(usb_uart->tx_stream); - if(max_len > 0) { - if(max_len > USB_PKT_LEN) max_len = USB_PKT_LEN; - int32_t size = furi_hal_cdc_receive(usb_uart->cfg_cur.vcp_ch, usb_uart->tx_buf, max_len); - - if(size > 0) { - size_t ret = xStreamBufferSendFromISR( - usb_uart->tx_stream, usb_uart->tx_buf, size, &xHigherPriorityTaskWoken); - furi_check(ret == size); - } - } - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -} - -static void vcp_state_callback(uint8_t state) { -} - -static void vcp_on_cdc_control_line(uint8_t state) { -} - -static void vcp_on_line_config(struct usb_cdc_line_coding* config) { - if((usb_uart->cfg_cur.baudrate == 0) && (config->dwDTERate != 0)) { - if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) - furi_hal_usart_set_br(config->dwDTERate); - else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) - furi_hal_lpuart_set_br(config->dwDTERate); - } -} - -/* USB UART app */ - -static void usb_uart_enable() { - if(usb_uart->running == false) { - usb_uart->thread = NULL; - usb_uart->thread_attr.name = "usb_uart"; - usb_uart->thread_attr.stack_size = 1024; - usb_uart->parent_thread = osThreadGetId(); - usb_uart->running = true; - usb_uart->thread = osThreadNew(usb_uart_worker, NULL, &usb_uart->thread_attr); - } -} - -static void usb_uart_disable() { - if(usb_uart->running == true) { - osThreadFlagsSet(usb_uart->thread, WorkerCmdStop); - osSemaphoreRelease(usb_uart->rx_done_sem); - osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny, osWaitForever); - usb_uart->running = false; - } -} - bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { //GpioApp* app = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == UsbUartLineIndexEnable) { - usb_uart_enable(); - } else if(event.event == UsbUartLineIndexDisable) { + if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE) { + usb_uart_enable(cfg_set); + } else if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE) { usb_uart_disable(); } consumed = true; @@ -271,15 +42,13 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { return consumed; } -/* Scene callbacks */ - static void line_vcp_cb(VariableItem* item) { //GpioApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, vcp_ch[index]); - usb_uart->cfg_set.vcp_ch = index; + cfg_set->vcp_ch = index; } static void line_port_cb(VariableItem* item) { @@ -288,34 +57,44 @@ static void line_port_cb(VariableItem* item) { variable_item_set_current_value_text(item, uart_ch[index]); - usb_uart->cfg_set.uart_ch = index; + if(index == 0) + cfg_set->uart_ch = FuriHalUartIdUSART1; + else if(index == 1) + cfg_set->uart_ch = FuriHalUartIdLPUART1; } static void line_baudrate_cb(VariableItem* item) { //GpioApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); + char br_text[8]; + if(index > 0) { - snprintf(usb_uart->br_text, 7, "%lu", baudrate_list[index - 1]); - variable_item_set_current_value_text(item, usb_uart->br_text); - usb_uart->cfg_set.baudrate = baudrate_list[index - 1]; + snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); + variable_item_set_current_value_text(item, br_text); + cfg_set->baudrate = baudrate_list[index - 1]; } else { variable_item_set_current_value_text(item, baudrate_mode[index]); - usb_uart->cfg_set.baudrate = 0; + cfg_set->baudrate = 0; } } static void gpio_scene_usb_uart_enter_callback(void* context, uint32_t index) { furi_assert(context); GpioApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, index); + if(index == UsbUartLineIndexEnable) + view_dispatcher_send_custom_event( + app->view_dispatcher, GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE); + else if(index == UsbUartLineIndexDisable) + view_dispatcher_send_custom_event( + app->view_dispatcher, GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE); } void gpio_scene_usb_uart_on_enter(void* context) { GpioApp* app = context; VariableItemList* var_item_list = app->var_item_list; - usb_uart = furi_alloc(sizeof(UsbUartParams)); + cfg_set = furi_alloc(sizeof(UsbUartConfig)); VariableItem* item; @@ -341,12 +120,19 @@ void gpio_scene_usb_uart_on_enter(void* context) { item = variable_item_list_add(var_item_list, "Enable", 0, NULL, NULL); item = variable_item_list_add(var_item_list, "Disable", 0, NULL, NULL); + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneUsbUart)); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); } void gpio_scene_usb_uart_on_exit(void* context) { GpioApp* app = context; usb_uart_disable(); + scene_manager_set_scene_state( + app->scene_manager, + GpioSceneUsbUart, + variable_item_list_get_selected_item_index(app->var_item_list)); variable_item_list_clean(app->var_item_list); - free(usb_uart); -} \ No newline at end of file + free(cfg_set); +} diff --git a/applications/gpio/usb_uart_bridge.c b/applications/gpio/usb_uart_bridge.c new file mode 100644 index 00000000..9b43f643 --- /dev/null +++ b/applications/gpio/usb_uart_bridge.c @@ -0,0 +1,212 @@ +#include "usb_uart_bridge.h" +#include "furi-hal.h" +#include +#include +#include "usb_cdc.h" + +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) + +typedef enum { + WorkerEvtReserved = (1 << 0), // Reserved for StreamBuffer internal event + WorkerEvtStop = (1 << 1), + WorkerEvtRxDone = (1 << 2), + + WorkerEvtTxStop = (1 << 3), + WorkerEvtCdcRx = (1 << 4), +} WorkerEvtFlags; + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) +#define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) + +typedef struct { + UsbUartConfig cfg; + + FuriThread* thread; + FuriThread* tx_thread; + + StreamBufferHandle_t rx_stream; + + osMutexId_t usb_mutex; + + osSemaphoreId_t tx_sem; + + uint8_t rx_buf[USB_CDC_PKT_LEN]; + + bool buf_full; +} UsbUartParams; + +static UsbUartParams* usb_uart; +static bool running = false; + +static void vcp_on_cdc_tx_complete(); +static void vcp_on_cdc_rx(); +static void vcp_state_callback(uint8_t state); +static void vcp_on_cdc_control_line(uint8_t state); +static void vcp_on_line_config(struct usb_cdc_line_coding* config); + +static CdcCallbacks cdc_cb = { + vcp_on_cdc_tx_complete, + vcp_on_cdc_rx, + vcp_state_callback, + vcp_on_cdc_control_line, + vcp_on_line_config, +}; + +/* USB UART worker */ + +static int32_t usb_uart_tx_thread(void* context); + +static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + if(ev == UartIrqEventRXNE) { + xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtRxDone); + } +} + +static int32_t usb_uart_worker(void* context) { + memcpy(&usb_uart->cfg, context, sizeof(UsbUartConfig)); + + usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); + + usb_uart->tx_sem = osSemaphoreNew(1, 1, NULL); + usb_uart->usb_mutex = osMutexNew(NULL); + + usb_uart->tx_thread = furi_thread_alloc(); + furi_thread_set_name(usb_uart->tx_thread, "UsbUartTxWorker"); + furi_thread_set_stack_size(usb_uart->tx_thread, 512); + furi_thread_set_context(usb_uart->tx_thread, NULL); + furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); + + UsbMode usb_mode_prev = furi_hal_usb_get_config(); + if(usb_uart->cfg.vcp_ch == 0) { + furi_hal_usb_set_config(UsbModeVcpSingle); + furi_hal_vcp_disable(); + } else { + furi_hal_usb_set_config(UsbModeVcpDual); + } + osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); + + if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) { + furi_hal_console_disable(); + } else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) { + furi_hal_uart_init(usb_uart->cfg.uart_ch, 115200); + furi_hal_uart_set_irq_cb(usb_uart->cfg.uart_ch, usb_uart_on_irq_cb); + } + + furi_hal_uart_set_irq_cb(usb_uart->cfg.uart_ch, usb_uart_on_irq_cb); + if(usb_uart->cfg.baudrate != 0) + furi_hal_uart_set_br(usb_uart->cfg.uart_ch, usb_uart->cfg.baudrate); + else + vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch)); + + furi_hal_cdc_set_callbacks(usb_uart->cfg.vcp_ch, &cdc_cb); + + furi_thread_start(usb_uart->tx_thread); + + while(1) { + uint32_t events = osThreadFlagsWait(WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever); + furi_check((events & osFlagsError) == 0); + if(events & WorkerEvtStop) break; + if(events & WorkerEvtRxDone) { + size_t len = + xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); + if(len > 0) { + if(osSemaphoreAcquire(usb_uart->tx_sem, 100) == osOK) { + furi_check(osMutexAcquire(usb_uart->usb_mutex, osWaitForever) == osOK); + furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len); + furi_check(osMutexRelease(usb_uart->usb_mutex) == osOK); + } else { + xStreamBufferReset(usb_uart->rx_stream); + } + } + } + } + + osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtTxStop); + furi_thread_join(usb_uart->tx_thread); + furi_thread_free(usb_uart->tx_thread); + + if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) + furi_hal_console_enable(); + else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) + furi_hal_uart_deinit(usb_uart->cfg.uart_ch); + + furi_hal_cdc_set_callbacks(usb_uart->cfg.vcp_ch, NULL); + furi_hal_usb_set_config(usb_mode_prev); + if(usb_uart->cfg.vcp_ch == 0) furi_hal_vcp_enable(); + + vStreamBufferDelete(usb_uart->rx_stream); + osMutexDelete(usb_uart->usb_mutex); + osSemaphoreDelete(usb_uart->tx_sem); + + return 0; +} + +static int32_t usb_uart_tx_thread(void* context) { + uint8_t data[USB_CDC_PKT_LEN]; + while(1) { + uint32_t events = osThreadFlagsWait(WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever); + furi_check((events & osFlagsError) == 0); + if(events & WorkerEvtTxStop) break; + if(events & WorkerEvtCdcRx) { + furi_check(osMutexAcquire(usb_uart->usb_mutex, osWaitForever) == osOK); + int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, data, USB_CDC_PKT_LEN); + furi_check(osMutexRelease(usb_uart->usb_mutex) == osOK); + + if(size > 0) { + furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, size); + } + } + } + return 0; +} + +/* VCP callbacks */ + +static void vcp_on_cdc_tx_complete() { + osSemaphoreRelease(usb_uart->tx_sem); +} + +static void vcp_on_cdc_rx() { + osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); +} + +static void vcp_state_callback(uint8_t state) { +} + +static void vcp_on_cdc_control_line(uint8_t state) { +} + +static void vcp_on_line_config(struct usb_cdc_line_coding* config) { + if((usb_uart->cfg.baudrate == 0) && (config->dwDTERate != 0)) + furi_hal_uart_set_br(usb_uart->cfg.uart_ch, config->dwDTERate); +} + +void usb_uart_enable(UsbUartConfig* cfg) { + if(running == false) { + running = true; + usb_uart = furi_alloc(sizeof(UsbUartParams)); + + usb_uart->thread = furi_thread_alloc(); + furi_thread_set_name(usb_uart->thread, "UsbUartWorker"); + furi_thread_set_stack_size(usb_uart->thread, 1024); + furi_thread_set_context(usb_uart->thread, cfg); + furi_thread_set_callback(usb_uart->thread, usb_uart_worker); + + furi_thread_start(usb_uart->thread); + } +} + +void usb_uart_disable() { + if(running == true) { + osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtStop); + furi_thread_join(usb_uart->thread); + furi_thread_free(usb_uart->thread); + free(usb_uart); + running = false; + } +} diff --git a/applications/gpio/usb_uart_bridge.h b/applications/gpio/usb_uart_bridge.h new file mode 100644 index 00000000..2fe6d1d8 --- /dev/null +++ b/applications/gpio/usb_uart_bridge.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct { + uint8_t vcp_ch; + uint8_t uart_ch; + uint32_t baudrate; +} UsbUartConfig; + +void usb_uart_enable(UsbUartConfig* cfg); + +void usb_uart_disable(); diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c old mode 100755 new mode 100644 index 1a2bb06f..5ab42875 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -6,22 +6,32 @@ #include #include +const CanvasFontParameters canvas_font_params[FontTotalNumber] = { + [FontPrimary] = {.leading_default = 12, .leading_min = 11, .height = 8, .descender = 2}, + [FontSecondary] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2}, + [FontKeyboard] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2}, + [FontBigNumbers] = {.leading_default = 18, .leading_min = 16, .height = 15, .descender = 0}, +}; + Canvas* canvas_init() { Canvas* canvas = furi_alloc(sizeof(Canvas)); furi_hal_power_insomnia_enter(); - canvas->orientation = CanvasOrientationHorizontal; + // Setup u8g2 u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - - // send init sequence to the display, display is in sleep mode after this + canvas->orientation = CanvasOrientationHorizontal; + // Initialize display u8g2_InitDisplay(&canvas->fb); - // wake up display - u8g2_ClearBuffer(&canvas->fb); + // Wake up display u8g2_SetPowerSave(&canvas->fb, 0); - u8g2_SendBuffer(&canvas->fb); + + // Clear buffer and send to device + canvas_clear(canvas); + canvas_commit(canvas); furi_hal_power_insomnia_exit(); + return canvas; } @@ -32,9 +42,12 @@ void canvas_free(Canvas* canvas) { void canvas_reset(Canvas* canvas) { furi_assert(canvas); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); + canvas_set_font_direction(canvas, CanvasFontDirectionLeftToRight); } void canvas_commit(Canvas* canvas) { @@ -86,6 +99,12 @@ uint8_t canvas_current_font_height(Canvas* canvas) { return font_height; } +CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font) { + furi_assert(canvas); + furi_assert(font < FontTotalNumber); + return (CanvasFontParameters*)&canvas_font_params[font]; +} + void canvas_clear(Canvas* canvas) { furi_assert(canvas); u8g2_ClearBuffer(&canvas->fb); @@ -96,6 +115,11 @@ void canvas_set_color(Canvas* canvas, Color color) { u8g2_SetDrawColor(&canvas->fb, color); } +void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir) { + furi_assert(canvas); + u8g2_SetFontDirection(&canvas->fb, dir); +} + void canvas_invert_color(Canvas* canvas) { canvas->fb.draw_color = !canvas->fb.draw_color; } diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h index cbf9d068..b197e928 100644 --- a/applications/gui/canvas.h +++ b/applications/gui/canvas.h @@ -20,7 +20,15 @@ typedef enum { } Color; /** Fonts enumeration */ -typedef enum { FontPrimary, FontSecondary, FontKeyboard, FontBigNumbers } Font; +typedef enum { + FontPrimary, + FontSecondary, + FontKeyboard, + FontBigNumbers, + + // Keep last for fonts number calculation + FontTotalNumber, +} Font; /** Alignment enumeration */ typedef enum { @@ -37,6 +45,22 @@ typedef enum { CanvasOrientationVertical, } CanvasOrientation; +/** Font Direction */ +typedef enum { + CanvasFontDirectionLeftToRight, + CanvasFontDirectionTopToDown, + CanvasFontDirectionRightToLeft, + CanvasFontDirectionDownToTop, +} CanvasFontDirection; + +/** Font parameters */ +typedef struct { + uint8_t leading_default; + uint8_t leading_min; + uint8_t height; + uint8_t descender; +} CanvasFontParameters; + /** Canvas anonymouse structure */ typedef struct Canvas Canvas; @@ -64,6 +88,15 @@ uint8_t canvas_height(Canvas* canvas); */ uint8_t canvas_current_font_height(Canvas* canvas); +/** Get font parameters + * + * @param canvas Canvas instance + * @param font Font + * + * @return pointer to CanvasFontParameters structure + */ +CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font); + /** Clear canvas * * @param canvas Canvas instance @@ -77,6 +110,14 @@ void canvas_clear(Canvas* canvas); */ void canvas_set_color(Canvas* canvas, Color color); +/** Set font swap + * Argument String Rotation Description + * + * @param canvas Canvas instance + * @param dir Direction font + */ +void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir); + /** Invert drawing color * * @param canvas Canvas instance diff --git a/applications/gui/elements.c b/applications/gui/elements.c old mode 100644 new mode 100755 index 4a577780..0a03ed29 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -10,6 +10,18 @@ #include #include +#include + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t leading_min; + uint8_t leading_default; + uint8_t height; + uint8_t descender; + uint8_t len; + const char* text; +} ElementTextBoxLine; void elements_progress_bar( Canvas* canvas, @@ -352,3 +364,206 @@ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { string_cat(string, "..."); } } + +void elements_text_box( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(canvas); + + ElementTextBoxLine line[ELEMENTS_MAX_LINES_NUM]; + bool bold = false; + bool mono = false; + bool inversed = false; + bool inversed_present = false; + Font current_font = FontSecondary; + Font prev_font = FontSecondary; + CanvasFontParameters* font_params = canvas_get_font_params(canvas, current_font); + + // Fill line parameters + uint8_t line_leading_min = font_params->leading_min; + uint8_t line_leading_default = font_params->leading_default; + uint8_t line_height = font_params->height; + uint8_t line_descender = font_params->descender; + uint8_t line_num = 0; + uint8_t line_width = 0; + uint8_t line_len = 0; + uint8_t total_height_min = 0; + uint8_t total_height_default = 0; + uint16_t i = 0; + bool full_text_processed = false; + + canvas_set_font(canvas, FontSecondary); + + // Fill all lines + line[0].text = text; + for(i = 0; !full_text_processed; i++) { + line_len++; + // Identify line height + if(prev_font != current_font) { + font_params = canvas_get_font_params(canvas, current_font); + line_leading_min = MAX(line_leading_min, font_params->leading_min); + line_leading_default = MAX(line_leading_default, font_params->leading_default); + line_height = MAX(line_height, font_params->height); + line_descender = MAX(line_descender, font_params->descender); + prev_font = current_font; + } + // Set the font + if(text[i] == '\e' && text[i + 1]) { + i++; + line_len++; + if(text[i] == ELEMENTS_BOLD_MARKER) { + if(bold) { + current_font = FontSecondary; + } else { + current_font = FontPrimary; + } + canvas_set_font(canvas, current_font); + bold = !bold; + } + if(text[i] == ELEMENTS_MONO_MARKER) { + if(mono) { + current_font = FontSecondary; + } else { + current_font = FontKeyboard; + } + canvas_set_font(canvas, FontKeyboard); + mono = !mono; + } + if(text[i] == ELEMENTS_INVERSED_MARKER) { + inversed_present = true; + } + continue; + } + if(text[i] != '\n') { + line_width += canvas_glyph_width(canvas, text[i]); + } + // Process new line + if(text[i] == '\n' || text[i] == '\0' || line_width > width) { + if(line_width > width) { + line_width -= canvas_glyph_width(canvas, text[i--]); + line_len--; + } + if(text[i] == '\0') { + full_text_processed = true; + } + if(inversed_present) { + line_leading_min += 1; + line_leading_default += 1; + inversed_present = false; + } + line[line_num].leading_min = line_leading_min; + line[line_num].leading_default = line_leading_default; + line[line_num].height = line_height; + line[line_num].descender = line_descender; + if(total_height_min + line_leading_min > height) { + line_num--; + break; + } + total_height_min += line_leading_min; + total_height_default += line_leading_default; + line[line_num].len = line_len; + if(horizontal == AlignCenter) { + line[line_num].x = x + (width - line_width) / 2; + } else if(horizontal == AlignRight) { + line[line_num].x = x + (width - line_width); + } else { + line[line_num].x = x; + } + line[line_num].y = total_height_min; + line_num++; + if(text[i + 1]) { + line[line_num].text = &text[i + 1]; + } + + line_leading_min = font_params->leading_min; + line_height = font_params->height; + line_descender = font_params->descender; + line_width = 0; + line_len = 0; + } + } + + // Set vertical alignment for all lines + if(full_text_processed) { + if(total_height_default < height) { + if(vertical == AlignTop) { + line[0].y = y + line[0].height; + } else if(vertical == AlignCenter) { + line[0].y = y + line[0].height + (height - total_height_default) / 2; + } else if(vertical == AlignBottom) { + line[0].y = y + line[0].height + (height - total_height_default); + } + if(line_num > 1) { + for(uint8_t i = 1; i < line_num; i++) { + line[i].y = line[i - 1].y + line[i - 1].leading_default; + } + } + } else if(line_num > 1) { + uint8_t free_pixel_num = height - total_height_min; + uint8_t fill_pixel = 0; + uint8_t j = 1; + line[0].y = line[0].height; + while(fill_pixel < free_pixel_num) { + line[j].y = line[j - 1].y + line[j - 1].leading_min + 1; + fill_pixel++; + j = j % (line_num - 1) + 1; + } + } + } + + // Draw line by line + canvas_set_font(canvas, FontSecondary); + bold = false; + mono = false; + inversed = false; + for(uint8_t i = 0; i < line_num; i++) { + for(uint8_t j = 0; j < line[i].len; j++) { + // Process format symbols + if(line[i].text[j] == ELEMENTS_BOLD_MARKER) { + if(bold) { + current_font = FontSecondary; + } else { + current_font = FontPrimary; + } + canvas_set_font(canvas, current_font); + bold = !bold; + continue; + } + if(line[i].text[j] == ELEMENTS_MONO_MARKER) { + if(mono) { + current_font = FontSecondary; + } else { + current_font = FontKeyboard; + } + canvas_set_font(canvas, current_font); + mono = !mono; + continue; + } + if(line[i].text[j] == ELEMENTS_INVERSED_MARKER) { + inversed = !inversed; + continue; + } + if(inversed) { + canvas_draw_box( + canvas, + line[i].x - 1, + line[i].y - line[i].height - 1, + canvas_glyph_width(canvas, line[i].text[j]) + 1, + line[i].height + line[i].descender + 2); + canvas_invert_color(canvas); + canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]); + canvas_invert_color(canvas); + } else { + canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]); + } + line[i].x += canvas_glyph_width(canvas, line[i].text[j]); + } + } + canvas_set_font(canvas, FontSecondary); +} diff --git a/applications/gui/elements.h b/applications/gui/elements.h old mode 100644 new mode 100755 index 32156dc2..52b26864 --- a/applications/gui/elements.h +++ b/applications/gui/elements.h @@ -16,12 +16,19 @@ extern "C" { #endif +#define ELEMENTS_MAX_LINES_NUM (7) +#define ELEMENTS_BOLD_MARKER '#' +#define ELEMENTS_MONO_MARKER '*' +#define ELEMENTS_INVERSED_MARKER '!' + /** Draw progress bar. - * @param x - progress bar position on X axis - * @param y - progress bar position on Y axis - * @param width - progress bar width - * @param progress - progress in unnamed metric - * @param total - total amount in unnamed metric + * + * @param canvas Canvas instance + * @param x progress bar position on X axis + * @param y progress bar position on Y axis + * @param width progress bar width + * @param progress progress in unnamed metric + * @param total total amount in unnamed metric */ void elements_progress_bar( Canvas* canvas, @@ -32,11 +39,13 @@ void elements_progress_bar( uint8_t total); /** Draw scrollbar on canvas at specific position. - * @param x - scrollbar position on X axis - * @param y - scrollbar position on Y axis - * @param height - scrollbar height - * @param pos - current element - * @param total - total elements + * + * @param canvas Canvas instance + * @param x scrollbar position on X axis + * @param y scrollbar position on Y axis + * @param height scrollbar height + * @param pos current element + * @param total total elements */ void elements_scrollbar_pos( Canvas* canvas, @@ -47,37 +56,49 @@ void elements_scrollbar_pos( uint16_t total); /** Draw scrollbar on canvas. - * width 3px, height equal to canvas height - * @param pos - current element of total elements - * @param total - total elements + * @note width 3px, height equal to canvas height + * + * @param canvas Canvas instance + * @param pos current element of total elements + * @param total total elements */ void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total); /** Draw rounded frame - * @param x, y - top left corner coordinates - * @param width, height - frame width and height + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height frame width and height */ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); /** Draw button in left corner - * @param str - button text + * + * @param canvas Canvas instance + * @param str button text */ void elements_button_left(Canvas* canvas, const char* str); /** Draw button in right corner - * @param str - button text + * + * @param canvas Canvas instance + * @param str button text */ void elements_button_right(Canvas* canvas, const char* str); /** Draw button in center - * @param str - button text + * + * @param canvas Canvas instance + * @param str button text */ void elements_button_center(Canvas* canvas, const char* str); /** Draw aligned multiline text - * @param x, y - coordinates based on align param - * @param horizontal, vertical - aligment of multiline text - * @param text - string (possible multiline) + * + * @param canvas Canvas instance + * @param x, y coordinates based on align param + * @param horizontal, vertical aligment of multiline text + * @param text string (possible multiline) */ void elements_multiline_text_aligned( Canvas* canvas, @@ -88,20 +109,26 @@ void elements_multiline_text_aligned( const char* text); /** Draw multiline text - * @param x, y - top left corner coordinates - * @param text - string (possible multiline) + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param text string (possible multiline) */ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text); /** Draw framed multiline text - * @param x, y - top left corner coordinates - * @param text - string (possible multiline) + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param text string (possible multiline) */ void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text); /** Draw slightly rounded frame - * @param x, y - top left corner coordinates - * @param width, height - size of frame + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height size of frame */ void elements_slightly_rounded_frame( Canvas* canvas, @@ -111,8 +138,10 @@ void elements_slightly_rounded_frame( uint8_t height); /** Draw slightly rounded box - * @param x, y - top left corner coordinates - * @param width, height - size of box + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height size of box */ void elements_slightly_rounded_box( Canvas* canvas, @@ -122,19 +151,47 @@ void elements_slightly_rounded_box( uint8_t height); /** Draw bubble frame for text - * @param x - left x coordinates - * @param y - top y coordinate - * @param width - bubble width - * @param height - bubble height + * + * @param canvas Canvas instance + * @param x left x coordinates + * @param y top y coordinate + * @param width bubble width + * @param height bubble height */ void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); /** Trim string buffer to fit width in pixels - * @param string - string to trim - * @param width - max width + * + * @param canvas Canvas instance + * @param string string to trim + * @param width max width */ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width); +/** Draw text box element + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param width width to fit text + * @param height height to fit text + * @param horizontal Align instance + * @param vertical Align instance + * @param[in] text Formatted text. The following formats are available: + * "\e#Bold text\e#" - bold font is used + * "\e*Monospaced text\e*" - monospaced font is used + * "\e#Inversed text\e#" - white text on black background + */ +void elements_text_box( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text); + #ifdef __cplusplus } #endif diff --git a/applications/gui/gui.c b/applications/gui/gui.c index a7e7f295..ff872ab4 100644 --- a/applications/gui/gui.c +++ b/applications/gui/gui.c @@ -1,5 +1,7 @@ #include "gui_i.h" +#define TAG "GuiSrv" + ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { // Iterating backward ViewPortArray_it_t it; @@ -189,8 +191,8 @@ void gui_input(Gui* gui, InputEvent* input_event) { } else if(input_event->type == InputTypePress) { gui->ongoing_input |= key_bit; } else if(!(gui->ongoing_input & key_bit)) { - FURI_LOG_W( - "Gui", + FURI_LOG_D( + TAG, "non-complementary input, discarding key: %s type: %s, sequence: %p", input_get_key_name(input_event->key), input_get_type_name(input_event->type), @@ -211,8 +213,8 @@ void gui_input(Gui* gui, InputEvent* input_event) { if(view_port && view_port == gui->ongoing_input_view_port) { view_port_input(view_port, input_event); } else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) { - FURI_LOG_W( - "Gui", + FURI_LOG_D( + TAG, "ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", gui->ongoing_input_view_port, view_port, @@ -221,8 +223,8 @@ void gui_input(Gui* gui, InputEvent* input_event) { input_event->sequence); view_port_input(gui->ongoing_input_view_port, input_event); } else { - FURI_LOG_W( - "Gui", + FURI_LOG_D( + TAG, "ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p", gui->ongoing_input_view_port, view_port, @@ -258,8 +260,7 @@ void gui_cli_screen_stream_callback(uint8_t* data, size_t size, void* context) { void gui_cli_screen_stream(Cli* cli, string_t args, void* context) { furi_assert(context); Gui* gui = context; - gui_set_framebuffer_callback_context(gui, gui); - gui_set_framebuffer_callback(gui, gui_cli_screen_stream_callback); + gui_set_framebuffer_callback(gui, gui_cli_screen_stream_callback, gui); gui_redraw(gui); // Wait for control events @@ -279,8 +280,7 @@ void gui_cli_screen_stream(Cli* cli, string_t args, void* context) { } } - gui_set_framebuffer_callback(gui, NULL); - gui_set_framebuffer_callback_context(gui, NULL); + gui_set_framebuffer_callback(gui, NULL, NULL); } void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) { @@ -387,14 +387,12 @@ 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 gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); + gui_lock(gui); gui->canvas_callback = callback; -} - -void gui_set_framebuffer_callback_context(Gui* gui, void* context) { - furi_assert(gui); gui->canvas_callback_context = context; + gui_unlock(gui); } Gui* gui_alloc() { @@ -414,7 +412,7 @@ Gui* gui_alloc() { gui->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); gui->input_events = furi_record_open("input_events"); furi_check(gui->input_events); - subscribe_pubsub(gui->input_events, gui_input_events_callback, gui); + furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); // Cli gui->cli = furi_record_open("cli"); cli_add_command( diff --git a/applications/gui/gui.h b/applications/gui/gui.h index eb68f520..acdd2211 100644 --- a/applications/gui/gui.h +++ b/applications/gui/gui.h @@ -73,15 +73,9 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); * * @param gui Gui instance * @param callback GuiCanvasCommitCallback + * @param context GuiCanvasCommitCallback context */ -void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback); - -/** Set gui canvas commit callback context - * - * @param gui Gui instance - * @param context pointer to context - */ -void gui_set_framebuffer_callback_context(Gui* gui, void* context); +void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); #ifdef __cplusplus } diff --git a/applications/gui/gui_i.h b/applications/gui/gui_i.h index cfbf604f..5b32f602 100644 --- a/applications/gui/gui_i.h +++ b/applications/gui/gui_i.h @@ -50,7 +50,7 @@ struct Gui { // Input osMessageQueueId_t input_queue; - PubSub* input_events; + FuriPubSub* input_events; uint8_t ongoing_input; ViewPort* ongoing_input_view_port; diff --git a/applications/gui/icon_animation.c b/applications/gui/icon_animation.c index 22089d63..3ed0973d 100644 --- a/applications/gui/icon_animation.c +++ b/applications/gui/icon_animation.c @@ -2,7 +2,6 @@ #include "icon_i.h" #include -#include IconAnimation* icon_animation_alloc(const Icon* icon) { furi_assert(icon); diff --git a/applications/gui/modules/button_menu.c b/applications/gui/modules/button_menu.c index 81958f0d..0dbb1366 100644 --- a/applications/gui/modules/button_menu.c +++ b/applications/gui/modules/button_menu.c @@ -246,7 +246,7 @@ void button_menu_clean(ButtonMenu* button_menu) { with_view_model( button_menu->view, (ButtonMenuModel * model) { - ButtonMenuItemArray_clean(model->items); + ButtonMenuItemArray_reset(model->items); model->position = 0; return true; }); diff --git a/applications/gui/modules/button_panel.c b/applications/gui/modules/button_panel.c index b4cffe05..65fbb90c 100644 --- a/applications/gui/modules/button_panel.c +++ b/applications/gui/modules/button_panel.c @@ -139,8 +139,8 @@ void button_panel_clean(ButtonPanel* button_panel) { } model->reserve_x = 0; model->reserve_y = 0; - LabelList_clean(model->labels); - ButtonMatrix_clean(model->button_matrix); + LabelList_reset(model->labels); + ButtonMatrix_reset(model->button_matrix); return true; }); } @@ -150,8 +150,8 @@ static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, siz furi_check(x < model->reserve_x); furi_check(y < model->reserve_y); - ButtonArray_t* button_array = ButtonMatrix_get_at(model->button_matrix, x); - ButtonItem** button_item = ButtonArray_get_at(*button_array, y); + ButtonArray_t* button_array = ButtonMatrix_safe_get(model->button_matrix, x); + ButtonItem** button_item = ButtonArray_safe_get(*button_array, y); return button_item; } diff --git a/applications/gui/modules/code_input.c b/applications/gui/modules/code_input.c new file mode 100644 index 00000000..62f5118d --- /dev/null +++ b/applications/gui/modules/code_input.c @@ -0,0 +1,475 @@ +#include "code_input.h" +#include +#include + +#define MAX_CODE_LEN 10 + +struct CodeInput { + View* view; +}; + +typedef enum { + CodeInputStateVerify, + CodeInputStateUpdate, + CodeInputStateTotal, +} CodeInputStateEnum; + +typedef enum { + CodeInputFirst, + CodeInputSecond, + CodeInputTotal, +} CodeInputsEnum; + +typedef struct { + uint8_t state; + uint8_t current; + bool ext_update; + + uint8_t input_length[CodeInputTotal]; + uint8_t local_buffer[CodeInputTotal][MAX_CODE_LEN]; + + CodeInputOkCallback ok_callback; + CodeInputFailCallback fail_callback; + void* callback_context; + + const char* header; + + uint8_t* ext_buffer; + uint8_t* ext_buffer_length; +} CodeInputModel; + +static const Icon* keys_assets[] = { + [InputKeyUp] = &I_ButtonUp_7x4, + [InputKeyDown] = &I_ButtonDown_7x4, + [InputKeyRight] = &I_ButtonRight_4x7, + [InputKeyLeft] = &I_ButtonLeft_4x7, +}; + +/** + * @brief Compare buffers + * + * @param in Input buffer pointer + * @param len_in Input array length + * @param src Source buffer pointer + * @param len_src Source array length + */ + +bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src) { + bool result = false; + do { + result = (len_in && len_src); + if(!result) { + break; + } + result = (len_in == len_src); + if(!result) { + break; + } + for(size_t i = 0; i < len_in; i++) { + result = (in[i] == src[i]); + if(!result) { + break; + } + } + } while(false); + + return result; +} + +/** + * @brief Compare local buffers + * + * @param model + */ +static bool code_input_compare_local(CodeInputModel* model) { + uint8_t* source = model->local_buffer[CodeInputFirst]; + size_t source_length = model->input_length[CodeInputFirst]; + + uint8_t* input = model->local_buffer[CodeInputSecond]; + size_t input_length = model->input_length[CodeInputSecond]; + + return code_input_compare(input, input_length, source, source_length); +} + +/** + * @brief Compare ext with local + * + * @param model + */ +static bool code_input_compare_ext(CodeInputModel* model) { + uint8_t* input = model->local_buffer[CodeInputFirst]; + size_t input_length = model->input_length[CodeInputFirst]; + + uint8_t* source = model->ext_buffer; + size_t source_length = *model->ext_buffer_length; + + return code_input_compare(input, input_length, source, source_length); +} + +/** + * @brief Set ext buffer + * + * @param model + */ +static void code_input_set_ext(CodeInputModel* model) { + *model->ext_buffer_length = model->input_length[CodeInputFirst]; + for(size_t i = 0; i <= model->input_length[CodeInputFirst]; i++) { + model->ext_buffer[i] = model->local_buffer[CodeInputFirst][i]; + } +} + +/** + * @brief Draw input sequence + * + * @param canvas + * @param buffer + * @param length + * @param x + * @param y + * @param active + */ +static void code_input_draw_sequence( + Canvas* canvas, + uint8_t* buffer, + uint8_t length, + uint8_t x, + uint8_t y, + bool active) { + uint8_t pos_x = x + 6; + uint8_t pos_y = y + 3; + + if(active) canvas_draw_icon(canvas, x - 4, y + 5, &I_ButtonRightSmall_3x5); + + elements_slightly_rounded_frame(canvas, x, y, 116, 15); + + for(size_t i = 0; i < length; i++) { + // maybe symmetrical assets? :-/ + uint8_t offset_y = buffer[i] < 2 ? 2 + (buffer[i] * 2) : 1; + canvas_draw_icon(canvas, pos_x, pos_y + offset_y, keys_assets[buffer[i]]); + pos_x += buffer[i] > 1 ? 9 : 11; + } +} + +/** + * @brief Reset input count + * + * @param model + */ +static void code_input_reset_count(CodeInputModel* model) { + model->input_length[model->current] = 0; +} + +/** + * @brief Call input callback + * + * @param model + */ +static void code_input_call_ok_callback(CodeInputModel* model) { + if(model->ok_callback != NULL) { + model->ok_callback(model->callback_context); + } +} + +/** + * @brief Call changed callback + * + * @param model + */ +static void code_input_call_fail_callback(CodeInputModel* model) { + if(model->fail_callback != NULL) { + model->fail_callback(model->callback_context); + } +} + +/** + * @brief Handle Back button + * + * @param model + */ +static bool code_input_handle_back(CodeInputModel* model) { + if(model->current && !model->input_length[model->current]) { + --model->current; + return true; + } + + if(model->input_length[model->current]) { + code_input_reset_count(model); + return true; + } + + code_input_call_fail_callback(model); + return false; +} + +/** + * @brief Handle OK button + * + * @param model + */ +static void code_input_handle_ok(CodeInputModel* model) { + switch(model->state) { + case CodeInputStateVerify: + + if(code_input_compare_ext(model)) { + if(model->ext_update) { + model->state = CodeInputStateUpdate; + } else { + code_input_call_ok_callback(model); + } + } + code_input_reset_count(model); + break; + + case CodeInputStateUpdate: + + if(!model->current && model->input_length[model->current]) { + model->current++; + } else { + if(code_input_compare_local(model)) { + if(model->ext_update) { + code_input_set_ext(model); + } + code_input_call_ok_callback(model); + } else { + code_input_reset_count(model); + } + } + + break; + default: + break; + } +} + +/** + * @brief Handle input + * + * @param model + * @param key + */ + +size_t code_input_push(uint8_t* buffer, size_t length, InputKey key) { + buffer[length] = key; + length = CLAMP(length + 1, MAX_CODE_LEN, 0); + return length; +} + +/** + * @brief Handle D-pad keys + * + * @param model + * @param key + */ +static void code_input_handle_dpad(CodeInputModel* model, InputKey key) { + uint8_t at = model->current; + size_t new_length = code_input_push(model->local_buffer[at], model->input_length[at], key); + model->input_length[at] = new_length; +} + +/** + * @brief Draw callback + * + * @param canvas + * @param _model + */ +static void code_input_view_draw_callback(Canvas* canvas, void* _model) { + CodeInputModel* model = _model; + uint8_t y_offset = 0; + if(!strlen(model->header)) y_offset = 5; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str(canvas, 2, 9, model->header); + + canvas_set_font(canvas, FontSecondary); + + switch(model->state) { + case CodeInputStateVerify: + code_input_draw_sequence( + canvas, + model->local_buffer[CodeInputFirst], + model->input_length[CodeInputFirst], + 6, + 30 - y_offset, + true); + break; + case CodeInputStateUpdate: + code_input_draw_sequence( + canvas, + model->local_buffer[CodeInputFirst], + model->input_length[CodeInputFirst], + 6, + 14 - y_offset, + !model->current); + code_input_draw_sequence( + canvas, + model->local_buffer[CodeInputSecond], + model->input_length[CodeInputSecond], + 6, + 44 - y_offset, + model->current); + + if(model->current) canvas_draw_str(canvas, 2, 39 - y_offset, "Repeat code"); + + break; + default: + break; + } +} + +/** + * @brief Input callback + * + * @param event + * @param context + * @return true + * @return false + */ +static bool code_input_view_input_callback(InputEvent* event, void* context) { + CodeInput* code_input = context; + furi_assert(code_input); + bool consumed = false; + + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + switch(event->key) { + case InputKeyBack: + with_view_model( + code_input->view, (CodeInputModel * model) { + consumed = code_input_handle_back(model); + return true; + }); + break; + + case InputKeyOk: + with_view_model( + code_input->view, (CodeInputModel * model) { + code_input_handle_ok(model); + return true; + }); + consumed = true; + break; + default: + + with_view_model( + code_input->view, (CodeInputModel * model) { + code_input_handle_dpad(model, event->key); + return true; + }); + consumed = true; + break; + } + } + + return consumed; +} + +/** + * @brief Reset all input-related data in model + * + * @param model CodeInputModel + */ +static void code_input_reset_model_input_data(CodeInputModel* model) { + model->current = 0; + model->input_length[CodeInputFirst] = 0; + model->input_length[CodeInputSecond] = 0; + model->ext_buffer = NULL; + model->ext_update = false; + model->state = 0; +} + +/** + * @brief Allocate and initialize code input. This code input is used to enter codes. + * + * @return CodeInput instance pointer + */ +CodeInput* code_input_alloc() { + CodeInput* code_input = furi_alloc(sizeof(CodeInput)); + code_input->view = view_alloc(); + view_set_context(code_input->view, code_input); + view_allocate_model(code_input->view, ViewModelTypeLocking, sizeof(CodeInputModel)); + view_set_draw_callback(code_input->view, code_input_view_draw_callback); + view_set_input_callback(code_input->view, code_input_view_input_callback); + + with_view_model( + code_input->view, (CodeInputModel * model) { + model->header = ""; + model->ok_callback = NULL; + model->fail_callback = NULL; + model->callback_context = NULL; + code_input_reset_model_input_data(model); + return true; + }); + + return code_input; +} + +/** + * @brief Deinitialize and free code input + * + * @param code_input Code input instance + */ +void code_input_free(CodeInput* code_input) { + furi_assert(code_input); + view_free(code_input->view); + free(code_input); +} + +/** + * @brief Get code input view + * + * @param code_input code input instance + * @return View instance that can be used for embedding + */ +View* code_input_get_view(CodeInput* code_input) { + furi_assert(code_input); + return code_input->view; +} + +/** + * @brief Set code input callbacks + * + * @param code_input code input instance + * @param ok_callback input callback fn + * @param fail_callback code match callback fn + * @param callback_context callback context + * @param buffer buffer + * @param buffer_length ptr to buffer length uint + * @param ext_update true to update buffer + */ +void code_input_set_result_callback( + CodeInput* code_input, + CodeInputOkCallback ok_callback, + CodeInputFailCallback fail_callback, + void* callback_context, + uint8_t* buffer, + uint8_t* buffer_length, + bool ext_update) { + with_view_model( + code_input->view, (CodeInputModel * model) { + code_input_reset_model_input_data(model); + model->ok_callback = ok_callback; + model->fail_callback = fail_callback; + model->callback_context = callback_context; + + model->ext_buffer = buffer; + model->ext_buffer_length = buffer_length; + model->state = (*buffer_length == 0) ? 1 : 0; + model->ext_update = ext_update; + + return true; + }); +} + +/** + * @brief Set code input header text + * + * @param code_input code input instance + * @param text text to be shown + */ +void code_input_set_header_text(CodeInput* code_input, const char* text) { + with_view_model( + code_input->view, (CodeInputModel * model) { + model->header = text; + return true; + }); +} diff --git a/applications/gui/modules/code_input.h b/applications/gui/modules/code_input.h new file mode 100644 index 00000000..d6a43fc1 --- /dev/null +++ b/applications/gui/modules/code_input.h @@ -0,0 +1,91 @@ +/** + * @file code_input.h + * GUI: CodeInput keyboard view module API + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Code input anonymous structure */ +typedef struct CodeInput CodeInput; + +/** callback that is executed when entered code matches ext buffer */ +typedef void (*CodeInputOkCallback)(void* context); + +/** callback that is executed when entered code does not matches ext buffer */ +typedef void (*CodeInputFailCallback)(void* context); + +/** Allocate and initialize code input. This code input is used to enter codes. + * + * @return CodeInput instance pointer + */ +CodeInput* code_input_alloc(); + +/** Deinitialize and free code input + * + * @param code_input Code input instance + */ +void code_input_free(CodeInput* code_input); + +/** Get code input view + * + * @param code_input code input instance + * + * @return View instance that can be used for embedding + */ +View* code_input_get_view(CodeInput* code_input); + +/** Set code input result callback + * + * @param code_input code input instance + * @param ok_callback ok callback fn + * @param fail_callback fail callback fn + * @param callback_context callback context + * @param buffer buffer to use + * @param buffer_length buffer length + * @param update set true to update buffer + */ +void code_input_set_result_callback( + CodeInput* code_input, + CodeInputOkCallback ok_callback, + CodeInputFailCallback fail_callback, + void* callback_context, + uint8_t* buffer, + uint8_t* buffer_length, + bool update); + +/** Set code input header text + * + * @param code_input code input instance + * @param text text to be shown + */ +void code_input_set_header_text(CodeInput* code_input, const char* text); + +/** Compare two buffers + * + * @param in buffer to compare to source + * @param len_in length of input buffer + * @param src source buffer + * @param len_src length of insourceput buffer + * @return true if buffers match + */ + +bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src); + +/** Push input into the end of array + * + * @param buffer buffer + * @param length length of buffer + * @param key input key + * @return new length of input buffer + */ +size_t code_input_push(uint8_t* buffer, size_t length, InputKey key); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/gui/modules/menu.c b/applications/gui/modules/menu.c index 6450b1d3..110ff0c1 100644 --- a/applications/gui/modules/menu.c +++ b/applications/gui/modules/menu.c @@ -178,7 +178,7 @@ void menu_clean(Menu* menu) { furi_assert(menu); with_view_model( menu->view, (MenuModel * model) { - MenuItemArray_clean(model->items); + MenuItemArray_reset(model->items); model->position = 0; return true; }); diff --git a/applications/gui/modules/submenu.c b/applications/gui/modules/submenu.c index 105ebd6a..60a1b092 100644 --- a/applications/gui/modules/submenu.c +++ b/applications/gui/modules/submenu.c @@ -174,7 +174,7 @@ void submenu_clean(Submenu* submenu) { with_view_model( submenu->view, (SubmenuModel * model) { - SubmenuItemArray_clean(model->items); + SubmenuItemArray_reset(model->items); model->position = 0; model->window_position = 0; model->header = NULL; diff --git a/applications/gui/modules/variable-item-list.c b/applications/gui/modules/variable-item-list.c old mode 100755 new mode 100644 index 800e0602..39f646b6 --- a/applications/gui/modules/variable-item-list.c +++ b/applications/gui/modules/variable-item-list.c @@ -84,6 +84,40 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); } +void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + uint8_t position = index; + if(position >= VariableItemArray_size(model->items)) { + position = 0; + } + + model->position = position; + model->window_position = position; + + if(model->window_position > 0) { + model->window_position -= 1; + } + + if(VariableItemArray_size(model->items) <= 4) { + model->window_position = 0; + } else { + if(model->window_position >= (VariableItemArray_size(model->items) - 4)) { + model->window_position = (VariableItemArray_size(model->items) - 4); + } + } + + return true; + }); +} + +uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) { + VariableItemListModel* model = view_get_model(variable_item_list->view); + uint8_t idx = model->position; + view_commit_model(variable_item_list->view, false); + return idx; +} + static bool variable_item_list_input_callback(InputEvent* event, void* context) { VariableItemList* variable_item_list = context; furi_assert(variable_item_list); @@ -261,7 +295,7 @@ void variable_item_list_clean(VariableItemList* variable_item_list) { VariableItemArray_next(it)) { string_clear(VariableItemArray_ref(it)->current_value_text); } - VariableItemArray_clean(model->items); + VariableItemArray_reset(model->items); return false; }); } @@ -323,4 +357,4 @@ uint8_t variable_item_get_current_value_index(VariableItem* item) { void* variable_item_get_context(VariableItem* item) { return item->context; -} \ No newline at end of file +} diff --git a/applications/gui/modules/variable-item-list.h b/applications/gui/modules/variable-item-list.h index e1e3cbf7..b67a02e6 100755 --- a/applications/gui/modules/variable-item-list.h +++ b/applications/gui/modules/variable-item-list.h @@ -70,6 +70,10 @@ void variable_item_list_set_enter_callback( VariableItemListEnterCallback callback, void* context); +void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index); + +uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list); + /** Set item current selected index * * @param item VariableItem* instance diff --git a/applications/gui/modules/widget.c b/applications/gui/modules/widget.c index 92ca9133..58ca7db2 100755 --- a/applications/gui/modules/widget.c +++ b/applications/gui/modules/widget.c @@ -81,7 +81,7 @@ void widget_clear(Widget* widget) { element->free(element); ElementArray_next(it); } - ElementArray_clean(model->element); + ElementArray_reset(model->element); return true; }); } @@ -146,6 +146,21 @@ void widget_add_string_element( widget_add_element(widget, string_element); } +void widget_add_text_box_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(widget); + WidgetElement* text_box_element = + widget_element_text_box_create(x, y, width, height, horizontal, vertical, text); + widget_add_element(widget, text_box_element); +} + void widget_add_button_element( Widget* widget, GuiButtonType button_type, diff --git a/applications/gui/modules/widget.h b/applications/gui/modules/widget.h index 09177c57..af881be1 100755 --- a/applications/gui/modules/widget.h +++ b/applications/gui/modules/widget.h @@ -75,6 +75,30 @@ void widget_add_string_element( Font font, const char* text); +/** Add Text Box Element + * + * @param widget Widget instance + * @param x x coordinate + * @param y y coordinate + * @param width width to fit text + * @param height height to fit text + * @param horizontal Align instance + * @param vertical Align instance + * @param[in] text Formatted text. The following formats are available: + * "\e#Bold text\e#" - bold font is used + * "\e*Monospaced text\e*" - monospaced font is used + * "\e#Inversed text\e#" - white text on black background + */ +void widget_add_text_box_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text); + /** Add Button Element * * @param widget Widget instance diff --git a/applications/gui/modules/widget_elements/widget_element_i.h b/applications/gui/modules/widget_elements/widget_element_i.h index bbc58ff9..d7b4e463 100755 --- a/applications/gui/modules/widget_elements/widget_element_i.h +++ b/applications/gui/modules/widget_elements/widget_element_i.h @@ -52,6 +52,16 @@ WidgetElement* widget_element_string_create( Font font, const char* text); +/** Create text box element */ +WidgetElement* widget_element_text_box_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text); + /** Create button element */ WidgetElement* widget_element_button_create( GuiButtonType button_type, diff --git a/applications/gui/modules/widget_elements/widget_element_text_box.c b/applications/gui/modules/widget_elements/widget_element_text_box.c new file mode 100644 index 00000000..9ee33188 --- /dev/null +++ b/applications/gui/modules/widget_elements/widget_element_text_box.c @@ -0,0 +1,71 @@ +#include "widget_element_i.h" +#include +#include + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t width; + uint8_t height; + Align horizontal; + Align vertical; + string_t text; +} GuiTextBoxModel; + +static void gui_text_box_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiTextBoxModel* model = element->model; + + if(string_size(model->text)) { + elements_text_box( + canvas, + model->x, + model->y, + model->width, + model->height, + model->horizontal, + model->vertical, + string_get_cstr(model->text)); + } +} + +static void gui_text_box_free(WidgetElement* gui_string) { + furi_assert(gui_string); + + GuiTextBoxModel* model = gui_string->model; + string_clear(model->text); + free(gui_string->model); + free(gui_string); +} + +WidgetElement* widget_element_text_box_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(text); + + // Allocate and init model + GuiTextBoxModel* model = furi_alloc(sizeof(GuiTextBoxModel)); + model->x = x; + model->y = y; + model->width = width; + model->height = height; + model->horizontal = horizontal; + model->vertical = vertical; + string_init_set_str(model->text, text); + + // Allocate and init Element + WidgetElement* gui_string = furi_alloc(sizeof(WidgetElement)); + gui_string->parent = NULL; + gui_string->input = NULL; + gui_string->draw = gui_text_box_draw; + gui_string->free = gui_text_box_free; + gui_string->model = model; + + return gui_string; +} diff --git a/applications/gui/view_dispatcher.c b/applications/gui/view_dispatcher.c index c0246d97..f12cfe8b 100644 --- a/applications/gui/view_dispatcher.c +++ b/applications/gui/view_dispatcher.c @@ -1,5 +1,7 @@ #include "view_dispatcher_i.h" +#define TAG "ViewDispatcher" + ViewDispatcher* view_dispatcher_alloc() { ViewDispatcher* view_dispatcher = furi_alloc(sizeof(ViewDispatcher)); @@ -236,8 +238,8 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e } else if(event->type == InputTypeRelease) { view_dispatcher->ongoing_input &= ~key_bit; } else if(!(view_dispatcher->ongoing_input & key_bit)) { - FURI_LOG_W( - "ViewDispatcher", + FURI_LOG_D( + TAG, "non-complementary input, discarding key: %s, type: %s, sequence: %p", input_get_key_name(event->key), input_get_type_name(event->type), @@ -275,8 +277,8 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e } } } else if(view_dispatcher->ongoing_input_view && event->type == InputTypeRelease) { - FURI_LOG_W( - "ViewDispatcher", + FURI_LOG_D( + TAG, "View changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", view_dispatcher->ongoing_input_view, view_dispatcher->current_view, diff --git a/applications/ibutton/ibutton-app.cpp b/applications/ibutton/ibutton-app.cpp index 3491c95e..99454cf0 100644 --- a/applications/ibutton/ibutton-app.cpp +++ b/applications/ibutton/ibutton-app.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include const char* iButtonApp::app_folder = "/any/ibutton"; const char* iButtonApp::app_extension = ".ibtn"; @@ -48,8 +48,8 @@ iButtonApp::iButtonApp() iButtonApp::~iButtonApp() { for(std::map::iterator it = scenes.begin(); it != scenes.end(); ++it) { delete it->second; - scenes.erase(it); } + scenes.clear(); delete key_worker; furi_hal_power_insomnia_exit(); @@ -191,7 +191,7 @@ bool iButtonApp::save_key(const char* key_name) { // Create ibutton directory if necessary make_app_folder(); - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); string_t key_file_name; bool result = false; string_init(key_file_name); @@ -207,27 +207,30 @@ bool iButtonApp::save_key(const char* key_name) { string_printf(key_file_name, "%s/%s%s", app_folder, key.get_name(), app_extension); // Open file for write - if(!file.new_write(string_get_cstr(key_file_name))) break; + if(!flipper_file_open_always(file, string_get_cstr(key_file_name))) break; // Write header - if(!file.write_header_cstr(iButtonApp::app_filetype, 1)) break; + if(!flipper_file_write_header_cstr(file, iButtonApp::app_filetype, 1)) break; // Write key type - if(!file.write_comment_cstr("Key type can be Cyfral, Dallas or Metakom")) break; + if(!flipper_file_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom")) + break; const char* key_type = key.get_key_type_string_by_type(key.get_key_type()); - if(!file.write_string_cstr("Key type", key_type)) break; + if(!flipper_file_write_string_cstr(file, "Key type", key_type)) break; // Write data - if(!file.write_comment_cstr( - "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) + if(!flipper_file_write_comment_cstr( + file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) break; - if(!file.write_hex_array("Data", key.get_data(), key.get_type_data_size())) break; + if(!flipper_file_write_hex(file, "Data", key.get_data(), key.get_type_data_size())) break; result = true; } while(false); - file.close(); + flipper_file_close(file); + flipper_file_free(file); + string_clear(key_file_name); if(!result) { @@ -238,28 +241,29 @@ bool iButtonApp::save_key(const char* key_name) { } bool iButtonApp::load_key_data(string_t key_path) { - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); bool result = false; string_t data; string_init(data); do { - if(!file.open_read(string_get_cstr(key_path))) break; + if(!flipper_file_open_existing(file, string_get_cstr(key_path))) break; // header uint32_t version; - if(!file.read_header(data, &version)) break; + if(!flipper_file_read_header(file, data, &version)) break; if(string_cmp_str(data, iButtonApp::app_filetype) != 0) break; if(version != 1) break; // key type iButtonKeyType type; - if(!file.read_string("Key type", data)) break; + if(!flipper_file_read_string(file, "Key type", data)) break; if(!key.get_key_type_by_type_string(string_get_cstr(data), &type)) break; // key data uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; - if(!file.read_hex_array("Data", key_data, key.get_type_data_size_by_type(type))) break; + if(!flipper_file_read_hex(file, "Data", key_data, key.get_type_data_size_by_type(type))) + break; key.set_type(type); key.set_data(key_data, IBUTTON_KEY_DATA_SIZE); @@ -267,7 +271,8 @@ bool iButtonApp::load_key_data(string_t key_path) { result = true; } while(false); - file.close(); + flipper_file_close(file); + flipper_file_free(file); string_clear(data); if(!result) { diff --git a/applications/input/input.c b/applications/input/input.c index 62be3dba..1d160e55 100644 --- a/applications/input/input.c +++ b/applications/input/input.c @@ -28,11 +28,11 @@ void input_press_timer_callback(void* arg) { input_pin->press_counter++; if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) { event.type = InputTypeLong; - notify_pubsub(&input->event_pubsub, &event); + furi_pubsub_publish(input->event_pubsub, &event); } else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) { input_pin->press_counter--; event.type = InputTypeRepeat; - notify_pubsub(&input->event_pubsub, &event); + furi_pubsub_publish(input->event_pubsub, &event); } } @@ -89,7 +89,7 @@ void input_cli_send(Cli* cli, string_t args, void* context) { return; } // Publish input event - notify_pubsub(&input->event_pubsub, &event); + furi_pubsub_publish(input->event_pubsub, &event); } const char* input_get_key_name(InputKey key) { @@ -120,8 +120,8 @@ const char* input_get_type_name(InputType type) { int32_t input_srv() { input = furi_alloc(sizeof(Input)); input->thread = osThreadGetId(); - init_pubsub(&input->event_pubsub); - furi_record_create("input_events", &input->event_pubsub); + input->event_pubsub = furi_pubsub_alloc(); + furi_record_create("input_events", input->event_pubsub); input->cli = furi_record_open("cli"); if(input->cli) { @@ -168,14 +168,14 @@ int32_t input_srv() { input_timer_stop(input->pin_states[i].press_timer); if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { event.type = InputTypeShort; - notify_pubsub(&input->event_pubsub, &event); + furi_pubsub_publish(input->event_pubsub, &event); } input->pin_states[i].press_counter = 0; } // Send Press/Release event event.type = input->pin_states[i].state ? InputTypePress : InputTypeRelease; - notify_pubsub(&input->event_pubsub, &event); + furi_pubsub_publish(input->event_pubsub, &event); } } diff --git a/applications/input/input.h b/applications/input/input.h index d848b1c6..08723795 100644 --- a/applications/input/input.h +++ b/applications/input/input.h @@ -18,7 +18,7 @@ typedef enum { InputTypeRepeat, /**< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ } InputType; -/** Input Event, dispatches with PubSub */ +/** Input Event, dispatches with FuriPubSub */ typedef struct { uint32_t sequence; InputKey key; diff --git a/applications/input/input_i.h b/applications/input/input_i.h index db314dad..2111de7c 100644 --- a/applications/input/input_i.h +++ b/applications/input/input_i.h @@ -6,8 +6,6 @@ #pragma once #include "input.h" -#include -#include #include #include #include @@ -35,7 +33,7 @@ typedef struct { /** Input state */ typedef struct { osThreadId_t thread; - PubSub event_pubsub; + FuriPubSub* event_pubsub; InputPinState* pin_states; Cli* cli; volatile uint32_t counter; diff --git a/applications/irda/irda-app-file-parser.cpp b/applications/irda/irda-app-file-parser.cpp index 3ab14fc0..b556fb6c 100644 --- a/applications/irda/irda-app-file-parser.cpp +++ b/applications/irda/irda-app-file-parser.cpp @@ -11,6 +11,8 @@ #include #include +#define TAG "IrdaFileParser" + bool IrdaAppFileParser::open_irda_file_read(const char* name) { std::string full_filename; if(name[0] != '/') @@ -154,11 +156,7 @@ std::unique_ptr if(!irda_is_protocol_valid((IrdaProtocol)protocol)) { size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); FURI_LOG_E( - "IrdaFileParser", - "Unknown protocol(\'%.*s...\'): \'%s\'", - end_of_str, - str.c_str(), - protocol_name); + TAG, "Unknown protocol(\'%.*s...\'): \'%s\'", end_of_str, str.c_str(), protocol_name); return nullptr; } @@ -167,7 +165,7 @@ std::unique_ptr if(address != (address & address_mask)) { size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); FURI_LOG_E( - "IrdaFileParser", + TAG, "Signal(\'%.*s...\'): address is too long (mask for this protocol is 0x%08X): 0x%X", end_of_str, str.c_str(), @@ -181,7 +179,7 @@ std::unique_ptr if(command != (command & command_mask)) { size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); FURI_LOG_E( - "IrdaFileParser", + TAG, "Signal(\'%.*s...\'): command is too long (mask for this protocol is 0x%08X): 0x%X", end_of_str, str.c_str(), @@ -256,7 +254,7 @@ std::unique_ptr if((frequency < IRDA_MIN_FREQUENCY) || (frequency > IRDA_MAX_FREQUENCY)) { size_t end_of_str = MIN(string.find_last_not_of(" \t\r\n") + 1, (size_t)30); FURI_LOG_E( - "IrdaFileParser", + TAG, "RAW signal(\'%.*s...\'): frequency is out of bounds (%ld-%ld): %ld", end_of_str, string.c_str(), @@ -269,7 +267,7 @@ std::unique_ptr if((duty_cycle == 0) || (duty_cycle > 100)) { size_t end_of_str = MIN(string.find_last_not_of(" \t\r\n") + 1, (size_t)30); FURI_LOG_E( - "IrdaFileParser", + TAG, "RAW signal(\'%.*s...\'): duty cycle is out of bounds (0-100): %ld", end_of_str, string.c_str(), @@ -283,8 +281,7 @@ std::unique_ptr if(last_valid_ch != std::string_view::npos) { str.remove_suffix(str.size() - last_valid_ch - 1); } else { - FURI_LOG_E( - "IrdaFileParser", "RAW signal(\'%.*s\'): no timings", header_len, string.c_str()); + FURI_LOG_E(TAG, "RAW signal(\'%.*s\'): no timings", header_len, string.c_str()); return nullptr; } @@ -303,7 +300,7 @@ std::unique_ptr parsed = std::sscanf(str.data(), "%9s", buf); if(parsed != 1) { FURI_LOG_E( - "IrdaFileParser", + TAG, "RAW signal(\'%.*s...\'): failed on timing[%ld] \'%*s\'", header_len, string.c_str(), @@ -318,7 +315,7 @@ std::unique_ptr int value = atoi(buf); if(value <= 0) { FURI_LOG_E( - "IrdaFileParser", + TAG, "RAW signal(\'%.*s...\'): failed on timing[%ld] \'%s\'", header_len, string.c_str(), @@ -330,7 +327,7 @@ std::unique_ptr if(raw_signal.timings_cnt >= max_raw_timings_in_signal) { FURI_LOG_E( - "IrdaFileParser", + TAG, "RAW signal(\'%.*s...\'): too much timings (max %ld)", header_len, string.c_str(), diff --git a/applications/irda/irda-app.cpp b/applications/irda/irda-app.cpp index 042d237c..7c7a5cf3 100644 --- a/applications/irda/irda-app.cpp +++ b/applications/irda/irda-app.cpp @@ -211,10 +211,12 @@ void IrdaApp::notify_red_blink() { notification_message(notification, &sequence_blink_red_10); } -void IrdaApp::notify_space_blink() { +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, @@ -261,10 +263,6 @@ void IrdaApp::notify_blink_green() { notification_message(notification, &sequence); } -void IrdaApp::notify_double_vibro() { - notification_message(notification, &sequence_double_vibro); -} - void IrdaApp::notify_green_on() { notification_message(notification, &sequence_set_only_green_255); } diff --git a/applications/irda/irda-app.h b/applications/irda/irda-app.h index 904bead3..adc53b45 100644 --- a/applications/irda/irda-app.h +++ b/applications/irda/irda-app.h @@ -77,8 +77,7 @@ public: void notify_success(); void notify_red_blink(); - void notify_space_blink(); - void notify_double_vibro(); + void notify_sent_just_learnt(); void notify_green_on(); void notify_green_off(); void notify_click(); diff --git a/applications/irda/scene/irda-app-scene-learn-success.cpp b/applications/irda/scene/irda-app-scene-learn-success.cpp index 030ca0e3..59a81727 100644 --- a/applications/irda/scene/irda-app-scene-learn-success.cpp +++ b/applications/irda/scene/irda-app-scene-learn-success.cpp @@ -23,16 +23,24 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { 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 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( 1, "A: 0x%0*lX\nC: 0x%0*lX\n", - ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), + adr_digits, message->address, - ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), + cmd_digits, message->command); - dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, app->get_text_store(1), 75, 23, AlignLeft, AlignTop); + + dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 7, AlignCenter, AlignCenter); + dialog_ex_set_text( + dialog_ex, app->get_text_store(1), label_x_offset, 34, AlignLeft, AlignCenter); } else { dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter); app->set_text_store(0, "%d samples", signal.get_raw_signal().timings_cnt); @@ -42,7 +50,7 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "Save"); dialog_ex_set_center_button_text(dialog_ex, "Send"); - dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinExcited_64x63); + dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63); dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); dialog_ex_set_context(dialog_ex, app); @@ -62,7 +70,7 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); break; case DialogExResultCenter: { - app->notify_space_blink(); + app->notify_sent_just_learnt(); auto signal = app->get_received_signal(); signal.transmit(); break; diff --git a/applications/irda/scene/irda-app-scene-learn.cpp b/applications/irda/scene/irda-app-scene-learn.cpp index cbbbbe7a..b044215f 100644 --- a/applications/irda/scene/irda-app-scene-learn.cpp +++ b/applications/irda/scene/irda-app-scene-learn.cpp @@ -39,10 +39,6 @@ void IrdaAppSceneLearn::on_enter(IrdaApp* app) { popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); popup_set_callback(popup, NULL); - if(app->get_learn_new_remote()) { - app->notify_double_vibro(); - } - view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); } diff --git a/applications/lfrfid/helpers/rfid-timer-emulator.cpp b/applications/lfrfid/helpers/rfid-timer-emulator.cpp index 05afb146..56c614f6 100644 --- a/applications/lfrfid/helpers/rfid-timer-emulator.cpp +++ b/applications/lfrfid/helpers/rfid-timer-emulator.cpp @@ -10,8 +10,9 @@ RfidTimerEmulator::~RfidTimerEmulator() { for(it = encoders.begin(); it != encoders.end(); ++it) { delete it->second; - encoders.erase(it); } + + encoders.clear(); } void RfidTimerEmulator::start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size) { diff --git a/applications/lfrfid/lfrfid-app.cpp b/applications/lfrfid/lfrfid-app.cpp index 92ddaa63..a64b1380 100644 --- a/applications/lfrfid/lfrfid-app.cpp +++ b/applications/lfrfid/lfrfid-app.cpp @@ -16,8 +16,8 @@ #include "scene/lfrfid-app-scene-delete-confirm.h" #include "scene/lfrfid-app-scene-delete-success.h" -#include -#include +#include +#include const char* LfRfidApp::app_folder = "/any/lfrfid"; const char* LfRfidApp::app_extension = ".rfid"; @@ -119,17 +119,17 @@ bool LfRfidApp::delete_key(RfidKey* key) { } bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); bool result = false; string_t str_result; string_init(str_result); do { - if(!file.open_read(path)) break; + if(!flipper_file_open_existing(file, path)) break; // header uint32_t version; - if(!file.read_header(str_result, &version)) break; + if(!flipper_file_read_header(file, str_result, &version)) break; if(string_cmp_str(str_result, app_filetype) != 0) break; if(version != 1) break; @@ -137,13 +137,13 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { LfrfidKeyType type; RfidKey loaded_key; - if(!file.read_string("Key type", str_result)) break; + if(!flipper_file_read_string(file, "Key type", str_result)) break; if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; loaded_key.set_type(type); // key data uint8_t key_data[loaded_key.get_type_data_count()] = {}; - if(!file.read_hex_array("Data", key_data, loaded_key.get_type_data_count())) break; + if(!flipper_file_read_hex(file, "Data", key_data, loaded_key.get_type_data_count())) break; loaded_key.set_data(key_data, loaded_key.get_type_data_count()); path_extract_filename_no_ext(path, str_result); @@ -153,7 +153,8 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { result = true; } while(0); - file.close(); + flipper_file_close(file); + flipper_file_free(file); string_clear(str_result); if(!result) { @@ -164,21 +165,27 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { } bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); bool result = false; do { - if(!file.new_write(path)) break; - if(!file.write_header_cstr(app_filetype, 1)) break; - if(!file.write_comment_cstr("Key type can be EM4100, H10301 or I40134")) break; - if(!file.write_string_cstr("Key type", lfrfid_key_get_type_string(key->get_type()))) break; - if(!file.write_comment_cstr("Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) + if(!flipper_file_open_always(file, path)) break; + if(!flipper_file_write_header_cstr(file, app_filetype, 1)) break; + if(!flipper_file_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) + break; + if(!flipper_file_write_string_cstr( + file, "Key type", lfrfid_key_get_type_string(key->get_type()))) + break; + if(!flipper_file_write_comment_cstr( + file, "Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) + break; + if(!flipper_file_write_hex(file, "Data", key->get_data(), key->get_type_data_count())) break; - if(!file.write_hex_array("Data", key->get_data(), key->get_type_data_count())) break; result = true; } while(0); - file.close(); + flipper_file_close(file); + flipper_file_free(file); if(!result) { dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); diff --git a/applications/loader/loader.c b/applications/loader/loader.c old mode 100755 new mode 100644 index d52eaa18..fb5133df --- a/applications/loader/loader.c +++ b/applications/loader/loader.c @@ -1,6 +1,8 @@ #include "loader/loader.h" #include "loader_i.h" +#define TAG "LoaderSrv" + #define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0) #define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU) @@ -15,15 +17,13 @@ static void loader_menu_callback(void* _ctx, uint32_t index) { if(!loader_lock(loader_instance)) return; if(furi_thread_get_state(loader_instance->thread) != FuriThreadStateStopped) { - FURI_LOG_E( - LOADER_LOG_TAG, "Can't start app. %s is running", loader_instance->current_app->name); + FURI_LOG_E(TAG, "Can't start app. %s is running", loader_instance->current_app->name); return; } furi_hal_power_insomnia_enter(); loader_instance->current_app = flipper_app; - FURI_LOG_I( - LOADER_LOG_TAG, "Starting furi application: %s", loader_instance->current_app->name); + FURI_LOG_I(TAG, "Starting furi application: %s", loader_instance->current_app->name); furi_thread_set_name(loader_instance->thread, flipper_app->name); furi_thread_set_stack_size(loader_instance->thread, flipper_app->stack_size); furi_thread_set_context(loader_instance->thread, NULL); @@ -79,14 +79,14 @@ LoaderStatus loader_start(Loader* instance, const char* name, const char* args) } if(!flipper_app) { - FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); + FURI_LOG_E(TAG, "Can't find application with name %s", name); return LoaderStatusErrorUnknownApp; } bool locked = loader_lock(instance); if(!locked || (furi_thread_get_state(instance->thread) != FuriThreadStateStopped)) { - FURI_LOG_E(LOADER_LOG_TAG, "Can't start app. %s is running", instance->current_app->name); + FURI_LOG_E(TAG, "Can't start app. %s is running", instance->current_app->name); /* no need to call loader_unlock() - it is called as soon as application stops */ return LoaderStatusErrorAppStarted; } @@ -97,10 +97,10 @@ LoaderStatus loader_start(Loader* instance, const char* name, const char* args) string_set_str(instance->args, args); string_strim(instance->args); thread_args = (void*)string_get_cstr(instance->args); - FURI_LOG_I(LOADER_LOG_TAG, "Start %s app with args: %s", name, args); + FURI_LOG_I(TAG, "Start %s app with args: %s", name, args); } else { - string_clean(instance->args); - FURI_LOG_I(LOADER_LOG_TAG, "Start %s app with no args", name); + string_reset(instance->args); + FURI_LOG_I(TAG, "Start %s app with no args", name); } furi_thread_set_name(instance->thread, flipper_app->name); @@ -140,7 +140,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con Loader* instance = context; if(thread_state == FuriThreadStateRunning) { - instance->free_heap_size = xPortGetFreeHeapSize(); + instance->free_heap_size = memmgr_get_free_heap(); } else if(thread_state == FuriThreadStateStopped) { /* * Current Leak Sanitizer assumes that memory is allocated and freed @@ -153,9 +153,9 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con * both values should be taken into account. */ delay(20); - int heap_diff = instance->free_heap_size - xPortGetFreeHeapSize(); + int heap_diff = instance->free_heap_size - memmgr_get_free_heap(); FURI_LOG_I( - LOADER_LOG_TAG, + TAG, "Application thread stopped. Heap allocation balance: %d. Thread allocation balance: %d.", heap_diff, furi_thread_get_heap_size(instance->thread)); @@ -266,7 +266,7 @@ static void loader_add_cli_command(FlipperApplication* app) { } static void loader_build_menu() { - FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); + FURI_LOG_I(TAG, "Building main menu"); size_t i; for(i = 0; i < FLIPPER_APPS_COUNT; i++) { loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]); @@ -300,7 +300,7 @@ static void loader_build_menu() { loader_submenu_callback, (void*)LoaderMenuViewSettings); - FURI_LOG_I(LOADER_LOG_TAG, "Building plugins menu"); + FURI_LOG_I(TAG, "Building plugins menu"); for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]); submenu_add_item( @@ -311,7 +311,7 @@ static void loader_build_menu() { (void*)&FLIPPER_PLUGINS[i]); } - FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu"); + FURI_LOG_I(TAG, "Building debug menu"); for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]); submenu_add_item( @@ -322,7 +322,7 @@ static void loader_build_menu() { (void*)&FLIPPER_DEBUG_APPS[i]); } - FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu"); + FURI_LOG_I(TAG, "Building settings menu"); for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { submenu_add_item( loader_instance->settings_menu, @@ -339,7 +339,7 @@ void loader_show_menu() { } int32_t loader_srv(void* p) { - FURI_LOG_I(LOADER_LOG_TAG, "Starting"); + FURI_LOG_I(TAG, "Starting"); loader_instance = loader_alloc(); @@ -350,7 +350,7 @@ int32_t loader_srv(void* p) { FLIPPER_ON_SYSTEM_START[i](); } - FURI_LOG_I(LOADER_LOG_TAG, "Started"); + FURI_LOG_I(TAG, "Started"); furi_record_create("loader", loader_instance); diff --git a/applications/loader/loader_i.h b/applications/loader/loader_i.h index 8d44911f..08c9ac09 100644 --- a/applications/loader/loader_i.h +++ b/applications/loader/loader_i.h @@ -12,8 +12,6 @@ #include #include -#define LOADER_LOG_TAG "loader" - struct Loader { osThreadId_t loader_thread; FuriThread* thread; diff --git a/applications/nfc/helpers/nfc_emv_parser.c b/applications/nfc/helpers/nfc_emv_parser.c index b84d35c3..54c6c203 100755 --- a/applications/nfc/helpers/nfc_emv_parser.c +++ b/applications/nfc/helpers/nfc_emv_parser.c @@ -1,49 +1,80 @@ #include "nfc_emv_parser.h" +#include -#include +static const char* nfc_resources_header = "Flipper EMV resources"; +static const uint32_t nfc_resources_file_version = 1; -static bool - nfc_emv_parser_get_value(const char* file_path, string_t key, char delimiter, string_t value) { - bool found = false; - FileWorker* file_worker = file_worker_alloc(true); +static bool nfc_emv_parser_search_data( + Storage* storage, + const char* file_name, + string_t key, + string_t data) { + bool parsed = false; + FlipperFile* file = flipper_file_alloc(storage); + string_t temp_str; + string_init(temp_str); - if(file_worker_open(file_worker, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { - if(file_worker_get_value_from_key(file_worker, key, delimiter, value)) { - found = true; - } - } + do { + // Open file + if(!flipper_file_open_existing(file, file_name)) break; + // Read file header and version + uint32_t version = 0; + if(!flipper_file_read_header(file, temp_str, &version)) break; + if(string_cmp_str(temp_str, nfc_resources_header) || + (version != nfc_resources_file_version)) + break; + if(!flipper_file_read_string(file, string_get_cstr(key), data)) break; + parsed = true; + } while(false); - file_worker_close(file_worker); - file_worker_free(file_worker); - return found; + string_clear(temp_str); + flipper_file_free(file); + return parsed; } -bool nfc_emv_parser_get_aid_name(uint8_t* aid, uint8_t aid_len, string_t aid_name) { - bool result = false; +bool nfc_emv_parser_get_aid_name( + Storage* storage, + uint8_t* aid, + uint8_t aid_len, + string_t aid_name) { + furi_assert(storage); + bool parsed = false; string_t key; string_init(key); for(uint8_t i = 0; i < aid_len; i++) { string_cat_printf(key, "%02X", aid[i]); } - result = nfc_emv_parser_get_value("/ext/nfc/emv/aid.nfc", key, ' ', aid_name); + if(nfc_emv_parser_search_data(storage, "/ext/nfc/emv/aid.nfc", key, aid_name)) { + parsed = true; + } string_clear(key); - return result; + return parsed; } -bool nfc_emv_parser_get_country_name(uint16_t country_code, string_t country_name) { - bool result = false; +bool nfc_emv_parser_get_country_name( + Storage* storage, + uint16_t country_code, + string_t country_name) { + bool parsed = false; string_t key; string_init_printf(key, "%04X", country_code); - result = nfc_emv_parser_get_value("/ext/nfc/emv/country_code.nfc", key, ' ', country_name); + if(nfc_emv_parser_search_data(storage, "/ext/nfc/emv/country_code.nfc", key, country_name)) { + parsed = true; + } string_clear(key); - return result; + return parsed; } -bool nfc_emv_parser_get_currency_name(uint16_t currency_code, string_t currency_name) { - bool result = false; +bool nfc_emv_parser_get_currency_name( + Storage* storage, + uint16_t currency_code, + string_t currency_name) { + bool parsed = false; string_t key; string_init_printf(key, "%04X", currency_code); - result = nfc_emv_parser_get_value("/ext/nfc/emv/currency_code.nfc", key, ' ', currency_name); + if(nfc_emv_parser_search_data(storage, "/ext/nfc/emv/currency_code.nfc", key, currency_name)) { + parsed = true; + } string_clear(key); - return result; + return parsed; } diff --git a/applications/nfc/helpers/nfc_emv_parser.h b/applications/nfc/helpers/nfc_emv_parser.h index b81a06ee..5948ed34 100755 --- a/applications/nfc/helpers/nfc_emv_parser.h +++ b/applications/nfc/helpers/nfc_emv_parser.h @@ -3,25 +3,39 @@ #include #include #include +#include /** Get EMV application name by number + * @param storage Storage instance * @param aid - AID number array * @param aid_len - AID length * @param aid_name - string to keep AID name * @return - true if AID found, false otherwies */ -bool nfc_emv_parser_get_aid_name(uint8_t* aid, uint8_t aid_len, string_t aid_name); +bool nfc_emv_parser_get_aid_name( + Storage* storage, + uint8_t* aid, + uint8_t aid_len, + string_t aid_name); /** Get country name by country code + * @param storage Storage instance * @param country_code - ISO 3166 country code * @param country_name - string to keep country name * @return - true if country found, false otherwies */ -bool nfc_emv_parser_get_country_name(uint16_t country_code, string_t country_name); +bool nfc_emv_parser_get_country_name( + Storage* storage, + uint16_t country_code, + string_t country_name); /** Get currency name by currency code + * @param storage Storage instance * @param currency_code - ISO 3166 currency code * @param currency_name - string to keep currency name * @return - true if currency found, false otherwies */ -bool nfc_emv_parser_get_currency_name(uint16_t currency_code, string_t currency_name); +bool nfc_emv_parser_get_currency_name( + Storage* storage, + uint16_t currency_code, + string_t currency_name); diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index 5bf788b9..a29a6d19 100755 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -31,6 +31,9 @@ Nfc* nfc_alloc() { view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback); view_dispatcher_set_tick_event_callback(nfc->view_dispatcher, nfc_tick_event_callback, 100); + // Nfc device + nfc->dev = nfc_device_alloc(); + // Open GUI record nfc->gui = furi_record_open("gui"); view_dispatcher_attach_to_gui(nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); @@ -82,6 +85,9 @@ Nfc* nfc_alloc() { void nfc_free(Nfc* nfc) { furi_assert(nfc); + // Nfc device + nfc_device_free(nfc->dev); + // Submenu view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewMenu); submenu_free(nfc->submenu); @@ -154,8 +160,12 @@ int32_t nfc_app(void* p) { char* args = p; // Check argument and run corresponding scene - if((*args != '\0') && nfc_device_load(&nfc->dev, p)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); + if((*args != '\0') && nfc_device_load(nfc->dev, p)) { + if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); + } else { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); + } } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); } diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 24fd8e40..17a261f2 100755 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -1,45 +1,38 @@ -#include "nfc_device_i.h" +#include "nfc_device.h" -#include #include -#include - -#define NFC_DEVICE_MAX_DATA_LEN 14 +#include static const char* nfc_app_folder = "/any/nfc"; static const char* nfc_app_extension = ".nfc"; static const char* nfc_app_shadow_extension = ".shd"; +static const char* nfc_file_header = "Flipper NFC device"; +static const uint32_t nfc_file_version = 2; -static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len, uint8_t delim_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; - string_right(str, delim_len + 2); - } else { - parsed = false; - break; - } - } - return parsed; +NfcDevice* nfc_device_alloc() { + NfcDevice* nfc_dev = furi_alloc(sizeof(NfcDevice)); + nfc_dev->storage = furi_record_open("storage"); + nfc_dev->dialogs = furi_record_open("dialogs"); + return nfc_dev; } -uint16_t nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { +void nfc_device_free(NfcDevice* nfc_dev) { + furi_assert(nfc_dev); + furi_record_close("storage"); + furi_record_close("dialogs"); + free(nfc_dev); +} + +void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { if(dev->format == NfcDeviceSaveFormatUid) { - string_set_str(format_string, "UID\n"); + string_set_str(format_string, "UID"); } else if(dev->format == NfcDeviceSaveFormatBankCard) { - string_set_str(format_string, "Bank card\n"); + string_set_str(format_string, "Bank card"); } else if(dev->format == NfcDeviceSaveFormatMifareUl) { - string_set_str(format_string, "Mifare Ultralight\n"); + string_set_str(format_string, "Mifare Ultralight"); } else { - string_set_str(format_string, "Unknown\n"); + string_set_str(format_string, "Unknown"); } - return string_size(format_string); } bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { @@ -59,228 +52,166 @@ bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { return false; } -uint16_t nfc_device_prepare_uid_string(NfcDevice* dev, string_t uid_string) { - NfcDeviceCommonData* uid_data = &dev->dev_data.nfc_data; - string_printf(uid_string, "UID len: %02X UID: ", dev->dev_data.nfc_data.uid_len); - for(uint8_t i = 0; i < uid_data->uid_len; i++) { - string_cat_printf(uid_string, "%02X ", uid_data->uid[i]); - } - string_cat_printf( - uid_string, - "ATQA: %02X %02X SAK: %02X\n", - uid_data->atqa[0], - uid_data->atqa[1], - uid_data->sak); - return string_size(uid_string); -} - -bool nfc_device_parse_uid_string(NfcDevice* dev, string_t uid_string) { - NfcDeviceCommonData* uid_data = &dev->dev_data.nfc_data; - bool parsed = false; - - do { - // strlen("UID len: ") = 9 - string_right(uid_string, 9); - if(!nfc_device_read_hex(uid_string, &uid_data->uid_len, 1, 1)) { - break; - } - // strlen("UID: ") = 5 - string_right(uid_string, 5); - if(!nfc_device_read_hex(uid_string, uid_data->uid, uid_data->uid_len, 1)) { - break; - } - // strlen("ATQA: ") = 6 - string_right(uid_string, 6); - if(!nfc_device_read_hex(uid_string, uid_data->atqa, 2, 1)) { - break; - } - // strlen("SAK: ") = 5 - string_right(uid_string, 5); - if(!nfc_device_read_hex(uid_string, &uid_data->sak, 1, 1)) { - break; - } - parsed = true; - } while(0); - - return parsed; -} - -uint16_t nfc_device_prepare_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string) { +static bool nfc_device_save_mifare_ul_data(FlipperFile* file, NfcDevice* dev) { + bool saved = false; MifareUlData* data = &dev->dev_data.mf_ul_data; - string_printf(mifare_ul_string, "Signature:"); - for(uint8_t i = 0; i < sizeof(data->signature); i++) { - string_cat_printf(mifare_ul_string, " %02X", data->signature[i]); - } - string_cat_printf(mifare_ul_string, "\nVersion:"); - uint8_t* version = (uint8_t*)&data->version; - for(uint8_t i = 0; i < sizeof(data->version); i++) { - string_cat_printf(mifare_ul_string, " %02X", version[i]); - } - for(uint8_t i = 0; i < 3; i++) { - string_cat_printf( - mifare_ul_string, - "\nCounter %d: %lu Tearing flag %d: %02X", - i, - data->counter[i], - i, - data->tearing[i]); - } - string_cat_printf(mifare_ul_string, "\nData size: %d\n", data->data_size); - for(uint16_t i = 0; i < data->data_size; i += 4) { - string_cat_printf( - mifare_ul_string, - "%02X %02X %02X %02X\n", - data->data[i], - data->data[i + 1], - data->data[i + 2], - data->data[i + 3]); - } - return string_size(mifare_ul_string); -} - -bool nfc_device_parse_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string) { - MifareUlData* data = &dev->dev_data.mf_ul_data; - uint16_t tearing_tmp = 0; - uint16_t cnt_num = 0; - size_t ws = 0; - int res = 0; - bool parsed = false; + string_t temp_str; + string_init(temp_str); + // Save Mifare Ultralight specific data do { - // strlen("Signature: ") = 11 - string_right(mifare_ul_string, 11); - if(!nfc_device_read_hex(mifare_ul_string, data->signature, sizeof(data->signature), 1)) { + if(!flipper_file_write_comment_cstr(file, "Mifare Ultralight specific data")) break; + if(!flipper_file_write_hex(file, "Signature", data->signature, sizeof(data->signature))) break; - } - // strlen("Version: ") = 9 - string_right(mifare_ul_string, 9); - if(!nfc_device_read_hex( - mifare_ul_string, (uint8_t*)&data->version, sizeof(data->version), 1)) { + if(!flipper_file_write_hex( + file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) break; - } - string_strim(mifare_ul_string); - // Read counters and tearing flags + // Write conters and tearing flags data + bool counters_saved = true; for(uint8_t i = 0; i < 3; i++) { - res = sscanf( - string_get_cstr(mifare_ul_string), - "Counter %hX: %lu Tearing flag %hX: %02hX", - &cnt_num, - &data->counter[i], - &cnt_num, - &tearing_tmp); - if(res != 4) { + string_printf(temp_str, "Counter %d", i); + if(!flipper_file_write_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) { + counters_saved = false; + break; + } + string_printf(temp_str, "Tearing %d", i); + if(!flipper_file_write_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { + counters_saved = false; break; } - data->tearing[i] = tearing_tmp; - ws = string_search_char(mifare_ul_string, '\n'); - string_right(mifare_ul_string, ws + 1); } - // Read data size - res = sscanf(string_get_cstr(mifare_ul_string), "Data size: %hu", &data->data_size); - if(res != 1) { - break; - } - ws = string_search_char(mifare_ul_string, '\n'); - string_right(mifare_ul_string, ws + 1); - // Read data + if(!counters_saved) break; + // Write pages data + uint32_t pages_total = data->data_size / 4; + if(!flipper_file_write_uint32(file, "Pages total", &pages_total, 1)) break; + bool pages_saved = true; for(uint16_t i = 0; i < data->data_size; i += 4) { - if(!nfc_device_read_hex(mifare_ul_string, &data->data[i], 4, 1)) { + string_printf(temp_str, "Page %d", i / 4); + if(!flipper_file_write_hex(file, string_get_cstr(temp_str), &data->data[i], 4)) { + pages_saved = false; break; } } - parsed = true; - } while(0); + if(!pages_saved) break; + saved = true; + } while(false); + string_clear(temp_str); + return saved; +} + +bool nfc_device_load_mifare_ul_data(FlipperFile* file, NfcDevice* dev) { + bool parsed = false; + MifareUlData* data = &dev->dev_data.mf_ul_data; + string_t temp_str; + string_init(temp_str); + + do { + // Read signature + if(!flipper_file_read_hex(file, "Signature", data->signature, sizeof(data->signature))) + break; + // Read Mifare version + if(!flipper_file_read_hex( + file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) + break; + // Read counters and tearing flags + bool counters_parsed = true; + for(uint8_t i = 0; i < 3; i++) { + string_printf(temp_str, "Counter %d", i); + if(!flipper_file_read_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) { + counters_parsed = false; + break; + } + string_printf(temp_str, "Tearing %d", i); + if(!flipper_file_read_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { + counters_parsed = false; + break; + } + } + if(!counters_parsed) break; + // Read pages + uint32_t pages = 0; + if(!flipper_file_read_uint32(file, "Pages total", &pages, 1)) break; + data->data_size = pages * 4; + bool pages_parsed = true; + for(uint16_t i = 0; i < pages; i++) { + string_printf(temp_str, "Page %d", i); + if(!flipper_file_read_hex(file, string_get_cstr(temp_str), &data->data[i * 4], 4)) { + pages_parsed = false; + break; + } + } + if(!pages_parsed) break; + parsed = true; + } while(false); + + string_clear(temp_str); return parsed; } -uint16_t nfc_device_prepare_bank_card_string(NfcDevice* dev, string_t bank_card_string) { +static bool nfc_device_save_bank_card_data(FlipperFile* file, NfcDevice* dev) { + bool saved = false; NfcEmvData* data = &dev->dev_data.emv_data; - string_printf(bank_card_string, "AID len: %d, AID:", data->aid_len); - for(uint8_t i = 0; i < data->aid_len; i++) { - string_cat_printf(bank_card_string, " %02X", data->aid[i]); - } - string_cat_printf( - bank_card_string, "\nName: %s\nNumber len: %d\nNumber:", data->name, data->number_len); - for(uint8_t i = 0; i < data->number_len; i++) { - string_cat_printf(bank_card_string, " %02X", data->number[i]); - } - if(data->exp_mon) { - string_cat_printf( - bank_card_string, "\nExp date: %02X/%02X", data->exp_mon, data->exp_year); - } - if(data->country_code) { - string_cat_printf(bank_card_string, "\nCountry code: %04X", data->country_code); - } - if(data->currency_code) { - string_cat_printf(bank_card_string, "\nCurrency code: %04X", data->currency_code); - } - return string_size(bank_card_string); -} - -bool nfc_device_parse_bank_card_string(NfcDevice* dev, string_t bank_card_string) { - NfcEmvData* data = &dev->dev_data.emv_data; - bool parsed = false; - int res = 0; - uint8_t code[2] = {}; - memset(data, 0, sizeof(NfcEmvData)); + uint32_t data_temp = 0; do { - res = sscanf(string_get_cstr(bank_card_string), "AID len: %hu", &data->aid_len); - if(res != 1) { - break; + // Write Bank card specific data + if(!flipper_file_write_comment_cstr(file, "Bank card specific data")) break; + if(!flipper_file_write_hex(file, "AID", data->aid, data->aid_len)) break; + if(!flipper_file_write_string_cstr(file, "Name", data->name)) break; + if(!flipper_file_write_hex(file, "Number", data->number, data->number_len)) break; + if(data->exp_mon) { + uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; + if(!flipper_file_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; } - // strlen("AID len: ") = 9 - string_right(bank_card_string, 9); - size_t ws = string_search_char(bank_card_string, ':'); - string_right(bank_card_string, ws + 1); - if(!nfc_device_read_hex(bank_card_string, data->aid, data->aid_len, 1)) { - break; + if(data->country_code) { + data_temp = data->country_code; + if(!flipper_file_write_uint32(file, "Country code", &data_temp, 1)) break; } - res = sscanf(string_get_cstr(bank_card_string), "Name: %s\n", data->name); - if(res != 1) { - break; + if(data->currency_code) { + data_temp = data->currency_code; + if(!flipper_file_write_uint32(file, "Currency code", &data_temp, 1)) break; } - ws = string_search_char(bank_card_string, '\n'); - string_right(bank_card_string, ws + 1); - res = sscanf(string_get_cstr(bank_card_string), "Number len: %hhu", &data->number_len); - if(res != 1) { - break; - } - ws = string_search_char(bank_card_string, '\n'); - string_right(bank_card_string, ws + 1); - // strlen("Number: ") = 8 - string_right(bank_card_string, 8); - if(!nfc_device_read_hex(bank_card_string, data->number, data->number_len, 1)) { - break; - } - parsed = true; - // Check expiration date presence - ws = string_search_str(bank_card_string, "Exp date: "); - if(ws != STRING_FAILURE) { - // strlen("Exp date: ") = 10 - string_right(bank_card_string, 10); - nfc_device_read_hex(bank_card_string, &data->exp_mon, 1, 1); - nfc_device_read_hex(bank_card_string, &data->exp_year, 1, 1); - } - // Check country code presence - ws = string_search_str(bank_card_string, "Country code: "); - if(ws != STRING_FAILURE) { - // strlen("Country code: ") = 14 - string_right(bank_card_string, 14); - nfc_device_read_hex(bank_card_string, code, 2, 0); - data->country_code = code[0] << 8 | code[1]; - } - // Check currency code presence - ws = string_search_str(bank_card_string, "Currency code: "); - if(ws != STRING_FAILURE) { - // strlen("Currency code: ") = 15 - string_right(bank_card_string, 15); - nfc_device_read_hex(bank_card_string, code, 2, 0); - data->currency_code = code[0] << 8 | code[1]; - } - } while(0); + saved = true; + } while(false); + return saved; +} + +bool nfc_device_load_bank_card_data(FlipperFile* file, NfcDevice* dev) { + bool parsed = false; + NfcEmvData* data = &dev->dev_data.emv_data; + memset(data, 0, sizeof(NfcEmvData)); + uint32_t data_cnt = 0; + string_t temp_str; + string_init(temp_str); + + do { + // Load essential data + if(!flipper_file_get_value_count(file, "AID", &data_cnt)) break; + data->aid_len = data_cnt; + if(!flipper_file_read_hex(file, "AID", data->aid, data->aid_len)) break; + if(!flipper_file_read_string(file, "Name", temp_str)) break; + strlcpy(data->name, string_get_cstr(temp_str), sizeof(data->name)); + if(!flipper_file_get_value_count(file, "Number", &data_cnt)) break; + data->number_len = data_cnt; + if(!flipper_file_read_hex(file, "Number", data->number, data->number_len)) break; + parsed = true; + // Load optional data + uint8_t exp_data[2] = {}; + if(flipper_file_read_hex(file, "Exp data", exp_data, 2)) { + data->exp_mon = exp_data[0]; + data->exp_year = exp_data[1]; + } + if(flipper_file_read_uint32(file, "Country code", &data_cnt, 1)) { + data->country_code = data_cnt; + } + if(flipper_file_read_uint32(file, "Currency code", &data_cnt, 1)) { + data->currency_code = data_cnt; + } + } while(false); + + string_clear(temp_str); return parsed; } @@ -297,58 +228,49 @@ static bool nfc_device_save_file( const char* extension) { furi_assert(dev); - FileWorker* file_worker = file_worker_alloc(false); - string_t dev_file_name; - string_init(dev_file_name); + bool saved = false; + FlipperFile* file = flipper_file_alloc(dev->storage); + NfcDeviceCommonData* data = &dev->dev_data.nfc_data; string_t temp_str; string_init(temp_str); - uint16_t string_len = 0; do { // Create nfc directory if necessary - if(!file_worker_mkdir(file_worker, nfc_app_folder)) { - break; - }; + if(!storage_simply_mkdir(dev->storage, nfc_app_folder)) break; // First remove nfc device file if it was saved - string_printf(dev_file_name, "%s/%s%s", folder, dev_name, extension); - if(!file_worker_remove(file_worker, string_get_cstr(dev_file_name))) { - break; - }; + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); // Open file - if(!file_worker_open( - file_worker, string_get_cstr(dev_file_name), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + if(!flipper_file_open_always(file, string_get_cstr(temp_str))) break; + // Write header + if(!flipper_file_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; + // Write nfc device type + if(!flipper_file_write_comment_cstr( + file, "Nfc device type can be UID, Mifare Ultralight, Bank card")) break; - } - // Prepare and write format name on 1st line - string_len = nfc_device_prepare_format_string(dev, temp_str); - if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { + nfc_device_prepare_format_string(dev, temp_str); + if(!flipper_file_write_string(file, "Device type", temp_str)) break; + // Write UID, ATQA, SAK + if(!flipper_file_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) break; - } - // Prepare and write UID data on 2nd line - string_len = nfc_device_prepare_uid_string(dev, temp_str); - if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { - break; - } + if(!flipper_file_write_hex(file, "UID", data->uid, data->uid_len)) break; + if(!flipper_file_write_hex(file, "ATQA", data->atqa, 2)) break; + if(!flipper_file_write_hex(file, "SAK", &data->sak, 1)) break; // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { - string_len = nfc_device_prepare_mifare_ul_string(dev, temp_str); - if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { - break; - } + if(!nfc_device_save_mifare_ul_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { - string_len = nfc_device_prepare_bank_card_string(dev, temp_str); - if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { - break; - } + if(!nfc_device_save_bank_card_data(file, dev)) break; } + saved = true; } while(0); + if(!saved) { + dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + } string_clear(temp_str); - string_clear(dev_file_name); - file_worker_close(file_worker); - file_worker_free(file_worker); - - return true; + flipper_file_close(file); + flipper_file_free(file); + return saved; } bool nfc_device_save(NfcDevice* dev, const char* dev_name) { @@ -360,73 +282,64 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { return nfc_device_save_file(dev, dev_name, nfc_app_folder, nfc_app_shadow_extension); } -static bool nfc_device_load_data(FileWorker* file_worker, string_t path, NfcDevice* dev) { - string_t temp_string; - string_init(temp_string); +static bool nfc_device_load_data(NfcDevice* dev, string_t path) { bool parsed = false; + FlipperFile* file = flipper_file_alloc(dev->storage); + NfcDeviceCommonData* data = &dev->dev_data.nfc_data; + uint32_t data_cnt = 0; + string_t temp_str; + string_init(temp_str); + bool depricated_version = false; do { // Check existance of shadow file size_t ext_start = string_search_str(path, nfc_app_extension); - string_set_n(temp_string, path, 0, ext_start); - string_cat_printf(temp_string, "%s", nfc_app_shadow_extension); - if(!file_worker_is_file_exist( - file_worker, string_get_cstr(temp_string), &dev->shadow_file_exist)) { - break; - } + string_set_n(temp_str, path, 0, ext_start); + string_cat_printf(temp_str, "%s", nfc_app_shadow_extension); + dev->shadow_file_exist = + storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; // Open shadow file if it exists. If not - open original if(dev->shadow_file_exist) { - if(!file_worker_open( - file_worker, string_get_cstr(temp_string), FSAM_READ, FSOM_OPEN_EXISTING)) { - break; - } + if(!flipper_file_open_existing(file, string_get_cstr(temp_str))) break; } else { - if(!file_worker_open( - file_worker, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { - break; - } + if(!flipper_file_open_existing(file, string_get_cstr(path))) break; } - - // Read and parse format from 1st line - if(!file_worker_read_until(file_worker, temp_string, '\n')) { - break; - } - if(!nfc_device_parse_format_string(dev, temp_string)) { - break; - } - // Read and parse UID data from 2nd line - if(!file_worker_read_until(file_worker, temp_string, '\n')) { - break; - } - if(!nfc_device_parse_uid_string(dev, temp_string)) { + // Read and verify file header + uint32_t version = 0; + if(!flipper_file_read_header(file, temp_str, &version)) break; + if(string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { + depricated_version = true; break; } + // Read Nfc device type + if(!flipper_file_read_string(file, "Device type", temp_str)) break; + if(!nfc_device_parse_format_string(dev, temp_str)) break; + // Read and parse UID, ATQA and SAK + if(!flipper_file_get_value_count(file, "UID", &data_cnt)) break; + data->uid_len = data_cnt; + if(!flipper_file_read_hex(file, "UID", data->uid, data->uid_len)) break; + if(!flipper_file_read_hex(file, "ATQA", data->atqa, 2)) break; + if(!flipper_file_read_hex(file, "SAK", &data->sak, 1)) break; // Parse other data if(dev->format == NfcDeviceSaveFormatMifareUl) { - // Read until EOF - if(!file_worker_read_until(file_worker, temp_string, 0x05)) { - break; - } - if(!nfc_device_parse_mifare_ul_string(dev, temp_string)) { - break; - } + if(!nfc_device_load_mifare_ul_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { - // Read until EOF - if(!file_worker_read_until(file_worker, temp_string, 0x05)) { - break; - } - if(!nfc_device_parse_bank_card_string(dev, temp_string)) { - break; - } + if(!nfc_device_load_bank_card_data(file, dev)) break; } parsed = true; - } while(0); + } while(false); if(!parsed) { - file_worker_show_error(file_worker, "Can not parse\nfile"); + if(depricated_version) { + dialog_message_show_storage_error(dev->dialogs, "File format depricated"); + } else { + dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); + } } - string_clear(temp_string); + string_clear(temp_str); + flipper_file_close(file); + flipper_file_free(file); return parsed; } @@ -434,19 +347,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { furi_assert(dev); furi_assert(file_path); - FileWorker* file_worker = file_worker_alloc(false); // Load device data string_t path; string_init_set_str(path, file_path); - bool dev_load = nfc_device_load_data(file_worker, path, dev); + bool dev_load = nfc_device_load_data(dev, path); if(dev_load) { // Set device name path_extract_filename_no_ext(file_path, path); nfc_device_set_name(dev, string_get_cstr(path)); } string_clear(path); - file_worker_close(file_worker); - file_worker_free(file_worker); return dev_load; } @@ -454,10 +364,9 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); - FileWorker* file_worker = file_worker_alloc(false); // Input events and views are managed by file_select - bool res = file_worker_file_select( - file_worker, + bool res = dialog_file_select_show( + dev->dialogs, nfc_app_folder, nfc_app_extension, dev->file_name, @@ -465,18 +374,14 @@ bool nfc_file_select(NfcDevice* dev) { dev->dev_name); if(res) { string_t dev_str; - // Get key file path string_init_printf(dev_str, "%s/%s%s", nfc_app_folder, dev->file_name, nfc_app_extension); - - res = nfc_device_load_data(file_worker, dev_str, dev); + res = nfc_device_load_data(dev, dev_str); if(res) { nfc_device_set_name(dev, dev->file_name); } string_clear(dev_str); } - file_worker_close(file_worker); - file_worker_free(file_worker); return res; } @@ -491,61 +396,48 @@ void nfc_device_clear(NfcDevice* dev) { bool nfc_device_delete(NfcDevice* dev) { furi_assert(dev); - bool result = true; - FileWorker* file_worker = file_worker_alloc(false); + bool deleted = false; string_t file_path; + string_init(file_path); do { // Delete original file string_init_printf(file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension); - if(!file_worker_remove(file_worker, string_get_cstr(file_path))) { - result = false; - break; - } + if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; // Delete shadow file if it exists if(dev->shadow_file_exist) { - string_clean(file_path); string_printf( file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension); - if(!file_worker_remove(file_worker, string_get_cstr(file_path))) { - result = false; - break; - } + if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; } + deleted = true; } while(0); + if(!deleted) { + dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + } + string_clear(file_path); - file_worker_close(file_worker); - file_worker_free(file_worker); - return result; + return deleted; } bool nfc_device_restore(NfcDevice* dev) { furi_assert(dev); furi_assert(dev->shadow_file_exist); - bool result = true; - FileWorker* file_worker = file_worker_alloc(false); + bool restored = false; string_t path; do { string_init_printf( path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension); - if(!file_worker_remove(file_worker, string_get_cstr(path))) { - result = false; - break; - } + if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; dev->shadow_file_exist = false; - string_clean(path); string_printf(path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension); - if(!nfc_device_load_data(file_worker, path, dev)) { - result = false; - break; - } + if(!nfc_device_load_data(dev, path)) break; + restored = true; } while(0); string_clear(path); - file_worker_close(file_worker); - file_worker_free(file_worker); - return result; + return restored; } diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index 50f435ca..888f0c2f 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include "mifare_ultralight.h" @@ -57,6 +59,8 @@ typedef struct { } NfcDeviceData; typedef struct { + Storage* storage; + DialogsApp* dialogs; NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; char file_name[NFC_FILE_NAME_MAX_LEN]; @@ -64,6 +68,10 @@ typedef struct { bool shadow_file_exist; } NfcDevice; +NfcDevice* nfc_device_alloc(); + +void nfc_device_free(NfcDevice* nfc_dev); + void nfc_device_set_name(NfcDevice* dev, const char* name); bool nfc_device_save(NfcDevice* dev, const char* dev_name); diff --git a/applications/nfc/nfc_device_i.h b/applications/nfc/nfc_device_i.h deleted file mode 100644 index 01309eb5..00000000 --- a/applications/nfc/nfc_device_i.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "nfc_device.h" -#include - -uint16_t nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string); -bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string); - -uint16_t nfc_device_prepare_uid_string(NfcDevice* dev, string_t uid_string); -bool nfc_device_parse_uid_string(NfcDevice* dev, string_t uid_string); - -uint16_t nfc_device_prepare_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string); -bool nfc_device_parse_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string); - -uint16_t nfc_device_prepare_bank_card_string(NfcDevice* dev, string_t bank_card_string); -bool nfc_device_parse_bank_card_string(NfcDevice* dev, string_t bank_card_string); \ No newline at end of file diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index 379cc100..61a11ce8 100755 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -36,7 +36,7 @@ struct Nfc { Gui* gui; NotificationApp* notifications; SceneManager* scene_manager; - NfcDevice dev; + NfcDevice* dev; NfcDeviceCommonData dev_edit_data; char text_store[NFC_TEXT_STORE_SIZE + 1]; diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c old mode 100755 new mode 100644 index e8312b88..3f7f87e4 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -3,14 +3,14 @@ #include "nfc_protocols/emv_decoder.h" #include "nfc_protocols/mifare_ultralight.h" -#define NFC_WORKER_TAG "nfc worker" +#define TAG "NfcWorker" /***************************** NFC Worker API *******************************/ NfcWorker* nfc_worker_alloc() { NfcWorker* nfc_worker = furi_alloc(sizeof(NfcWorker)); // Worker thread attributes - nfc_worker->thread_attr.name = "nfc_worker"; + nfc_worker->thread_attr.name = "NfcWorker"; nfc_worker->thread_attr.stack_size = 8192; nfc_worker->callback = NULL; nfc_worker->context = NULL; @@ -144,7 +144,7 @@ void nfc_worker_emulate(NfcWorker* nfc_worker) { NfcDeviceCommonData* data = &nfc_worker->dev_data->nfc_data; while(nfc_worker->state == NfcWorkerStateEmulate) { if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { - FURI_LOG_I(NFC_WORKER_TAG, "Reader detected"); + FURI_LOG_I(TAG, "Reader detected"); } osDelay(10); } @@ -174,18 +174,17 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); result->nfc_data.protocol = NfcDeviceProtocolEMV; - FURI_LOG_I(NFC_WORKER_TAG, "Send select PPSE command"); + FURI_LOG_I(TAG, "Send select PPSE command"); tx_len = emv_prepare_select_ppse(tx_buff); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err != ERR_NONE) { - FURI_LOG_E(NFC_WORKER_TAG, "Error during selection PPSE request: %d", err); + FURI_LOG_E(TAG, "Error during selection PPSE request: %d", err); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I( - NFC_WORKER_TAG, "Select PPSE response received. Start parsing response"); + FURI_LOG_I(TAG, "Select PPSE response received. Start parsing response"); if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { - FURI_LOG_I(NFC_WORKER_TAG, "Select PPSE responce parced"); + FURI_LOG_I(TAG, "Select PPSE responce parced"); // Notify caller and exit result->emv_data.aid_len = emv_app.aid_len; memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len); @@ -194,18 +193,18 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { } break; } else { - FURI_LOG_E(NFC_WORKER_TAG, "Can't find pay application"); + FURI_LOG_E(TAG, "Can't find pay application"); furi_hal_nfc_deactivate(); continue; } } else { // Can't find EMV card - FURI_LOG_W(NFC_WORKER_TAG, "Card doesn't support EMV"); + FURI_LOG_W(TAG, "Card doesn't support EMV"); furi_hal_nfc_deactivate(); } } else { // Can't find EMV card - FURI_LOG_W(NFC_WORKER_TAG, "Can't find any cards"); + FURI_LOG_W(TAG, "Can't find any cards"); furi_hal_nfc_deactivate(); } osDelay(20); @@ -236,56 +235,53 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); result->nfc_data.protocol = NfcDeviceProtocolEMV; - FURI_LOG_I(NFC_WORKER_TAG, "Send select PPSE command"); + FURI_LOG_I(TAG, "Send select PPSE command"); tx_len = emv_prepare_select_ppse(tx_buff); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err != ERR_NONE) { - FURI_LOG_E(NFC_WORKER_TAG, "Error during selection PPSE request: %d", err); + FURI_LOG_E(TAG, "Error during selection PPSE request: %d", err); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I( - NFC_WORKER_TAG, "Select PPSE response received. Start parsing response"); + FURI_LOG_I(TAG, "Select PPSE response received. Start parsing response"); if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { - FURI_LOG_I(NFC_WORKER_TAG, "Select PPSE responce parced"); + FURI_LOG_I(TAG, "Select PPSE responce parced"); + result->emv_data.aid_len = emv_app.aid_len; + memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len); } else { - FURI_LOG_E(NFC_WORKER_TAG, "Can't find pay application"); + FURI_LOG_E(TAG, "Can't find pay application"); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I(NFC_WORKER_TAG, "Starting application ..."); + FURI_LOG_I(TAG, "Starting application ..."); tx_len = emv_prepare_select_app(tx_buff, &emv_app); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err != ERR_NONE) { - FURI_LOG_E( - NFC_WORKER_TAG, "Error during application selection request: %d", err); + FURI_LOG_E(TAG, "Error during application selection request: %d", err); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I( - NFC_WORKER_TAG, - "Select application response received. Start parsing response"); + FURI_LOG_I(TAG, "Select application response received. Start parsing response"); if(emv_decode_select_app_response(rx_buff, *rx_len, &emv_app)) { - FURI_LOG_I(NFC_WORKER_TAG, "Card name: %s", emv_app.name); + FURI_LOG_I(TAG, "Card name: %s", emv_app.name); memcpy(result->emv_data.name, emv_app.name, sizeof(emv_app.name)); } else if(emv_app.pdol.size > 0) { - FURI_LOG_W(NFC_WORKER_TAG, "Can't find card name, but PDOL is present."); + FURI_LOG_W(TAG, "Can't find card name, but PDOL is present."); } else { - FURI_LOG_E(NFC_WORKER_TAG, "Can't find card name or PDOL"); + FURI_LOG_E(TAG, "Can't find card name or PDOL"); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I(NFC_WORKER_TAG, "Starting Get Processing Options command ..."); + FURI_LOG_I(TAG, "Starting Get Processing Options command ..."); tx_len = emv_prepare_get_proc_opt(tx_buff, &emv_app); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err != ERR_NONE) { - FURI_LOG_E( - NFC_WORKER_TAG, "Error during Get Processing Options command: %d", err); + FURI_LOG_E(TAG, "Error during Get Processing Options command: %d", err); furi_hal_nfc_deactivate(); continue; } if(emv_decode_get_proc_opt(rx_buff, *rx_len, &emv_app)) { - FURI_LOG_I(NFC_WORKER_TAG, "Card number parsed"); + FURI_LOG_I(TAG, "Card number parsed"); result->emv_data.number_len = emv_app.card_number_len; memcpy(result->emv_data.number, emv_app.card_number, emv_app.card_number_len); // Notify caller and exit @@ -309,7 +305,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { tx_buff, tx_len, &rx_buff, &rx_len, false); if(err != ERR_NONE) { FURI_LOG_E( - NFC_WORKER_TAG, + TAG, "Error reading application sfi %d, record %d", sfi, record); @@ -321,7 +317,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { } } if(pan_found) { - FURI_LOG_I(NFC_WORKER_TAG, "Card PAN found"); + FURI_LOG_I(TAG, "Card PAN found"); result->emv_data.number_len = emv_app.card_number_len; memcpy( result->emv_data.number, @@ -343,18 +339,18 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { } break; } else { - FURI_LOG_E(NFC_WORKER_TAG, "Can't read card number"); + FURI_LOG_E(TAG, "Can't read card number"); } furi_hal_nfc_deactivate(); } } else { // Can't find EMV card - FURI_LOG_W(NFC_WORKER_TAG, "Card doesn't support EMV"); + FURI_LOG_W(TAG, "Card doesn't support EMV"); furi_hal_nfc_deactivate(); } } else { // Can't find EMV card - FURI_LOG_W(NFC_WORKER_TAG, "Can't find any cards"); + FURI_LOG_W(TAG, "Can't find any cards"); furi_hal_nfc_deactivate(); } osDelay(20); @@ -416,63 +412,63 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { while(nfc_worker->state == NfcWorkerStateEmulateApdu) { if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { - FURI_LOG_I(NFC_WORKER_TAG, "POS terminal detected"); + FURI_LOG_I(TAG, "POS terminal detected"); // Read data from POS terminal err = furi_hal_nfc_data_exchange(NULL, 0, &rx_buff, &rx_len, false); if(err == ERR_NONE) { - FURI_LOG_I(NFC_WORKER_TAG, "Received Select PPSE"); + FURI_LOG_I(TAG, "Received Select PPSE"); } else { - FURI_LOG_E(NFC_WORKER_TAG, "Error in 1st data exchange: select PPSE"); + FURI_LOG_E(TAG, "Error in 1st data exchange: select PPSE"); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I(NFC_WORKER_TAG, "Transive SELECT PPSE ANS"); + FURI_LOG_I(TAG, "Transive SELECT PPSE ANS"); tx_len = emv_select_ppse_ans(tx_buff); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err == ERR_NONE) { - FURI_LOG_I(NFC_WORKER_TAG, "Received Select APP"); + FURI_LOG_I(TAG, "Received Select APP"); } else { - FURI_LOG_E(NFC_WORKER_TAG, "Error in 2nd data exchange: select APP"); + FURI_LOG_E(TAG, "Error in 2nd data exchange: select APP"); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I(NFC_WORKER_TAG, "Transive SELECT APP ANS"); + FURI_LOG_I(TAG, "Transive SELECT APP ANS"); tx_len = emv_select_app_ans(tx_buff); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err == ERR_NONE) { - FURI_LOG_I(NFC_WORKER_TAG, "Received PDOL"); + FURI_LOG_I(TAG, "Received PDOL"); } else { - FURI_LOG_E(NFC_WORKER_TAG, "Error in 3rd data exchange: receive PDOL"); + FURI_LOG_E(TAG, "Error in 3rd data exchange: receive PDOL"); furi_hal_nfc_deactivate(); continue; } - FURI_LOG_I(NFC_WORKER_TAG, "Transive PDOL ANS"); + FURI_LOG_I(TAG, "Transive PDOL ANS"); tx_len = emv_get_proc_opt_ans(tx_buff); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err == ERR_NONE) { - FURI_LOG_I(NFC_WORKER_TAG, "Transive PDOL ANS"); + FURI_LOG_I(TAG, "Transive PDOL ANS"); } else { - FURI_LOG_E(NFC_WORKER_TAG, "Error in 4rd data exchange: Transive PDOL ANS"); + FURI_LOG_E(TAG, "Error in 4rd data exchange: Transive PDOL ANS"); furi_hal_nfc_deactivate(); continue; } if(*rx_len != sizeof(debug_rx) || memcmp(rx_buff, debug_rx, sizeof(debug_rx))) { - FURI_LOG_E(NFC_WORKER_TAG, "Failed long message test"); + FURI_LOG_E(TAG, "Failed long message test"); } else { - FURI_LOG_I(NFC_WORKER_TAG, "Correct debug message received"); + FURI_LOG_I(TAG, "Correct debug message received"); tx_len = sizeof(debug_tx); err = furi_hal_nfc_data_exchange( (uint8_t*)debug_tx, tx_len, &rx_buff, &rx_len, false); if(err == ERR_NONE) { - FURI_LOG_I(NFC_WORKER_TAG, "Transive Debug message"); + FURI_LOG_I(TAG, "Transive Debug message"); } } furi_hal_nfc_deactivate(); } else { - FURI_LOG_W(NFC_WORKER_TAG, "Can't find reader"); + FURI_LOG_W(TAG, "Can't find reader"); } osDelay(20); } @@ -499,71 +495,69 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { dev_list[0].dev.nfca.sensRes.platformInfo, dev_list[0].dev.nfca.selRes.sak)) { // Get Mifare Ultralight version - FURI_LOG_I(NFC_WORKER_TAG, "Found Mifare Ultralight tag. Reading tag version"); + FURI_LOG_I(TAG, "Found Mifare Ultralight tag. Reading tag version"); tx_len = mf_ul_prepare_get_version(tx_buff); err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); if(err == ERR_NONE) { mf_ul_parse_get_version_response(rx_buff, &mf_ul_read); FURI_LOG_I( - NFC_WORKER_TAG, + TAG, "Mifare Ultralight Type: %d, Pages: %d", mf_ul_read.type, mf_ul_read.pages_to_read); - FURI_LOG_I(NFC_WORKER_TAG, "Reading signature ..."); + FURI_LOG_I(TAG, "Reading signature ..."); tx_len = mf_ul_prepare_read_signature(tx_buff); if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { - FURI_LOG_W(NFC_WORKER_TAG, "Failed reading signature"); + FURI_LOG_W(TAG, "Failed reading signature"); memset(mf_ul_read.data.signature, 0, sizeof(mf_ul_read.data.signature)); } else { mf_ul_parse_read_signature_response(rx_buff, &mf_ul_read); } } else if(err == ERR_TIMEOUT) { FURI_LOG_W( - NFC_WORKER_TAG, + TAG, "Card doesn't respond to GET VERSION command. Setting default read parameters"); err = ERR_NONE; mf_ul_set_default_version(&mf_ul_read); // Reinit device furi_hal_nfc_deactivate(); if(!furi_hal_nfc_detect(&dev_list, &dev_cnt, 300, false)) { - FURI_LOG_E(NFC_WORKER_TAG, "Lost connection. Restarting search"); + FURI_LOG_E(TAG, "Lost connection. Restarting search"); continue; } } else { FURI_LOG_E( - NFC_WORKER_TAG, - "Error getting Mifare Ultralight version. Error code: %d", - err); + TAG, "Error getting Mifare Ultralight version. Error code: %d", err); continue; } if(mf_ul_read.support_fast_read) { - FURI_LOG_I(NFC_WORKER_TAG, "Reading pages ..."); + FURI_LOG_I(TAG, "Reading pages ..."); tx_len = mf_ul_prepare_fast_read(tx_buff, 0x00, mf_ul_read.pages_to_read - 1); if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { - FURI_LOG_E(NFC_WORKER_TAG, "Failed reading pages"); + FURI_LOG_E(TAG, "Failed reading pages"); continue; } else { mf_ul_parse_fast_read_response( rx_buff, 0x00, mf_ul_read.pages_to_read - 1, &mf_ul_read); } - FURI_LOG_I(NFC_WORKER_TAG, "Reading 3 counters ..."); + FURI_LOG_I(TAG, "Reading 3 counters ..."); for(uint8_t i = 0; i < 3; i++) { tx_len = mf_ul_prepare_read_cnt(tx_buff, i); if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { - FURI_LOG_W(NFC_WORKER_TAG, "Failed reading Counter %d", i); + FURI_LOG_W(TAG, "Failed reading Counter %d", i); mf_ul_read.data.counter[i] = 0; } else { mf_ul_parse_read_cnt_response(rx_buff, i, &mf_ul_read); } } - FURI_LOG_I(NFC_WORKER_TAG, "Checking tearing flags ..."); + FURI_LOG_I(TAG, "Checking tearing flags ..."); for(uint8_t i = 0; i < 3; i++) { tx_len = mf_ul_prepare_check_tearing(tx_buff, i); if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { - FURI_LOG_E(NFC_WORKER_TAG, "Error checking tearing flag %d", i); + FURI_LOG_E(TAG, "Error checking tearing flag %d", i); mf_ul_read.data.tearing[i] = MF_UL_TEARING_FLAG_DEFAULT; } else { mf_ul_parse_check_tearing_response(rx_buff, i, &mf_ul_read); @@ -572,11 +566,10 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { } else { // READ card with READ command (4 pages at a time) for(uint8_t page = 0; page < mf_ul_read.pages_to_read; page += 4) { - FURI_LOG_I(NFC_WORKER_TAG, "Reading pages %d - %d ...", page, page + 3); + FURI_LOG_I(TAG, "Reading pages %d - %d ...", page, page + 3); tx_len = mf_ul_prepare_read(tx_buff, page); if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { - FURI_LOG_E( - NFC_WORKER_TAG, "Read pages %d - %d failed", page, page + 3); + FURI_LOG_E(TAG, "Read pages %d - %d failed", page, page + 3); continue; } else { mf_ul_parse_read_response(rx_buff, page, &mf_ul_read); @@ -600,10 +593,10 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { } break; } else { - FURI_LOG_W(NFC_WORKER_TAG, "Tag does not support Mifare Ultralight"); + FURI_LOG_W(TAG, "Tag does not support Mifare Ultralight"); } } else { - FURI_LOG_W(NFC_WORKER_TAG, "Can't find any tags"); + FURI_LOG_W(TAG, "Can't find any tags"); } osDelay(100); } @@ -627,7 +620,7 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { data->nfc_data.sak, true, 200)) { - FURI_LOG_D(NFC_WORKER_TAG, "Anticollision passed"); + FURI_LOG_D(TAG, "Anticollision passed"); if(furi_hal_nfc_get_first_frame(&rx_buff, &rx_len)) { // Data exchange loop while(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { @@ -639,17 +632,17 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { if(err == ERR_NONE) { continue; } else { - FURI_LOG_E(NFC_WORKER_TAG, "Communication error: %d", err); + FURI_LOG_E(TAG, "Communication error: %d", err); break; } } else { - FURI_LOG_W(NFC_WORKER_TAG, "Not valid command: %02X", rx_buff[0]); + FURI_LOG_W(TAG, "Not valid command: %02X", rx_buff[0]); furi_hal_nfc_deactivate(); break; } } } else { - FURI_LOG_W(NFC_WORKER_TAG, "Error in 1st data exchange"); + FURI_LOG_W(TAG, "Error in 1st data exchange"); furi_hal_nfc_deactivate(); } } @@ -660,7 +653,7 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { nfc_worker->callback(nfc_worker->context); } } - FURI_LOG_W(NFC_WORKER_TAG, "Can't find reader"); + FURI_LOG_W(TAG, "Can't find reader"); osThreadYield(); } } diff --git a/applications/nfc/scenes/nfc_scene_card_menu.c b/applications/nfc/scenes/nfc_scene_card_menu.c index dbaefe4f..5724abdb 100755 --- a/applications/nfc/scenes/nfc_scene_card_menu.c +++ b/applications/nfc/scenes/nfc_scene_card_menu.c @@ -17,7 +17,7 @@ void nfc_scene_card_menu_on_enter(void* context) { Nfc* nfc = (Nfc*)context; Submenu* submenu = nfc->submenu; - if(nfc->dev.dev_data.nfc_data.protocol > NfcDeviceProtocolUnknown) { + if(nfc->dev->dev_data.nfc_data.protocol > NfcDeviceProtocolUnknown) { submenu_add_item( submenu, "Run compatible app", @@ -48,9 +48,9 @@ bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRunApp) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexRunApp); - if(nfc->dev.dev_data.nfc_data.protocol == NfcDeviceProtocolMifareUl) { + if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl); - } else if(nfc->dev.dev_data.nfc_data.protocol == NfcDeviceProtocolEMV) { + } else if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolEMV) { scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp); } return true; @@ -66,7 +66,7 @@ bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { return true; } else if(event.event == SubmenuIndexSave) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexSave); - nfc->dev.format = NfcDeviceSaveFormatUid; + nfc->dev->format = NfcDeviceSaveFormatUid; scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); return true; } diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/nfc/scenes/nfc_scene_delete.c index 993e1e37..498e6ecd 100755 --- a/applications/nfc/scenes/nfc_scene_delete.c +++ b/applications/nfc/scenes/nfc_scene_delete.c @@ -12,14 +12,14 @@ void nfc_scene_delete_on_enter(void* context) { // Setup Custom Widget view char delete_str[64]; - snprintf(delete_str, sizeof(delete_str), "Delete %s", nfc->dev.dev_name); - widget_add_string_element(nfc->widget, 64, 6, AlignCenter, AlignTop, FontPrimary, delete_str); + snprintf(delete_str, sizeof(delete_str), "\e#Delete %s\e#", nfc->dev->dev_name); + widget_add_text_box_element(nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, delete_str); widget_add_button_element( nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc); widget_add_button_element( nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc); char uid_str[32]; - NfcDeviceCommonData* data = &nfc->dev.dev_data.nfc_data; + NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; if(data->uid_len == 4) { snprintf( uid_str, @@ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { if(event.event == GuiButtonTypeLeft) { return scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == GuiButtonTypeRight) { - if(nfc_device_delete(&nfc->dev)) { + if(nfc_device_delete(nfc->dev)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_device_info.c b/applications/nfc/scenes/nfc_scene_device_info.c index 674b3f6d..1495c127 100755 --- a/applications/nfc/scenes/nfc_scene_device_info.c +++ b/applications/nfc/scenes/nfc_scene_device_info.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include "../helpers/nfc_emv_parser.h" #define NFC_SCENE_DEVICE_INFO_BACK_EVENT (0UL) @@ -35,14 +36,14 @@ void nfc_scene_device_info_on_enter(void* context) { Nfc* nfc = context; // Setup Custom Widget view - widget_add_string_element( - nfc->widget, 64, 6, AlignCenter, AlignTop, FontSecondary, nfc->dev.dev_name); + widget_add_text_box_element( + nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, nfc->dev->dev_name); widget_add_button_element( nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc); widget_add_button_element( nfc->widget, GuiButtonTypeRight, "Data", nfc_scene_device_info_widget_callback, nfc); char uid_str[32]; - NfcDeviceCommonData* data = &nfc->dev.dev_data.nfc_data; + NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; if(data->uid_len == 4) { snprintf( uid_str, @@ -87,14 +88,14 @@ void nfc_scene_device_info_on_enter(void* context) { widget_add_string_element(nfc->widget, 118, 42, AlignRight, AlignTop, FontSecondary, atqa_str); // Setup Data View - if(nfc->dev.format == NfcDeviceSaveFormatUid) { + if(nfc->dev->format == NfcDeviceSaveFormatUid) { DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Back"); dialog_ex_set_text(dialog_ex, "No data", 64, 32, AlignCenter, AlignCenter); dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_device_info_dialog_callback); - } else if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { - MifareUlData* mf_ul_data = (MifareUlData*)&nfc->dev.dev_data.mf_ul_data; + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { + MifareUlData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; TextBox* text_box = nfc->text_box; text_box_set_context(text_box, nfc); text_box_set_exit_callback(text_box, nfc_scene_device_info_text_box_callback); @@ -107,8 +108,8 @@ void nfc_scene_device_info_on_enter(void* context) { nfc->text_box_store, "%02X%02X ", mf_ul_data->data[i], mf_ul_data->data[i + 1]); } text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); - } else if(nfc->dev.format == NfcDeviceSaveFormatBankCard) { - NfcEmvData* emv_data = &nfc->dev.dev_data.emv_data; + } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { + NfcEmvData* emv_data = &nfc->dev->dev_data.emv_data; BankCard* bank_card = nfc->bank_card; bank_card_set_name(bank_card, emv_data->name); bank_card_set_number(bank_card, emv_data->number, emv_data->number_len); @@ -116,12 +117,29 @@ void nfc_scene_device_info_on_enter(void* context) { if(emv_data->exp_mon) { bank_card_set_exp_date(bank_card, emv_data->exp_mon, emv_data->exp_year); } + string_t display_str; + string_init(display_str); if(emv_data->country_code) { - bank_card_set_country_name(bank_card, emv_data->country_code); + string_t country_name; + string_init(country_name); + if(nfc_emv_parser_get_country_name( + nfc->dev->storage, emv_data->country_code, country_name)) { + string_printf(display_str, "Reg:%s", string_get_cstr(country_name)); + bank_card_set_country_name(bank_card, string_get_cstr(display_str)); + } + string_clear(country_name); } if(emv_data->currency_code) { - bank_card_set_currency_name(bank_card, emv_data->currency_code); + string_t currency_name; + string_init(currency_name); + if(nfc_emv_parser_get_currency_name( + nfc->dev->storage, emv_data->country_code, currency_name)) { + string_printf(display_str, "Cur:%s", string_get_cstr(currency_name)); + bank_card_set_currency_name(bank_card, string_get_cstr(display_str)); + } + string_clear(currency_name); } + string_clear(display_str); } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoUid); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); @@ -136,17 +154,17 @@ bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) { if((state == NfcSceneDeviceInfoUid) && (event.event == GuiButtonTypeLeft)) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if((state == NfcSceneDeviceInfoUid) && (event.event == GuiButtonTypeRight)) { - if(nfc->dev.format == NfcDeviceSaveFormatUid) { + if(nfc->dev->format == NfcDeviceSaveFormatUid) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); consumed = true; - } else if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); consumed = true; - } else if(nfc->dev.format == NfcDeviceSaveFormatBankCard) { + } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewBankCard); @@ -168,7 +186,7 @@ void nfc_scene_device_info_on_exit(void* context) { // Clear Custom Widget widget_clear(nfc->widget); - if(nfc->dev.format == NfcDeviceSaveFormatUid) { + if(nfc->dev->format == NfcDeviceSaveFormatUid) { // Clear Dialog DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); @@ -179,11 +197,11 @@ void nfc_scene_device_info_on_exit(void* context) { dialog_ex_set_center_button_text(dialog_ex, NULL); dialog_ex_set_result_callback(dialog_ex, NULL); dialog_ex_set_context(dialog_ex, NULL); - } else if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { // Clear TextBox text_box_clean(nfc->text_box); - string_clean(nfc->text_box_store); - } else if(nfc->dev.format == NfcDeviceSaveFormatBankCard) { + string_reset(nfc->text_box_store); + } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { // Clear Bank Card bank_card_clear(nfc->bank_card); } diff --git a/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c index b08660b3..c9e822e2 100644 --- a/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ b/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c @@ -11,7 +11,7 @@ void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { // Setup and start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev.dev_data, NULL, nfc); + nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc); } bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c b/applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c index cba09437..bacdd347 100755 --- a/applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c +++ b/applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c @@ -14,8 +14,8 @@ void nfc_scene_emulate_mifare_ul_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - if(strcmp(nfc->dev.dev_name, "")) { - nfc_text_store_set(nfc, "%s", nfc->dev.dev_name); + if(strcmp(nfc->dev->dev_name, "")) { + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); } popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); popup_set_header(popup, "Emulating\nMf Ultralight", 56, 31, AlignLeft, AlignTop); @@ -25,7 +25,7 @@ void nfc_scene_emulate_mifare_ul_on_enter(void* context) { nfc_worker_start( nfc->worker, NfcWorkerStateEmulateMifareUl, - &nfc->dev.dev_data, + &nfc->dev->dev_data, nfc_emulate_mifare_ul_worker_callback, nfc); } @@ -45,7 +45,7 @@ bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event NFC_MF_UL_DATA_CHANGED) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_NOT_CHANGED); - nfc_device_save_shadow(&nfc->dev, nfc->dev.dev_name); + nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); } consumed = false; } diff --git a/applications/nfc/scenes/nfc_scene_emulate_uid.c b/applications/nfc/scenes/nfc_scene_emulate_uid.c index ccd27de5..a8fff57b 100755 --- a/applications/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/nfc/scenes/nfc_scene_emulate_uid.c @@ -5,10 +5,10 @@ void nfc_scene_emulate_uid_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - NfcDeviceCommonData* data = &nfc->dev.dev_data.nfc_data; + NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; - if(strcmp(nfc->dev.dev_name, "")) { - nfc_text_store_set(nfc, "%s", nfc->dev.dev_name); + if(strcmp(nfc->dev->dev_name, "")) { + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); } else if(data->uid_len == 4) { nfc_text_store_set( nfc, "%02X %02X %02X %02X", data->uid[0], data->uid[1], data->uid[2], data->uid[3]); @@ -32,7 +32,7 @@ void nfc_scene_emulate_uid_on_enter(void* context) { // Setup and start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start(nfc->worker, NfcWorkerStateEmulate, &nfc->dev.dev_data, NULL, nfc); + nfc_worker_start(nfc->worker, NfcWorkerStateEmulate, &nfc->dev->dev_data, NULL, nfc); } bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/nfc/scenes/nfc_scene_file_select.c b/applications/nfc/scenes/nfc_scene_file_select.c index 67894fbf..010e807e 100755 --- a/applications/nfc/scenes/nfc_scene_file_select.c +++ b/applications/nfc/scenes/nfc_scene_file_select.c @@ -3,7 +3,7 @@ void nfc_scene_file_select_on_enter(void* context) { Nfc* nfc = (Nfc*)context; // Process file_select return - if(nfc_file_select(&nfc->dev)) { + if(nfc_file_select(nfc->dev)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); } else { scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); diff --git a/applications/nfc/scenes/nfc_scene_mifare_ul_menu.c b/applications/nfc/scenes/nfc_scene_mifare_ul_menu.c index 236da374..344d4733 100755 --- a/applications/nfc/scenes/nfc_scene_mifare_ul_menu.c +++ b/applications/nfc/scenes/nfc_scene_mifare_ul_menu.c @@ -32,9 +32,9 @@ bool nfc_scene_mifare_ul_menu_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexSave) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave); - nfc->dev.format = NfcDeviceSaveFormatMifareUl; + nfc->dev->format = NfcDeviceSaveFormatMifareUl; // Clear device name - nfc_device_set_name(&nfc->dev, ""); + nfc_device_set_name(nfc->dev, ""); scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); return true; } else if(event.event == SubmenuIndexEmulate) { diff --git a/applications/nfc/scenes/nfc_scene_read_card.c b/applications/nfc/scenes/nfc_scene_read_card.c index 8853858e..d01fa75d 100755 --- a/applications/nfc/scenes/nfc_scene_read_card.c +++ b/applications/nfc/scenes/nfc_scene_read_card.c @@ -18,7 +18,7 @@ void nfc_scene_read_card_on_enter(void* context) { // Start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); nfc_worker_start( - nfc->worker, NfcWorkerStateDetect, &nfc->dev.dev_data, nfc_read_card_worker_callback, nfc); + nfc->worker, NfcWorkerStateDetect, &nfc->dev->dev_data, nfc_read_card_worker_callback, nfc); } bool nfc_scene_read_card_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/nfc/scenes/nfc_scene_read_card_success.c b/applications/nfc/scenes/nfc_scene_read_card_success.c index 5cac8258..c70bf1b2 100755 --- a/applications/nfc/scenes/nfc_scene_read_card_success.c +++ b/applications/nfc/scenes/nfc_scene_read_card_success.c @@ -15,7 +15,7 @@ void nfc_scene_read_card_success_on_enter(void* context) { notification_message(nfc->notifications, &sequence_success); // Setup view - NfcDeviceCommonData* data = (NfcDeviceCommonData*)&nfc->dev.dev_data.nfc_data; + NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "More"); @@ -68,7 +68,7 @@ bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event return scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultRight) { // Clear device name - nfc_device_set_name(&nfc->dev, ""); + nfc_device_set_name(nfc->dev, ""); scene_manager_next_scene(nfc->scene_manager, NfcSceneCardMenu); return true; } diff --git a/applications/nfc/scenes/nfc_scene_read_emv_app.c b/applications/nfc/scenes/nfc_scene_read_emv_app.c index 63509ad8..665a121b 100755 --- a/applications/nfc/scenes/nfc_scene_read_emv_app.c +++ b/applications/nfc/scenes/nfc_scene_read_emv_app.c @@ -20,7 +20,7 @@ void nfc_scene_read_emv_app_on_enter(void* context) { nfc_worker_start( nfc->worker, NfcWorkerStateReadEMVApp, - &nfc->dev.dev_data, + &nfc->dev->dev_data, nfc_read_emv_app_worker_callback, nfc); } diff --git a/applications/nfc/scenes/nfc_scene_read_emv_app_success.c b/applications/nfc/scenes/nfc_scene_read_emv_app_success.c index bea8d719..3fe62f3f 100755 --- a/applications/nfc/scenes/nfc_scene_read_emv_app_success.c +++ b/applications/nfc/scenes/nfc_scene_read_emv_app_success.c @@ -13,8 +13,8 @@ void nfc_scene_read_emv_app_success_on_enter(void* context) { Nfc* nfc = (Nfc*)context; // Setup view - NfcDeviceCommonData* nfc_data = &nfc->dev.dev_data.nfc_data; - NfcEmvData* emv_data = &nfc->dev.dev_data.emv_data; + NfcDeviceCommonData* nfc_data = &nfc->dev->dev_data.nfc_data; + NfcEmvData* emv_data = &nfc->dev->dev_data.emv_data; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "Run app"); @@ -23,7 +23,8 @@ void nfc_scene_read_emv_app_success_on_enter(void* context) { // Display UID and AID string_t aid; string_init(aid); - bool aid_found = nfc_emv_parser_get_aid_name(emv_data->aid, emv_data->aid_len, aid); + bool aid_found = + nfc_emv_parser_get_aid_name(nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid); if(!aid_found) { for(uint8_t i = 0; i < emv_data->aid_len; i++) { string_cat_printf(aid, "%02X", emv_data->aid[i]); diff --git a/applications/nfc/scenes/nfc_scene_read_emv_data.c b/applications/nfc/scenes/nfc_scene_read_emv_data.c index 953ac36f..4da1aaa7 100755 --- a/applications/nfc/scenes/nfc_scene_read_emv_data.c +++ b/applications/nfc/scenes/nfc_scene_read_emv_data.c @@ -17,12 +17,12 @@ void nfc_scene_read_emv_data_on_enter(void* context) { view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); // Clear emv data - memset(&nfc->dev.dev_data.emv_data, 0, sizeof(nfc->dev.dev_data.emv_data)); + memset(&nfc->dev->dev_data.emv_data, 0, sizeof(nfc->dev->dev_data.emv_data)); // Start worker nfc_worker_start( nfc->worker, NfcWorkerStateReadEMV, - &nfc->dev.dev_data, + &nfc->dev->dev_data, nfc_read_emv_data_worker_callback, nfc); } diff --git a/applications/nfc/scenes/nfc_scene_read_emv_data_success.c b/applications/nfc/scenes/nfc_scene_read_emv_data_success.c index 92402c13..0867311f 100755 --- a/applications/nfc/scenes/nfc_scene_read_emv_data_success.c +++ b/applications/nfc/scenes/nfc_scene_read_emv_data_success.c @@ -13,8 +13,8 @@ void nfc_scene_read_emv_data_success_widget_callback( void nfc_scene_read_emv_data_success_on_enter(void* context) { Nfc* nfc = (Nfc*)context; - NfcEmvData* emv_data = &nfc->dev.dev_data.emv_data; - NfcDeviceCommonData* nfc_data = &nfc->dev.dev_data.nfc_data; + NfcEmvData* emv_data = &nfc->dev->dev_data.emv_data; + NfcDeviceCommonData* nfc_data = &nfc->dev->dev_data.nfc_data; // Setup Custom Widget view // Add frame @@ -34,7 +34,7 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) { nfc); // Add card name widget_add_string_element( - nfc->widget, 64, 3, AlignCenter, AlignTop, FontSecondary, nfc->dev.dev_data.emv_data.name); + nfc->widget, 64, 3, AlignCenter, AlignTop, FontSecondary, nfc->dev->dev_data.emv_data.name); // Add cad number string_t pan_str; string_init(pan_str); @@ -49,7 +49,7 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) { string_t country_name; string_init(country_name); if((emv_data->country_code) && - nfc_emv_parser_get_country_name(emv_data->country_code, country_name)) { + nfc_emv_parser_get_country_name(nfc->dev->storage, emv_data->country_code, country_name)) { string_t disp_country; string_init_printf(disp_country, "Reg:%s", country_name); widget_add_string_element( @@ -61,7 +61,8 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) { string_t currency_name; string_init(currency_name); if((emv_data->currency_code) && - nfc_emv_parser_get_currency_name(emv_data->currency_code, currency_name)) { + nfc_emv_parser_get_currency_name( + nfc->dev->storage, emv_data->currency_code, currency_name)) { string_t disp_currency; string_init_printf(disp_currency, "Cur:%s", currency_name); widget_add_string_element( @@ -122,8 +123,8 @@ bool nfc_scene_read_emv_data_success_on_event(void* context, SceneManagerEvent e nfc->scene_manager, NfcSceneReadEmvAppSuccess); } else if(event.event == GuiButtonTypeRight) { // Clear device name - nfc_device_set_name(&nfc->dev, ""); - nfc->dev.format = NfcDeviceSaveFormatBankCard; + nfc_device_set_name(nfc->dev, ""); + nfc->dev->format = NfcDeviceSaveFormatBankCard; scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); return true; } diff --git a/applications/nfc/scenes/nfc_scene_read_mifare_ul.c b/applications/nfc/scenes/nfc_scene_read_mifare_ul.c index f4d4a6dc..a0bd057d 100755 --- a/applications/nfc/scenes/nfc_scene_read_mifare_ul.c +++ b/applications/nfc/scenes/nfc_scene_read_mifare_ul.c @@ -20,7 +20,7 @@ void nfc_scene_read_mifare_ul_on_enter(void* context) { nfc_worker_start( nfc->worker, NfcWorkerStateReadMifareUl, - &nfc->dev.dev_data, + &nfc->dev->dev_data, nfc_read_mifare_ul_worker_callback, nfc); } diff --git a/applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c b/applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c index 55571666..9c8dfc78 100755 --- a/applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c +++ b/applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c @@ -27,7 +27,7 @@ void nfc_scene_read_mifare_ul_success_on_enter(void* context) { notification_message(nfc->notifications, &sequence_success); // Setup dialog view - NfcDeviceCommonData* data = (NfcDeviceCommonData*)&nfc->dev.dev_data.nfc_data; + NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "More"); @@ -54,7 +54,7 @@ void nfc_scene_read_mifare_ul_success_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, nfc_scene_read_mifare_ul_success_dialog_callback); // Setup TextBox view - MifareUlData* mf_ul_data = (MifareUlData*)&nfc->dev.dev_data.mf_ul_data; + MifareUlData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; TextBox* text_box = nfc->text_box; text_box_set_context(text_box, nfc); text_box_set_exit_callback(text_box, nfc_scene_read_mifare_ul_success_text_box_callback); @@ -126,5 +126,5 @@ void nfc_scene_read_mifare_ul_success_on_exit(void* context) { // Clean TextBox TextBox* text_box = nfc->text_box; text_box_clean(text_box); - string_clean(nfc->text_box_store); + string_reset(nfc->text_box_store); } diff --git a/applications/nfc/scenes/nfc_scene_save_name.c b/applications/nfc/scenes/nfc_scene_save_name.c index ca3afdac..44babcdd 100755 --- a/applications/nfc/scenes/nfc_scene_save_name.c +++ b/applications/nfc/scenes/nfc_scene_save_name.c @@ -15,11 +15,11 @@ void nfc_scene_save_name_on_enter(void* context) { // Setup view TextInput* text_input = nfc->text_input; bool dev_name_empty = false; - if(!strcmp(nfc->dev.dev_name, "")) { + if(!strcmp(nfc->dev->dev_name, "")) { set_random_name(nfc->text_store, sizeof(nfc->text_store)); dev_name_empty = true; } else { - nfc_text_store_set(nfc, nfc->dev.dev_name); + nfc_text_store_set(nfc, nfc->dev->dev_name); } text_input_set_header_text(text_input, "Name the card"); text_input_set_result_callback( @@ -37,14 +37,14 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SCENE_SAVE_NAME_CUSTOM_EVENT) { - if(strcmp(nfc->dev.dev_name, "")) { - nfc_device_delete(&nfc->dev); + if(strcmp(nfc->dev->dev_name, "")) { + nfc_device_delete(nfc->dev); } if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { - nfc->dev.dev_data.nfc_data = nfc->dev_edit_data; + nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; } - strlcpy(nfc->dev.dev_name, nfc->text_store, strlen(nfc->text_store) + 1); - if(nfc_device_save(&nfc->dev, nfc->text_store)) { + strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); + if(nfc_device_save(nfc->dev, nfc->text_store)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); return true; } else { diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/nfc/scenes/nfc_scene_saved_menu.c index a8e2ce7f..ea38ce3d 100755 --- a/applications/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/nfc/scenes/nfc_scene_saved_menu.c @@ -18,7 +18,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { Nfc* nfc = (Nfc*)context; Submenu* submenu = nfc->submenu; - if(nfc->dev.format != NfcDeviceSaveFormatBankCard) { + if(nfc->dev->format != NfcDeviceSaveFormatBankCard) { submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); } @@ -30,7 +30,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); submenu_set_selected_item( nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSavedMenu)); - if(nfc->dev.shadow_file_exist) { + if(nfc->dev->shadow_file_exist) { submenu_add_item( submenu, "Restore original", @@ -49,7 +49,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, event.event); if(event.event == SubmenuIndexEmulate) { - if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { + if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); @@ -65,7 +65,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); consumed = true; } else if(event.event == SubmenuIndexRestoreOriginal) { - if(!nfc_device_restore(&nfc->dev)) { + if(!nfc_device_restore(nfc->dev)) { scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); } else { diff --git a/applications/nfc/scenes/nfc_scene_set_atqa.c b/applications/nfc/scenes/nfc_scene_set_atqa.c index 7936880c..1c682f18 100755 --- a/applications/nfc/scenes/nfc_scene_set_atqa.c +++ b/applications/nfc/scenes/nfc_scene_set_atqa.c @@ -19,7 +19,7 @@ void nfc_scene_set_atqa_on_enter(void* context) { nfc_scene_set_atqa_byte_input_callback, NULL, nfc, - nfc->dev.dev_data.nfc_data.atqa, + nfc->dev->dev_data.nfc_data.atqa, 2); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); } diff --git a/applications/nfc/scenes/nfc_scene_set_sak.c b/applications/nfc/scenes/nfc_scene_set_sak.c index 5df999d0..edeb27a9 100755 --- a/applications/nfc/scenes/nfc_scene_set_sak.c +++ b/applications/nfc/scenes/nfc_scene_set_sak.c @@ -19,7 +19,7 @@ void nfc_scene_set_sak_on_enter(void* context) { nfc_scene_set_sak_byte_input_callback, NULL, nfc, - &nfc->dev.dev_data.nfc_data.sak, + &nfc->dev->dev_data.nfc_data.sak, 1); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); } diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/nfc/scenes/nfc_scene_set_type.c index badab0c0..66eafe83 100755 --- a/applications/nfc/scenes/nfc_scene_set_type.c +++ b/applications/nfc/scenes/nfc_scene_set_type.c @@ -15,7 +15,7 @@ void nfc_scene_set_type_on_enter(void* context) { Nfc* nfc = (Nfc*)context; Submenu* submenu = nfc->submenu; // Clear device name - nfc_device_set_name(&nfc->dev, ""); + nfc_device_set_name(nfc->dev, ""); submenu_add_item( submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); submenu_add_item( @@ -28,13 +28,13 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexNFCA7) { - nfc->dev.dev_data.nfc_data.uid_len = 7; - nfc->dev.format = NfcDeviceSaveFormatUid; + nfc->dev->dev_data.nfc_data.uid_len = 7; + nfc->dev->format = NfcDeviceSaveFormatUid; scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak); return true; } else if(event.event == SubmenuIndexNFCA4) { - nfc->dev.dev_data.nfc_data.uid_len = 4; - nfc->dev.format = NfcDeviceSaveFormatUid; + nfc->dev->dev_data.nfc_data.uid_len = 4; + nfc->dev->format = NfcDeviceSaveFormatUid; scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak); return true; } diff --git a/applications/nfc/scenes/nfc_scene_set_uid.c b/applications/nfc/scenes/nfc_scene_set_uid.c index 7ecfbf32..5d179f77 100755 --- a/applications/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/nfc/scenes/nfc_scene_set_uid.c @@ -14,7 +14,7 @@ void nfc_scene_set_uid_on_enter(void* context) { // Setup view ByteInput* byte_input = nfc->byte_input; byte_input_set_header_text(byte_input, "Enter uid in hex"); - nfc->dev_edit_data = nfc->dev.dev_data.nfc_data; + nfc->dev_edit_data = nfc->dev->dev_data.nfc_data; byte_input_set_result_callback( byte_input, nfc_scene_set_uid_byte_input_callback, diff --git a/applications/nfc/scenes/nfc_scene_start.c b/applications/nfc/scenes/nfc_scene_start.c index cdf7dbe2..bfeb494b 100755 --- a/applications/nfc/scenes/nfc_scene_start.c +++ b/applications/nfc/scenes/nfc_scene_start.c @@ -34,7 +34,7 @@ void nfc_scene_start_on_enter(void* context) { submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneStart)); - nfc_device_clear(&nfc->dev); + nfc_device_clear(nfc->dev); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } diff --git a/applications/nfc/views/bank_card.c b/applications/nfc/views/bank_card.c index 483491d6..07b4db58 100755 --- a/applications/nfc/views/bank_card.c +++ b/applications/nfc/views/bank_card.c @@ -67,42 +67,14 @@ void bank_card_set_exp_date(BankCard* bank_card, uint8_t mon, uint8_t year) { bank_card->widget, 122, 54, AlignRight, AlignBottom, FontSecondary, exp_date_str); } -void bank_card_set_country_name(BankCard* bank_card, uint16_t country_code) { +void bank_card_set_country_name(BankCard* bank_card, const char* country_name) { furi_assert(bank_card); - string_t country_name; - string_init(country_name); - if(nfc_emv_parser_get_country_name(country_code, country_name)) { - string_t disp_country; - string_init_printf(disp_country, "Reg:%s", country_name); - widget_add_string_element( - bank_card->widget, - 120, - 18, - AlignRight, - AlignTop, - FontSecondary, - string_get_cstr(disp_country)); - string_clear(disp_country); - } - string_clear(country_name); + widget_add_string_element( + bank_card->widget, 120, 18, AlignRight, AlignTop, FontSecondary, country_name); } -void bank_card_set_currency_name(BankCard* bank_card, uint16_t currency_code) { +void bank_card_set_currency_name(BankCard* bank_card, const char* currency_name) { furi_assert(bank_card); - string_t currency_name; - string_init(currency_name); - if(nfc_emv_parser_get_currency_name(currency_code, currency_name)) { - string_t disp_currency; - string_init_printf(disp_currency, "Cur:%s", currency_name); - widget_add_string_element( - bank_card->widget, - 31, - 18, - AlignLeft, - AlignTop, - FontSecondary, - string_get_cstr(disp_currency)); - string_clear(disp_currency); - } - string_clear(currency_name); + widget_add_string_element( + bank_card->widget, 31, 18, AlignLeft, AlignTop, FontSecondary, currency_name); } diff --git a/applications/nfc/views/bank_card.h b/applications/nfc/views/bank_card.h index a4342856..628d9deb 100644 --- a/applications/nfc/views/bank_card.h +++ b/applications/nfc/views/bank_card.h @@ -21,6 +21,6 @@ void bank_card_set_number(BankCard* bank_card, uint8_t* number, uint8_t len); void bank_card_set_exp_date(BankCard* bank_card, uint8_t mon, uint8_t year); -void bank_card_set_country_name(BankCard* bank_card, uint16_t country_code); +void bank_card_set_country_name(BankCard* bank_card, const char* country_name); -void bank_card_set_currency_name(BankCard* bank_card, uint16_t currency_code); +void bank_card_set_currency_name(BankCard* bank_card, const char* currency_name); diff --git a/applications/notification/notification-app.c b/applications/notification/notification-app.c index 22b6794e..62a58ff6 100644 --- a/applications/notification/notification-app.c +++ b/applications/notification/notification-app.c @@ -5,6 +5,8 @@ #include "notification-messages.h" #include "notification-app.h" +#define TAG "NotificationSrv" + static const uint8_t minimal_delay = 100; static const uint8_t led_off_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; @@ -314,7 +316,7 @@ static bool notification_load_settings(NotificationApp* app) { File* file = storage_file_alloc(furi_record_open("storage")); const size_t settings_size = sizeof(NotificationSettings); - FURI_LOG_I("notification", "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH); + FURI_LOG_I(TAG, "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH); bool fs_result = storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); @@ -327,21 +329,18 @@ static bool notification_load_settings(NotificationApp* app) { } if(fs_result) { - FURI_LOG_I("notification", "load success"); + FURI_LOG_I(TAG, "load success"); if(settings.version != NOTIFICATION_SETTINGS_VERSION) { FURI_LOG_E( - "notification", - "version(%d != %d) mismatch", - settings.version, - NOTIFICATION_SETTINGS_VERSION); + TAG, "version(%d != %d) mismatch", settings.version, NOTIFICATION_SETTINGS_VERSION); } else { osKernelLock(); memcpy(&app->settings, &settings, settings_size); osKernelUnlock(); } } else { - FURI_LOG_E("notification", "load failed, %s", storage_file_get_error_desc(file)); + FURI_LOG_E(TAG, "load failed, %s", storage_file_get_error_desc(file)); } storage_file_close(file); @@ -356,7 +355,7 @@ static bool notification_save_settings(NotificationApp* app) { File* file = storage_file_alloc(furi_record_open("storage")); const size_t settings_size = sizeof(NotificationSettings); - FURI_LOG_I("notification", "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH); + FURI_LOG_I(TAG, "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH); osKernelLock(); memcpy(&settings, &app->settings, settings_size); @@ -374,9 +373,9 @@ static bool notification_save_settings(NotificationApp* app) { } if(fs_result) { - FURI_LOG_I("notification", "save success"); + FURI_LOG_I(TAG, "save success"); } else { - FURI_LOG_E("notification", "save failed, %s", storage_file_get_error_desc(file)); + FURI_LOG_E(TAG, "save failed, %s", storage_file_get_error_desc(file)); } storage_file_close(file); @@ -427,7 +426,7 @@ static NotificationApp* notification_app_alloc() { // display backlight control app->event_record = furi_record_open("input_events"); - subscribe_pubsub(app->event_record, input_event_callback, app); + furi_pubsub_subscribe(app->event_record, input_event_callback, app); notification_message(app, &sequence_display_on); return app; diff --git a/applications/notification/notification-app.h b/applications/notification/notification-app.h index 78c58f6d..f1fe5cef 100644 --- a/applications/notification/notification-app.h +++ b/applications/notification/notification-app.h @@ -44,7 +44,7 @@ typedef struct { struct NotificationApp { osMessageQueueId_t queue; - PubSub* event_record; + FuriPubSub* event_record; osTimerId_t display_timer; NotificationLedLayer display; diff --git a/applications/power/power_cli.c b/applications/power/power_cli.c index 4240ae45..63c0fda5 100644 --- a/applications/power/power_cli.c +++ b/applications/power/power_cli.c @@ -23,7 +23,7 @@ void power_cli_factory_reset(Cli* cli, string_t args, void* context) { char c = cli_getc(cli); if(c == 'y' || c == 'Y') { printf("Data will be wiped after reboot.\r\n"); - furi_hal_boot_set_flags(FuriHalBootFlagFactoryReset); + furi_hal_bootloader_set_flags(FuriHalBootloaderFlagFactoryReset); power_reboot(PowerBootModeNormal); } else { printf("Safe choice.\r\n"); diff --git a/applications/power/power_service/power.c b/applications/power/power_service/power.c index de712fd4..94137d73 100755 --- a/applications/power/power_service/power.c +++ b/applications/power/power_service/power.c @@ -31,7 +31,7 @@ Power* power_alloc() { power->gui = furi_record_open("gui"); // Pubsub - init_pubsub(&power->event_pubsub); + power->event_pubsub = furi_pubsub_alloc(); // State initialization power->state = PowerStateNotCharging; @@ -60,10 +60,6 @@ Power* power_alloc() { void power_free(Power* power) { furi_assert(power); - // Records - furi_record_close("notification"); - furi_record_close("gui"); - // Gui view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff); power_off_free(power->power_off); @@ -73,6 +69,14 @@ void power_free(Power* power) { // State osMutexDelete(power->info_mtx); + + // FuriPubSub + furi_pubsub_free(power->event_pubsub); + + // Records + furi_record_close("notification"); + furi_record_close("gui"); + free(power); } @@ -83,14 +87,14 @@ static void power_check_charging_state(Power* power) { notification_internal_message(power->notification, &sequence_charged); power->state = PowerStateCharged; power->event.type = PowerEventTypeFullyCharged; - notify_pubsub(&power->event_pubsub, &power->event); + furi_pubsub_publish(power->event_pubsub, &power->event); } } else { if(power->state != PowerStateCharging) { notification_internal_message(power->notification, &sequence_charging); power->state = PowerStateCharging; power->event.type = PowerEventTypeStartCharging; - notify_pubsub(&power->event_pubsub, &power->event); + furi_pubsub_publish(power->event_pubsub, &power->event); } } } else { @@ -98,7 +102,7 @@ static void power_check_charging_state(Power* power) { notification_internal_message(power->notification, &sequence_not_charging); power->state = PowerStateNotCharging; power->event.type = PowerEventTypeStopCharging; - notify_pubsub(&power->event_pubsub, &power->event); + furi_pubsub_publish(power->event_pubsub, &power->event); } } } @@ -156,7 +160,7 @@ static void power_check_battery_level_change(Power* power) { power->battery_level = power->info.charge; power->event.type = PowerEventTypeBatteryLevelChanged; power->event.data.battery_level = power->battery_level; - notify_pubsub(&power->event_pubsub, &power->event); + furi_pubsub_publish(power->event_pubsub, &power->event); } } diff --git a/applications/power/power_service/power.h b/applications/power/power_service/power.h index d3dec410..947d917c 100644 --- a/applications/power/power_service/power.h +++ b/applications/power/power_service/power.h @@ -62,4 +62,4 @@ void power_get_info(Power* power, PowerInfo* info); /** Get power event pubsub handler * @param power - Power instance */ -PubSub* power_get_pubsub(Power* power); +FuriPubSub* power_get_pubsub(Power* power); diff --git a/applications/power/power_service/power_api.c b/applications/power/power_service/power_api.c index 4147d17d..a3278f9b 100644 --- a/applications/power/power_service/power_api.c +++ b/applications/power/power_service/power_api.c @@ -1,7 +1,8 @@ #include "power_i.h" + #include #include "furi-hal-power.h" -#include "furi-hal-boot.h" +#include "furi-hal-bootloader.h" void power_off(Power* power) { furi_hal_power_off(); @@ -14,9 +15,9 @@ void power_off(Power* power) { void power_reboot(PowerBootMode mode) { if(mode == PowerBootModeNormal) { - furi_hal_boot_set_mode(FuriHalBootModeNormal); + furi_hal_bootloader_set_mode(FuriHalBootloaderModeNormal); } else if(mode == PowerBootModeDfu) { - furi_hal_boot_set_mode(FuriHalBootModeDFU); + furi_hal_bootloader_set_mode(FuriHalBootloaderModeDFU); } furi_hal_power_reset(); } @@ -30,7 +31,7 @@ void power_get_info(Power* power, PowerInfo* info) { osMutexRelease(power->info_mtx); } -PubSub* power_get_pubsub(Power* power) { +FuriPubSub* power_get_pubsub(Power* power) { furi_assert(power); - return &power->event_pubsub; + return power->event_pubsub; } diff --git a/applications/power/power_service/power_i.h b/applications/power/power_service/power_i.h index 5bebd8cb..9833df5f 100755 --- a/applications/power/power_service/power_i.h +++ b/applications/power/power_service/power_i.h @@ -25,7 +25,7 @@ struct Power { ViewPort* battery_view_port; Gui* gui; NotificationApp* notification; - PubSub event_pubsub; + FuriPubSub* event_pubsub; PowerEvent event; PowerState state; diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index adb23e78..450daaee 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -1,31 +1,28 @@ -#include "cmsis_os.h" -#include "cmsis_os2.h" -#include "flipper.pb.h" -#include "furi-hal-delay.h" -#include "furi/check.h" -#include "furi/log.h" -#include -#include "pb.h" -#include "pb_decode.h" -#include "pb_encode.h" -#include "portmacro.h" -#include "status.pb.h" -#include "storage.pb.h" +#include "rpc_i.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include -#include #include +#include #include -#include "rpc_i.h" -#define RPC_TAG "RPC" +#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) -#define DEBUG_PRINT 0 - DICT_DEF2(RpcHandlerDict, pb_size_t, M_DEFAULT_OPLIST, RpcHandler, M_POD_OPLIST) typedef struct { @@ -47,14 +44,20 @@ static RpcSystemCallbacks rpc_systems[] = { .alloc = rpc_system_app_alloc, .free = NULL, }, + { + .alloc = rpc_system_gui_alloc, + .free = rpc_system_gui_free, + }, }; struct RpcSession { RpcSendBytesCallback send_bytes_callback; - void* send_bytes_context; - osMutexId_t send_bytes_mutex; + RpcBufferIsEmptyCallback buffer_is_empty_callback; + RpcSessionClosedCallback closed_callback; + void* context; + osMutexId_t callbacks_mutex; Rpc* rpc; - bool terminate_session; + bool terminate; void** system_contexts; }; @@ -70,6 +73,20 @@ struct Rpc { 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); + + Rpc* rpc = context; + 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); + } + osMutexRelease(rpc->session.callbacks_mutex); +} + static size_t rpc_sprintf_msg_file( string_t str, const char* prefix, @@ -105,6 +122,31 @@ static size_t rpc_sprintf_msg_file( return cnt; } +void rpc_print_data(const char* prefix, uint8_t* buffer, size_t size) { + string_t str; + string_init(str); + string_reserve(str, 100 + size * 5); + + string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); + for(int i = 0; i < size; ++i) { + string_cat_printf(str, "%d, ", buffer[i]); + } + string_cat_printf(str, "}\r\n"); + + printf("%s", string_get_cstr(str)); + string_reset(str); + string_reserve(str, 100 + size * 3); + + string_cat_printf(str, "%s HEX(%d): {", prefix, size); + for(int i = 0; i < size; ++i) { + string_cat_printf(str, "%02X", buffer[i]); + } + string_cat_printf(str, "}\r\n\r\n"); + + printf("%s", string_get_cstr(str)); + string_clear(str); +} + void rpc_print_message(const PB_Main* message) { string_t str; string_init(str); @@ -120,10 +162,13 @@ void rpc_print_message(const PB_Main* message) { /* not implemented yet */ string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content); break; - case PB_Main_app_start_tag: { + case PB_Main_stop_session_tag: + string_cat_printf(str, "\tstop_session {\r\n"); + break; + case PB_Main_app_start_request_tag: { string_cat_printf(str, "\tapp_start {\r\n"); - const char* name = message->content.app_start.name; - const char* args = message->content.app_start.args; + const char* name = message->content.app_start_request.name; + const char* args = message->content.app_start_request.args; if(name) { string_cat_printf(str, "\t\tname: %s\r\n", name); } @@ -218,7 +263,30 @@ void rpc_print_message(const PB_Main* message) { size_t msg_file_count = message->content.storage_list_response.file_count; string_cat_printf(str, "\tlist_response {\r\n"); rpc_sprintf_msg_file(str, "\t\t", msg_file, msg_file_count); + break; } + case PB_Main_gui_start_screen_stream_request_tag: + string_cat_printf(str, "\tstart_screen_stream {\r\n"); + break; + case PB_Main_gui_stop_screen_stream_request_tag: + string_cat_printf(str, "\tstop_screen_stream {\r\n"); + break; + case PB_Main_gui_screen_frame_tag: + string_cat_printf(str, "\tscreen_frame {\r\n"); + break; + case PB_Main_gui_send_input_event_request_tag: + string_cat_printf(str, "\tsend_input_event {\r\n"); + string_cat_printf( + str, "\t\tkey: %d\r\n", message->content.gui_send_input_event_request.key); + string_cat_printf( + str, "\t\type: %d\r\n", message->content.gui_send_input_event_request.type); + break; + case PB_Main_gui_start_virtual_display_request_tag: + string_cat_printf(str, "\tstart_virtual_display {\r\n"); + break; + case PB_Main_gui_stop_virtual_display_request_tag: + string_cat_printf(str, "\tstop_virtual_display {\r\n"); + break; } string_cat_printf(str, "\t}\r\n}\r\n"); printf("%s", string_get_cstr(str)); @@ -231,7 +299,7 @@ static Rpc* rpc_alloc(void) { rpc->busy_mutex = osMutexNew(NULL); rpc->busy = false; rpc->events = osEventFlagsNew(NULL); - rpc->stream = xStreamBufferCreate(256, 1); + rpc->stream = xStreamBufferCreate(RPC_BUFFER_SIZE, 1); rpc->decoded_message = furi_alloc(sizeof(PB_Main)); rpc->decoded_message->cb_content.funcs.decode = content_callback; @@ -242,7 +310,7 @@ static Rpc* rpc_alloc(void) { return rpc; } -RpcSession* rpc_open_session(Rpc* rpc) { +RpcSession* rpc_session_open(Rpc* rpc) { furi_assert(rpc); bool result = false; furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK); @@ -256,41 +324,107 @@ RpcSession* rpc_open_session(Rpc* rpc) { if(result) { RpcSession* session = &rpc->session; - session->send_bytes_mutex = osMutexNew(NULL); + session->callbacks_mutex = osMutexNew(NULL); session->rpc = rpc; - session->terminate_session = false; + session->terminate = false; + xStreamBufferReset(rpc->stream); + session->system_contexts = furi_alloc(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); } - FURI_LOG_D(RPC_TAG, "Session started\r\n"); + + 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\r\n"); } return result ? &rpc->session : NULL; /* support 1 open session for now */ } -void rpc_close_session(RpcSession* session) { +void rpc_session_close(RpcSession* session) { furi_assert(session); furi_assert(session->rpc); furi_assert(session->rpc->busy); - rpc_set_send_bytes_callback(session, NULL, NULL); + rpc_session_set_send_bytes_callback(session, NULL); + rpc_session_set_close_callback(session, NULL); osEventFlagsSet(session->rpc->events, RPC_EVENT_DISCONNECT); } -void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context) { +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; +} + +void rpc_session_set_context(RpcSession* session, void* context) { furi_assert(session); furi_assert(session->rpc); furi_assert(session->rpc->busy); - osMutexAcquire(session->send_bytes_mutex, osWaitForever); - session->send_bytes_callback = callback; - session->send_bytes_context = context; - osMutexRelease(session->send_bytes_mutex); + osMutexAcquire(session->callbacks_mutex, osWaitForever); + session->context = context; + osMutexRelease(session->callbacks_mutex); } +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; + osMutexRelease(session->callbacks_mutex); +} + +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; + osMutexRelease(session->callbacks_mutex); +} + +void rpc_session_set_buffer_is_empty_callback( + RpcSession* session, + RpcBufferIsEmptyCallback callback) { + furi_assert(session); + furi_assert(callback); + furi_assert(session->rpc->busy); + + osMutexAcquire(session->callbacks_mutex, osWaitForever); + session->buffer_is_empty_callback = callback; + osMutexRelease(session->callbacks_mutex); +} + +/* Doesn't forbid using rpc_feed_bytes() after session close - it's safe. + * Because any bytes received in buffer will be flushed before next session. + * If bytes get into stream buffer before it's get epmtified and this + * command is gets processed - it's safe either. But case of it is quite + * odd: client sends close request and sends command after. + */ size_t - rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { + 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); @@ -301,21 +435,36 @@ size_t return bytes_sent; } +size_t rpc_session_get_available_size(RpcSession* session) { + furi_assert(session); + Rpc* rpc = session->rpc; + return xStreamBufferSpacesAvailable(rpc->stream); +} + bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { Rpc* rpc = istream->state; 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); + } + } 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_session = true; + rpc->session.terminate = true; + istream->bytes_left = 0; + bytes_received = 0; break; } else { /* Save disconnect flag and continue reading buffer */ @@ -325,61 +474,44 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { } } +#if SRV_RPC_DEBUG + rpc_print_data("INPUT", buf, bytes_received); +#endif + return (count == bytes_received); } -void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message) { +void rpc_send_and_release(Rpc* rpc, PB_Main* message) { furi_assert(rpc); - furi_assert(main_message); + furi_assert(message); RpcSession* session = &rpc->session; pb_ostream_t ostream = PB_OSTREAM_SIZING; -#if DEBUG_PRINT - FURI_LOG_I(RPC_TAG, "OUTPUT:"); - rpc_print_message(main_message); +#if SRV_RPC_DEBUG + FURI_LOG_I(TAG, "OUTPUT:"); + rpc_print_message(message); #endif - bool result = pb_encode_ex(&ostream, &PB_Main_msg, main_message, PB_ENCODE_DELIMITED); + bool result = pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); furi_check(result && ostream.bytes_written); uint8_t* buffer = furi_alloc(ostream.bytes_written); ostream = pb_ostream_from_buffer(buffer, ostream.bytes_written); - pb_encode_ex(&ostream, &PB_Main_msg, main_message, PB_ENCODE_DELIMITED); + pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); - { -#if DEBUG_PRINT - string_t str; - string_init(str); - string_reserve(str, 100 + ostream.bytes_written * 5); +#if SRV_RPC_DEBUG + rpc_print_data("OUTPUT", buffer, ostream.bytes_written); +#endif - string_cat_printf(str, "\r\nREPONSE DEC(%d): {", ostream.bytes_written); - for(int i = 0; i < ostream.bytes_written; ++i) { - string_cat_printf(str, "%d, ", buffer[i]); - } - string_cat_printf(str, "}\r\n"); - - printf("%s", string_get_cstr(str)); - string_clean(str); - string_reserve(str, 100 + ostream.bytes_written * 3); - - string_cat_printf(str, "REPONSE HEX(%d): {", ostream.bytes_written); - for(int i = 0; i < ostream.bytes_written; ++i) { - string_cat_printf(str, "%02X", buffer[i]); - } - string_cat_printf(str, "}\r\n\r\n"); - - printf("%s", string_get_cstr(str)); -#endif // DEBUG_PRINT - - osMutexAcquire(session->send_bytes_mutex, osWaitForever); - if(session->send_bytes_callback) { - session->send_bytes_callback( - session->send_bytes_context, buffer, ostream.bytes_written); - } - osMutexRelease(session->send_bytes_mutex); + osMutexAcquire(session->callbacks_mutex, osWaitForever); + if(session->send_bytes_callback) { + session->send_bytes_callback(session->context, buffer, ostream.bytes_written); } + osMutexRelease(session->callbacks_mutex); + free(buffer); + pb_release(&PB_Main_msg, message); } static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) { @@ -399,17 +531,22 @@ 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 = 0x7FFFFFFF, + .bytes_left = RPC_MAX_MESSAGE_SIZE, /* max incoming message size */ }; if(pb_decode_ex(&istream, &PB_Main_msg, rpc->decoded_message, PB_DECODE_DELIMITED)) { -#if DEBUG_PRINT - FURI_LOG_I(RPC_TAG, "INPUT:"); +#if SRV_RPC_DEBUG + FURI_LOG_I(TAG, "INPUT:"); rpc_print_message(rpc->decoded_message); #endif RpcHandler* handler = @@ -417,35 +554,24 @@ int32_t rpc_srv(void* p) { if(handler && handler->message_handler) { handler->message_handler(rpc->decoded_message, handler->context); - } else if(!handler) { - FURI_LOG_E( - RPC_TAG, - "Unhandled message, tag: %d\r\n", - rpc->decoded_message->which_content); + } else if(!handler && !rpc->session.terminate) { + FURI_LOG_E(TAG, "Unhandled message, tag: %d", rpc->decoded_message->which_content); } - pb_release(&PB_Main_msg, rpc->decoded_message); } else { - pb_release(&PB_Main_msg, rpc->decoded_message); - RpcSession* session = &rpc->session; - if(session->terminate_session) { - session->terminate_session = false; - osEventFlagsClear(rpc->events, RPC_EVENTS_ALL); - FURI_LOG_D(RPC_TAG, "Session terminated\r\n"); - 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->send_bytes_mutex); - RpcHandlerDict_clean(rpc->handlers); - rpc->busy = false; - } else { - xStreamBufferReset(rpc->stream); - FURI_LOG_E( - RPC_TAG, "Decode failed, error: \'%.128s\'\r\n", PB_GET_ERROR(&istream)); + xStreamBufferReset(rpc->stream); + if(!rpc->session.terminate) { + FURI_LOG_E(TAG, "Decode failed, error: \'%.128s\'", PB_GET_ERROR(&istream)); } } + + pb_release(&PB_Main_msg, rpc->decoded_message); + + if(rpc->session.terminate) { + FURI_LOG_D(TAG, "Session terminated"); + osEventFlagsClear(rpc->events, RPC_EVENTS_ALL); + rpc_free_session(&rpc->session); + rpc->busy = false; + } } return 0; } @@ -456,13 +582,13 @@ void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler) { RpcHandlerDict_set_at(rpc->handlers, message_tag, *handler); } -void rpc_encode_and_send_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status) { +void rpc_send_and_release_empty(Rpc* rpc, 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_encode_and_send(rpc, &message); + rpc_send_and_release(rpc, &message); pb_release(&PB_Main_msg, &message); } diff --git a/applications/rpc/rpc.h b/applications/rpc/rpc.h old mode 100644 new mode 100755 index 91557606..61e315ba --- a/applications/rpc/rpc.h +++ b/applications/rpc/rpc.h @@ -1,16 +1,101 @@ #pragma once #include #include +#include #include "cmsis_os.h" +#define RPC_BUFFER_SIZE (1024) +#define RPC_MAX_MESSAGE_SIZE (1536) + +/** Rpc interface. Used for opening session only. */ typedef struct Rpc Rpc; +/** Rpc session interface */ typedef struct RpcSession RpcSession; +/** Callback to send to client any data (e.g. response to command) */ typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes_len); +/** Callback to notify client that buffer is empty */ +typedef void (*RpcBufferIsEmptyCallback)(void* context); +/** Callback to notify transport layer that close_session command + * is received. Any other actions lays on transport layer. + * No destruction or session close preformed. */ +typedef void (*RpcSessionClosedCallback)(void* context); -RpcSession* rpc_open_session(Rpc* rpc); -void rpc_close_session(RpcSession* session); -/* WARN: can't call RPC API within RpcSendBytesCallback */ -void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context); -size_t - rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout); +/** Open RPC session + * + * USAGE: + * 1) rpc_session_open(); + * 2) rpc_session_set_context(); + * 3) rpc_session_set_send_bytes_callback(); + * 4) rpc_session_set_close_callback(); + * 5) while(1) { + * rpc_session_feed(); + * } + * 6) rpc_session_close(); + * + * + * @param rpc instance + * @return pointer to RpcSession descriptor, or + * NULL if RPC is busy and can't open session now + */ +RpcSession* rpc_session_open(Rpc* rpc); + +/** Close RPC session + * It is guaranteed that no callbacks will be called + * as soon as session is closed. So no need in setting + * callbacks to NULL after session close. + * + * @param session pointer to RpcSession descriptor + */ +void rpc_session_close(RpcSession* session); + +/** Set session context for callbacks to pass + * + * @param session pointer to RpcSession descriptor + * @param context context to pass to callbacks + */ +void rpc_session_set_context(RpcSession* session, void* context); + +/** Set callback to send bytes to client + * WARN: It's forbidden to call RPC API within RpcSendBytesCallback + * + * @param session pointer to RpcSession descriptor + * @param callback callback to send bytes to client (can be NULL) + */ +void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback); + +/** Set callback to notify that buffer is empty + * + * @param session pointer to RpcSession descriptor + * @param callback callback to notify client that buffer is empty (can be NULL) + */ +void rpc_session_set_buffer_is_empty_callback( + RpcSession* session, + RpcBufferIsEmptyCallback callback); + +/** Set callback to be called when RPC command to close session is received + * WARN: It's forbidden to call RPC API within RpcSessionClosedCallback + * + * @param session pointer to RpcSession descriptor + * @param callback callback to inform about RPC close session command (can be NULL) + */ +void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback); + +/** Give bytes to RPC service to decode them and perform command + * + * @param session pointer to RpcSession descriptor + * @param buffer buffer to provide to RPC service + * @param size size of buffer + * @param timeout max timeout to wait till all buffer will be consumed + * + * @return actually consumed bytes + */ +size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout); + +/** Get available size of RPC buffer + * + * @param session pointer to RpcSession descriptor + * + * @return bytes available in buffer + */ +size_t rpc_session_get_available_size(RpcSession* session); diff --git a/applications/rpc/rpc_app.c b/applications/rpc/rpc_app.c index db4e5567..79ddee72 100644 --- a/applications/rpc/rpc_app.c +++ b/applications/rpc/rpc_app.c @@ -9,13 +9,13 @@ 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_tag); + furi_assert(request->which_content == PB_Main_app_start_request_tag); PB_CommandStatus result = PB_CommandStatus_ERROR_APP_CANT_START; Loader* loader = furi_record_open("loader"); - const char* app_name = request->content.app_start.name; + const char* app_name = request->content.app_start_request.name; if(app_name) { - const char* app_args = request->content.app_start.args; + const char* app_args = request->content.app_start_request.args; LoaderStatus status = loader_start(loader, app_name, app_args); if(status == LoaderStatusErrorAppStarted) { result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; @@ -34,7 +34,7 @@ void rpc_system_app_start_process(const PB_Main* request, void* context) { furi_record_close("loader"); - rpc_encode_and_send_empty(rpc, request->command_id, result); + rpc_send_and_release_empty(rpc, request->command_id, result); } void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { @@ -56,7 +56,8 @@ void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { furi_record_close("loader"); - rpc_encode_and_send(rpc, &response); + rpc_send_and_release(rpc, &response); + pb_release(&PB_Main_msg, &response); } void* rpc_system_app_alloc(Rpc* rpc) { @@ -69,7 +70,7 @@ void* rpc_system_app_alloc(Rpc* rpc) { }; rpc_handler.message_handler = rpc_system_app_start_process; - rpc_add_handler(rpc, PB_Main_app_start_tag, &rpc_handler); + rpc_add_handler(rpc, 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); diff --git a/applications/rpc/rpc_cli.c b/applications/rpc/rpc_cli.c new file mode 100644 index 00000000..373217b9 --- /dev/null +++ b/applications/rpc/rpc_cli.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +typedef struct { + Cli* cli; + bool session_close_request; +} CliRpc; + +#define CLI_READ_BUFFER_SIZE 64 + +static void rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { + furi_assert(context); + furi_assert(bytes); + furi_assert(bytes_len); + CliRpc* cli_rpc = context; + + cli_write(cli_rpc->cli, bytes, bytes_len); +} + +static void rpc_session_close_callback(void* context) { + furi_assert(context); + CliRpc* cli_rpc = context; + + cli_rpc->session_close_request = true; +} + +void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { + Rpc* rpc = context; + + RpcSession* rpc_session = rpc_session_open(rpc); + if(rpc_session == NULL) { + printf("Another session is in progress\r\n"); + return; + } + + CliRpc cli_rpc = {.cli = cli, .session_close_request = false}; + rpc_session_set_context(rpc_session, &cli_rpc); + rpc_session_set_send_bytes_callback(rpc_session, rpc_send_bytes_callback); + rpc_session_set_close_callback(rpc_session, rpc_session_close_callback); + + uint8_t* buffer = furi_alloc(CLI_READ_BUFFER_SIZE); + size_t size_received = 0; + + while(1) { + size_received = furi_hal_vcp_rx_with_timeout(buffer, CLI_READ_BUFFER_SIZE, 50); + if(!furi_hal_vcp_is_connected() || cli_rpc.session_close_request) { + break; + } + + if(size_received) { + furi_assert( + rpc_session_feed(rpc_session, buffer, size_received, 3000) == size_received); + } + } + + rpc_session_close(rpc_session); + free(buffer); +} diff --git a/applications/rpc/rpc_gui.c b/applications/rpc/rpc_gui.c new file mode 100644 index 00000000..6e9cfb08 --- /dev/null +++ b/applications/rpc/rpc_gui.c @@ -0,0 +1,261 @@ +#include "flipper.pb.h" +#include "rpc_i.h" +#include "gui.pb.h" +#include + +#define TAG "RpcGui" + +typedef struct { + Rpc* rpc; + Gui* gui; + ViewPort* virtual_display_view_port; + uint8_t* virtual_display_buffer; + bool virtual_display_not_empty; +} RpcGuiSystem; + +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 = context; + + PB_Main* frame = furi_alloc(sizeof(PB_Main)); + + frame->which_content = PB_Main_gui_screen_frame_tag; + frame->command_status = PB_CommandStatus_OK; + frame->content.gui_screen_frame.data = furi_alloc(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(rpc_gui->rpc, frame); + + free(frame); +} + +void rpc_system_gui_start_screen_stream_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + RpcGuiSystem* rpc_gui = context; + + rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); + + gui_set_framebuffer_callback( + rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); +} + +void rpc_system_gui_stop_screen_stream_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + RpcGuiSystem* rpc_gui = context; + + gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + + rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK); +} + +void rpc_system_gui_send_input_event_request_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_gui_send_input_event_request_tag); + furi_assert(context); + RpcGuiSystem* rpc_gui = context; + + InputEvent event; + + bool invalid = false; + + switch(request->content.gui_send_input_event_request.key) { + case PB_Gui_InputKey_UP: + event.key = InputKeyUp; + break; + case PB_Gui_InputKey_DOWN: + event.key = InputKeyDown; + break; + case PB_Gui_InputKey_RIGHT: + event.key = InputKeyRight; + break; + case PB_Gui_InputKey_LEFT: + event.key = InputKeyLeft; + break; + case PB_Gui_InputKey_OK: + event.key = InputKeyOk; + break; + case PB_Gui_InputKey_BACK: + event.key = InputKeyBack; + break; + default: + // Invalid key + invalid = true; + break; + } + + switch(request->content.gui_send_input_event_request.type) { + case PB_Gui_InputType_PRESS: + event.type = InputTypePress; + break; + case PB_Gui_InputType_RELEASE: + event.type = InputTypeRelease; + break; + case PB_Gui_InputType_SHORT: + event.type = InputTypeShort; + break; + case PB_Gui_InputType_LONG: + event.type = InputTypeLong; + break; + case PB_Gui_InputType_REPEAT: + event.type = InputTypeRepeat; + break; + default: + // Invalid type + invalid = true; + break; + } + + if(invalid) { + rpc_send_and_release_empty( + rpc_gui->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + return; + } + + FuriPubSub* input_events = furi_record_open("input_events"); + 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); +} + +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) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "Virtual Display"); + canvas_draw_str_aligned(canvas, 64, 36, AlignCenter, AlignCenter, "Waiting for frames..."); + return; + } + + canvas_draw_xbm(canvas, 0, 0, canvas->width, canvas->height, rpc_gui->virtual_display_buffer); +} + +void rpc_system_gui_start_virtual_display_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + RpcGuiSystem* rpc_gui = context; + + 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); + return; + } + + // TODO: consider refactoring + // Using display framebuffer size as an XBM buffer size is like comparing apples and oranges + // Glad they both are 1024 for now + size_t buffer_size = canvas_get_buffer_size(rpc_gui->gui->canvas); + rpc_gui->virtual_display_buffer = furi_alloc(buffer_size); + rpc_gui->virtual_display_view_port = view_port_alloc(); + view_port_draw_callback_set( + rpc_gui->virtual_display_view_port, + rpc_system_gui_virtual_display_render_callback, + 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); +} + +void rpc_system_gui_stop_virtual_display_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + RpcGuiSystem* rpc_gui = context; + + 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); + return; + } + + gui_remove_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port); + view_port_free(rpc_gui->virtual_display_view_port); + free(rpc_gui->virtual_display_buffer); + 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); +} + +void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + RpcGuiSystem* rpc_gui = context; + + if(!rpc_gui->virtual_display_view_port) { + FURI_LOG_W(TAG, "Virtual display is not started, ignoring incoming frame packet"); + return; + } + + size_t buffer_size = canvas_get_buffer_size(rpc_gui->gui->canvas); + memcpy( + rpc_gui->virtual_display_buffer, + request->content.gui_screen_frame.data->bytes, + buffer_size); + rpc_gui->virtual_display_not_empty = true; + view_port_update(rpc_gui->virtual_display_view_port); +} + +void* rpc_system_gui_alloc(Rpc* rpc) { + furi_assert(rpc); + + RpcGuiSystem* rpc_gui = furi_alloc(sizeof(RpcGuiSystem)); + rpc_gui->gui = furi_record_open("gui"); + rpc_gui->rpc = rpc; + + RpcHandler rpc_handler = { + .message_handler = NULL, + .decode_submessage = NULL, + .context = rpc_gui, + }; + + 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_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_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_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_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_handler.message_handler = rpc_system_gui_virtual_display_frame_process; + rpc_add_handler(rpc, 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; + furi_assert(rpc_gui->gui); + + if(rpc_gui->virtual_display_view_port) { + gui_remove_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port); + view_port_free(rpc_gui->virtual_display_view_port); + free(rpc_gui->virtual_display_buffer); + rpc_gui->virtual_display_view_port = NULL; + rpc_gui->virtual_display_not_empty = false; + } + + gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL); + furi_record_close("gui"); + free(rpc_gui); +} \ No newline at end of file diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index 5bcb9d9b..76df1e5a 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -1,9 +1,11 @@ #pragma once #include "rpc.h" -#include "pb.h" -#include "pb_decode.h" -#include "pb_encode.h" -#include "flipper.pb.h" +#include "storage/filesystem-api-defines.h" +#include +#include +#include +#include +#include typedef void* (*RpcSystemAlloc)(Rpc*); typedef void (*RpcSystemFree)(void*); @@ -15,13 +17,18 @@ typedef struct { void* context; } RpcHandler; -void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message); -void rpc_encode_and_send_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status); +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_system_status_alloc(Rpc* rpc); void* rpc_system_storage_alloc(Rpc* rpc); void rpc_system_storage_free(void* ctx); void* rpc_system_app_alloc(Rpc* rpc); +void* rpc_system_gui_alloc(Rpc* rpc); +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 diff --git a/applications/rpc/rpc_status.c b/applications/rpc/rpc_status.c index 524675d9..e8f4e273 100644 --- a/applications/rpc/rpc_status.c +++ b/applications/rpc/rpc_status.c @@ -9,7 +9,8 @@ void rpc_system_status_ping_process(const PB_Main* msg_request, void* context) { msg_response.command_id = msg_request->command_id; msg_response.which_content = PB_Main_ping_response_tag; - rpc_encode_and_send(context, &msg_response); + rpc_send_and_release(context, &msg_response); + pb_release(&PB_Main_msg, &msg_response); } void* rpc_system_status_alloc(Rpc* rpc) { diff --git a/applications/rpc/rpc_storage.c b/applications/rpc/rpc_storage.c index 96842cba..99844e5d 100644 --- a/applications/rpc/rpc_storage.c +++ b/applications/rpc/rpc_storage.c @@ -35,7 +35,7 @@ static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool s if(rpc_storage->state != RpcStorageStateIdle) { if(send_error) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED); @@ -51,7 +51,7 @@ static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool s } } -static PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error) { +PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error) { PB_CommandStatus pb_error; switch(fs_error) { case FSE_OK: @@ -96,6 +96,65 @@ static PB_CommandStatus rpc_system_storage_get_file_error(File* file) { return rpc_system_storage_get_error(storage_file_get_error(file)); } +static void rpc_system_storage_stat_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_storage_stat_request_tag); + + RpcStorageSystem* rpc_storage = context; + rpc_system_storage_reset_state(rpc_storage, true); + + PB_Main* response = furi_alloc(sizeof(PB_Main)); + response->command_id = request->command_id; + + Storage* fs_api = furi_record_open("storage"); + + const char* path = request->content.storage_stat_request.path; + FileInfo fileinfo; + FS_Error error = storage_common_stat(fs_api, path, &fileinfo); + + response->command_status = rpc_system_storage_get_error(error); + response->which_content = PB_Main_empty_tag; + + if(error == FSE_OK) { + response->which_content = PB_Main_storage_stat_response_tag; + response->content.storage_stat_response.has_file = true; + response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ? + PB_Storage_File_FileType_DIR : + PB_Storage_File_FileType_FILE; + response->content.storage_stat_response.file.size = fileinfo.size; + } + + rpc_send_and_release(rpc_storage->rpc, response); + free(response); + furi_record_close("storage"); +} + +static void rpc_system_storage_list_root(const PB_Main* request, void* context) { + RpcStorageSystem* rpc_storage = context; + const char* hard_coded_dirs[] = {"any", "int", "ext"}; + + PB_Main response = { + .has_next = false, + .command_id = request->command_id, + .command_status = PB_CommandStatus_OK, + .which_content = PB_Main_storage_list_response_tag, + }; + furi_assert(COUNT_OF(hard_coded_dirs) < COUNT_OF(response.content.storage_list_response.file)); + + for(int i = 0; i < COUNT_OF(hard_coded_dirs); ++i) { + ++response.content.storage_list_response.file_count; + response.content.storage_list_response.file[i].data = NULL; + response.content.storage_list_response.file[i].size = 0; + response.content.storage_list_response.file[i].type = PB_Storage_File_FileType_DIR; + char* str = furi_alloc(strlen(hard_coded_dirs[i]) + 1); + strcpy(str, hard_coded_dirs[i]); + response.content.storage_list_response.file[i].name = str; + } + + rpc_send_and_release(rpc_storage->rpc, &response); +} + static void rpc_system_storage_list_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -104,17 +163,21 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex RpcStorageSystem* rpc_storage = context; rpc_system_storage_reset_state(rpc_storage, true); + if(!strcmp(request->content.storage_list_request.path, "/")) { + rpc_system_storage_list_root(request, context); + return; + } + Storage* fs_api = furi_record_open("storage"); File* dir = storage_file_alloc(fs_api); PB_Main response = { .command_id = request->command_id, .has_next = false, - .which_content = PB_Main_storage_list_request_tag, + .which_content = PB_Main_storage_list_response_tag, .command_status = PB_CommandStatus_OK, }; PB_Storage_ListResponse* list = &response.content.storage_list_response; - response.which_content = PB_Main_storage_list_response_tag; bool finish = false; int i = 0; @@ -132,8 +195,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_encode_and_send(rpc_storage->rpc, &response); - pb_release(&PB_Main_msg, &response); + rpc_send_and_release(rpc_storage->rpc, &response); i = 0; } list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? PB_Storage_File_FileType_DIR : @@ -150,8 +212,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex } response.has_next = false; - rpc_encode_and_send(rpc_storage->rpc, &response); - pb_release(&PB_Main_msg, &response); + rpc_send_and_release(rpc_storage->rpc, &response); storage_dir_close(dir); storage_file_free(dir); @@ -168,9 +229,6 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex /* use same message memory to send reponse */ PB_Main* response = furi_alloc(sizeof(PB_Main)); - response->command_id = request->command_id; - response->which_content = PB_Main_storage_read_response_tag; - response->command_status = PB_CommandStatus_OK; const char* path = request->content.storage_read_request.path; Storage* fs_api = furi_record_open("storage"); File* file = storage_file_alloc(fs_api); @@ -178,10 +236,13 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { size_t size_left = storage_file_size(file); - response->content.storage_read_response.has_file = true; - response->content.storage_read_response.file.data = - furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE))); do { + response->command_id = request->command_id; + response->which_content = PB_Main_storage_read_response_tag; + response->command_status = PB_CommandStatus_OK; + response->content.storage_read_response.has_file = true; + response->content.storage_read_response.file.data = + furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE))); uint8_t* buffer = response->content.storage_read_response.file.data->bytes; uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size; @@ -192,21 +253,19 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex if(result) { response->has_next = (size_left > 0); - rpc_encode_and_send(rpc_storage->rpc, response); - // no pb_release(...); + rpc_send_and_release(rpc_storage->rpc, response); } } while((size_left != 0) && result); if(!result) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); } } else { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); } - pb_release(&PB_Main_msg, response); free(response); storage_file_close(file); storage_file_free(file); @@ -245,14 +304,14 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte result = (written_size == buffer_size); if(result && !request->has_next) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_OK); rpc_system_storage_reset_state(rpc_storage, false); } } if(!result) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, rpc_storage->current_command_id, rpc_system_storage_get_file_error(file)); @@ -260,23 +319,57 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte } } +static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) { + FileInfo fileinfo; + bool is_dir_is_empty = false; + FS_Error error = storage_common_stat(fs_api, path, &fileinfo); + if((error == FSE_OK) && (fileinfo.flags & FSF_DIRECTORY)) { + File* dir = storage_file_alloc(fs_api); + if(storage_dir_open(dir, path)) { + char* name = furi_alloc(MAX_NAME_LENGTH); + is_dir_is_empty = !storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH); + free(name); + } + storage_dir_close(dir); + storage_file_free(dir); + } + + return is_dir_is_empty; +} + static void rpc_system_storage_delete_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_storage_delete_request_tag); furi_assert(context); RpcStorageSystem* rpc_storage = context; - PB_CommandStatus status; + PB_CommandStatus status = PB_CommandStatus_ERROR; rpc_system_storage_reset_state(rpc_storage, true); Storage* fs_api = furi_record_open("storage"); - char* path = request->content.storage_mkdir_request.path; - if(path) { - FS_Error error = storage_common_remove(fs_api, path); - status = rpc_system_storage_get_error(error); - } else { + + char* path = request->content.storage_delete_request.path; + if(!path) { status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; + } else { + FS_Error error_remove = storage_common_remove(fs_api, path); + // FSE_DENIED is for empty directory, but not only for this + // that's why we have to check it + if((error_remove == FSE_DENIED) && !rpc_system_storage_is_dir_is_empty(fs_api, path)) { + if(request->content.storage_delete_request.recursive) { + bool deleted = storage_simply_remove_recursive(fs_api, path); + status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR; + } else { + status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY; + } + } else if(error_remove == FSE_NOT_EXIST) { + status = PB_CommandStatus_OK; + } else { + status = rpc_system_storage_get_error(error_remove); + } } - rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status); + + furi_record_close("storage"); + rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); } static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) { @@ -295,7 +388,7 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte } else { status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; } - rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status); + rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); } static void rpc_system_storage_md5sum_process(const PB_Main* request, void* context) { @@ -307,7 +400,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont const char* filename = request->content.storage_md5sum_request.path; if(!filename) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } @@ -349,9 +442,9 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont free(hash); free(data); storage_file_close(file); - rpc_encode_and_send(rpc_storage->rpc, &response); + rpc_send_and_release(rpc_storage->rpc, &response); } else { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); } @@ -374,6 +467,9 @@ void* rpc_system_storage_alloc(Rpc* rpc) { .context = rpc_storage, }; + rpc_handler.message_handler = rpc_system_storage_stat_process; + rpc_add_handler(rpc, 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); diff --git a/applications/storage-settings/scenes/storage-settings-benchmark.c b/applications/storage-settings/scenes/storage-settings-benchmark.c index bc83e707..454e3a39 100644 --- a/applications/storage-settings/scenes/storage-settings-benchmark.c +++ b/applications/storage-settings/scenes/storage-settings-benchmark.c @@ -157,5 +157,5 @@ void storage_settings_scene_benchmark_on_exit(void* context) { dialog_ex_set_result_callback(dialog_ex, NULL); dialog_ex_set_context(dialog_ex, NULL); - string_clean(app->text_string); + string_reset(app->text_string); } diff --git a/applications/storage-settings/scenes/storage-settings-scene-internal-info.c b/applications/storage-settings/scenes/storage-settings-scene-internal-info.c index 389cad0c..8a831610 100644 --- a/applications/storage-settings/scenes/storage-settings-scene-internal-info.c +++ b/applications/storage-settings/scenes/storage-settings-scene-internal-info.c @@ -64,5 +64,5 @@ void storage_settings_scene_internal_info_on_exit(void* context) { dialog_ex_set_result_callback(dialog_ex, NULL); dialog_ex_set_context(dialog_ex, NULL); - string_clean(app->text_string); + string_reset(app->text_string); } diff --git a/applications/storage-settings/scenes/storage-settings-scene-sd-info.c b/applications/storage-settings/scenes/storage-settings-scene-sd-info.c index 617b2281..c82d2f14 100644 --- a/applications/storage-settings/scenes/storage-settings-scene-sd-info.c +++ b/applications/storage-settings/scenes/storage-settings-scene-sd-info.c @@ -70,5 +70,5 @@ void storage_settings_scene_sd_info_on_exit(void* context) { dialog_ex_set_result_callback(dialog_ex, NULL); dialog_ex_set_context(dialog_ex, NULL); - string_clean(app->text_string); + string_reset(app->text_string); } diff --git a/applications/storage/storage-external-api.c b/applications/storage/storage-external-api.c index aa11161e..ae798ecb 100644 --- a/applications/storage/storage-external-api.c +++ b/applications/storage/storage-external-api.c @@ -1,7 +1,11 @@ +#include +#include #include "storage.h" #include "storage-i.h" #include "storage-message.h" +#define MAX_NAME_LENGTH 256 + #define S_API_PROLOGUE \ osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \ furi_check(semaphore != NULL); @@ -320,6 +324,11 @@ FS_Error storage_file_get_error(File* file) { return file->error_id; } +int32_t storage_file_get_internal_error(File* file) { + furi_check(file != NULL); + return file->internal_error_id; +} + const char* storage_file_get_error_desc(File* file) { furi_check(file != NULL); return filesystem_api_error_get_desc(file->error_id); @@ -382,6 +391,67 @@ void storage_file_free(File* file) { free(file); } +bool storage_simply_remove_recursive(Storage* storage, const char* path) { + furi_assert(storage); + furi_assert(path); + FileInfo fileinfo; + bool result = false; + string_t fullname; + string_t cur_dir; + + if(storage_simply_remove(storage, path)) { + return true; + } + + char* name = furi_alloc(MAX_NAME_LENGTH + 1); + File* dir = storage_file_alloc(storage); + string_init_set_str(cur_dir, path); + bool go_deeper = false; + + while(1) { + if(!storage_dir_open(dir, string_get_cstr(cur_dir))) { + storage_dir_close(dir); + break; + } + + while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { + if(fileinfo.flags & FSF_DIRECTORY) { + string_cat_printf(cur_dir, "/%s", name); + go_deeper = true; + break; + } + + string_init_printf(fullname, "%s/%s", string_get_cstr(cur_dir), name); + FS_Error error = storage_common_remove(storage, string_get_cstr(fullname)); + furi_assert(error == FSE_OK); + string_clear(fullname); + } + storage_dir_close(dir); + + if(go_deeper) { + go_deeper = false; + continue; + } + + FS_Error error = storage_common_remove(storage, string_get_cstr(cur_dir)); + furi_assert(error == FSE_OK); + + if(string_cmp(cur_dir, path)) { + size_t last_char = string_search_rchar(cur_dir, '/'); + furi_assert(last_char != STRING_FAILURE); + string_left(cur_dir, last_char); + } else { + result = true; + break; + } + } + + storage_file_free(dir); + string_clear(cur_dir); + free(name); + return result; +} + bool storage_simply_remove(Storage* storage, const char* path) { FS_Error result; result = storage_common_remove(storage, path); @@ -392,4 +462,29 @@ bool storage_simply_mkdir(Storage* storage, const char* path) { FS_Error result; result = storage_common_mkdir(storage, path); return result == FSE_OK || result == FSE_EXIST; -} \ No newline at end of file +} + +void storage_get_next_filename( + Storage* storage, + const char* dirname, + const char* filename, + const char* fileextension, + string_t nextfilename) { + string_t temp_str; + uint16_t num = 0; + + string_init_printf(temp_str, "%s/%s%s", dirname, filename, fileextension); + + while(storage_common_stat(storage, string_get_cstr(temp_str), NULL) == FSE_OK) { + num++; + string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); + } + + if(num) { + string_printf(nextfilename, "%s%d", filename, num); + } else { + string_printf(nextfilename, "%s", filename); + } + + string_clear(temp_str); +} diff --git a/applications/storage/storage-glue.h b/applications/storage/storage-glue.h index 775742cc..3a6cbe5c 100644 --- a/applications/storage/storage-glue.h +++ b/applications/storage/storage-glue.h @@ -1,8 +1,10 @@ #pragma once + #include #include "filesystem-api-internal.h" #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/applications/storage/storage-test-app.c b/applications/storage/storage-test-app.c index 022181a5..1c4e1915 100644 --- a/applications/storage/storage-test-app.c +++ b/applications/storage/storage-test-app.c @@ -2,7 +2,7 @@ #include #include -#define TAG "storage-test" +#define TAG "StorageTest" #define BYTES_COUNT 16 #define TEST_STRING "TestDataStringProvidedByDiceRoll" #define SEEK_OFFSET_FROM_START 10 diff --git a/applications/storage/storage.h b/applications/storage/storage.h index d38153bf..fc7ac665 100644 --- a/applications/storage/storage.h +++ b/applications/storage/storage.h @@ -197,6 +197,12 @@ const char* storage_error_get_desc(FS_Error error_id); */ FS_Error storage_file_get_error(File* file); +/** Retrieves the internal (storage-specific) error id from the file object + * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE INTERNAL ERROR ID IF THE FILE HAS BEEN CLOSED + * @return FS_Error error id + */ +int32_t storage_file_get_internal_error(File* file); + /** Retrieves the error text from the file object * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED * @return const char* error text @@ -240,6 +246,14 @@ FS_Error storage_sd_status(Storage* api); */ bool storage_simply_remove(Storage* storage, const char* path); +/** + * Removes a file/directory from the repository, the directory can be not empty + * @param storage pointer to the api + * @param path + * @return true on success or if file/dir is not exist + */ +bool storage_simply_remove_recursive(Storage* storage, const char* path); + /** * Creates a directory * @param storage @@ -248,6 +262,22 @@ bool storage_simply_remove(Storage* storage, const char* path); */ bool storage_simply_mkdir(Storage* storage, const char* path); +/** + * @brief Get next free filename. + * + * @param storage + * @param dirname + * @param filename + * @param fileextension + * @param nextfilename return name + */ +void storage_get_next_filename( + Storage* storage, + const char* dirname, + const char* filename, + const char* fileextension, + string_t nextfilename); + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/applications/storage/storages/storage-ext.c b/applications/storage/storages/storage-ext.c index a434c2d8..51de7612 100644 --- a/applications/storage/storages/storage-ext.c +++ b/applications/storage/storages/storage-ext.c @@ -10,7 +10,7 @@ typedef DIR SDDir; typedef FILINFO SDFileInfo; typedef FRESULT SDError; -#define TAG "storage-ext" +#define TAG "StorageExt" #define STORAGE_PATH "/ext" /********************* Definitions ********************/ diff --git a/applications/storage/storages/storage-int.c b/applications/storage/storages/storage-int.c index 87c02786..69ffe15e 100644 --- a/applications/storage/storages/storage-int.c +++ b/applications/storage/storages/storage-int.c @@ -2,7 +2,7 @@ #include #include -#define TAG "storage-int" +#define TAG "StorageInt" #define STORAGE_PATH "/int" typedef struct { @@ -119,9 +119,9 @@ static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t bloc LFSData* lfs_data = c->context; size_t page = lfs_data->start_page + block; - FURI_LOG_D(TAG, "Device erase: page %d, translated page: %d", block, page); + FURI_LOG_D(TAG, "Device erase: page %d, translated page: %x", block, page); - if(furi_hal_flash_erase(page, 1)) { + if(furi_hal_flash_erase(page)) { return 0; } else { return -1; @@ -163,15 +163,15 @@ static LFSData* storage_int_lfs_data_alloc() { static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) { int err; - FuriHalBootFlag boot_flags = furi_hal_boot_get_flags(); + FuriHalBootloaderFlag bootloader_flags = furi_hal_bootloader_get_flags(); lfs_t* lfs = &lfs_data->lfs; - if(boot_flags & FuriHalBootFlagFactoryReset) { + if(bootloader_flags & FuriHalBootloaderFlagFactoryReset) { // Factory reset err = lfs_format(lfs, &lfs_data->config); if(err == 0) { FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount"); - furi_hal_boot_set_flags(boot_flags & ~FuriHalBootFlagFactoryReset); + furi_hal_bootloader_set_flags(bootloader_flags & ~FuriHalBootloaderFlagFactoryReset); err = lfs_mount(lfs, &lfs_data->config); if(err == 0) { FURI_LOG_I(TAG, "Factory reset: Mounted"); diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h new file mode 100644 index 00000000..5a423e6f --- /dev/null +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -0,0 +1,34 @@ +#pragma once + +typedef enum { + SubghzCustomEventManagerNoSet = 0, + SubghzCustomEventManagerSet, + + SubghzCustomEventSceneDeleteSuccess = 100, + SubghzCustomEventSceneDelete, + SubghzCustomEventSceneReceiverInfoTxStart, + SubghzCustomEventSceneReceiverInfoTxStop, + SubghzCustomEventSceneReceiverInfoSave, + SubghzCustomEventSceneSaveName, + SubghzCustomEventSceneSaveSuccess, + SubghzCustomEventSceneShowError, + SubghzCustomEventSceneShowOnlyRX, + + SubghzCustomEventSceneNeedSavingNo, + SubghzCustomEventSceneNeedSavingYes, + + SubghzCustomEventViewReceverOK, + SubghzCustomEventViewReceverConfig, + SubghzCustomEventViewReceverBack, + + SubghzCustomEventViewReadRAWBack, + SubghzCustomEventViewReadRAWIDLE, + SubghzCustomEventViewReadRAWREC, + SubghzCustomEventViewReadRAWConfig, + SubghzCustomEventViewReadRAWMore, + + SubghzCustomEventViewTransmitterBack, + SubghzCustomEventViewTransmitterSendStart, + SubghzCustomEventViewTransmitterSendStop, + SubghzCustomEventViewTransmitterError, +} SubghzCustomEvent; \ No newline at end of file diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index 5c7199b6..ddf557a6 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 = furi_alloc(sizeof(SubGhzFrequencyAnalyzerWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "subghz_frequency_analyzer_worker"); + 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_config.h b/applications/subghz/scenes/subghz_scene_config.h index eca3cc35..6d89cb74 100644 --- a/applications/subghz/scenes/subghz_scene_config.h +++ b/applications/subghz/scenes/subghz_scene_config.h @@ -16,4 +16,7 @@ ADD_SCENE(subghz, test_static, TestStatic) ADD_SCENE(subghz, test_carrier, TestCarrier) ADD_SCENE(subghz, test_packet, TestPacket) ADD_SCENE(subghz, set_type, SetType) -ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer) \ No newline at end of file +ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer) +ADD_SCENE(subghz, read_raw, ReadRAW) +ADD_SCENE(subghz, read_raw_menu, ReadRAWMenu) +ADD_SCENE(subghz, need_saving, NeedSaving) \ No newline at end of file diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/subghz/scenes/subghz_scene_delete.c index 94782e88..eb8a2217 100644 --- a/applications/subghz/scenes/subghz_scene_delete.c +++ b/applications/subghz/scenes/subghz_scene_delete.c @@ -1,47 +1,43 @@ #include "../subghz_i.h" - -typedef enum { - SubGhzSceneDeleteInfoCustomEventDelete, -} SubGhzSceneDeleteInfoCustomEvent; +#include "../helpers/subghz_custom_event.h" void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); SubGhz* subghz = context; if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzSceneDeleteInfoCustomEventDelete); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneDelete); } } void subghz_scene_delete_on_enter(void* context) { SubGhz* subghz = context; - - char buffer_str[16]; - snprintf( - buffer_str, - sizeof(buffer_str), - "%03ld.%02ld", - subghz->txrx->frequency / 1000000 % 1000, - subghz->txrx->frequency / 10000 % 100); - widget_add_string_element( - subghz->widget, 78, 0, AlignLeft, AlignTop, FontSecondary, buffer_str); - if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || - subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - snprintf(buffer_str, sizeof(buffer_str), "AM"); - } else if( - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - snprintf(buffer_str, sizeof(buffer_str), "FM"); - } else { - furi_crash(NULL); - } - widget_add_string_element( - subghz->widget, 113, 0, AlignLeft, AlignTop, FontSecondary, buffer_str); + string_t frequency_str; + string_t modulation_str; string_t text; + + string_init(frequency_str); + string_init(modulation_str); string_init(text); + + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + widget_add_string_element( + subghz->widget, 78, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(frequency_str)); + + widget_add_string_element( + subghz->widget, + 113, + 0, + AlignLeft, + AlignTop, + FontSecondary, + string_get_cstr(modulation_str)); + subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, text); widget_add_string_multiline_element( subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); + + string_clear(frequency_str); + string_clear(modulation_str); string_clear(text); widget_add_button_element( @@ -53,8 +49,8 @@ void subghz_scene_delete_on_enter(void* context) { bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzSceneDeleteInfoCustomEventDelete) { - memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name)); + if(event.event == SubghzCustomEventSceneDelete) { + memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name) + 1); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_delete_success.c b/applications/subghz/scenes/subghz_scene_delete_success.c index bfafb7e5..04b48531 100644 --- a/applications/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/subghz/scenes/subghz_scene_delete_success.c @@ -1,10 +1,10 @@ #include "../subghz_i.h" - -#define SCENE_DELETE_SUCCESS_CUSTOM_EVENT (0UL) +#include "../helpers/subghz_custom_event.h" void subghz_scene_delete_success_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_DELETE_SUCCESS_CUSTOM_EVENT); + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubghzCustomEventSceneDeleteSuccess); } void subghz_scene_delete_success_on_enter(void* context) { @@ -25,7 +25,7 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SCENE_DELETE_SUCCESS_CUSTOM_EVENT) { + if(event.event == SubghzCustomEventSceneDeleteSuccess) { return scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); } diff --git a/applications/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/subghz/scenes/subghz_scene_frequency_analyzer.c index b67494f0..548a0f8f 100644 --- a/applications/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -1,7 +1,7 @@ #include "../subghz_i.h" #include "../views/subghz_frequency_analyzer.h" -void subghz_scene_frequency_analyzer_callback(SubghzFrequencyAnalyzerEvent 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); @@ -15,13 +15,7 @@ void subghz_scene_frequency_analyzer_on_enter(void* context) { } bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzFrequencyAnalyzerEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } + //SubGhz* subghz = context; return false; } diff --git a/applications/subghz/scenes/subghz_scene_need_saving.c b/applications/subghz/scenes/subghz_scene_need_saving.c new file mode 100644 index 00000000..c7f83ce0 --- /dev/null +++ b/applications/subghz/scenes/subghz_scene_need_saving.c @@ -0,0 +1,63 @@ +#include "../subghz_i.h" +#include "../helpers/subghz_custom_event.h" + +void subghz_scene_need_saving_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + SubGhz* subghz = context; + + if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubghzCustomEventSceneNeedSavingYes); + } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubghzCustomEventSceneNeedSavingNo); + } +} + +void subghz_scene_need_saving_on_enter(void* context) { + SubGhz* subghz = context; + + widget_add_string_multiline_element( + subghz->widget, + 64, + 25, + AlignCenter, + AlignCenter, + FontSecondary, + "There is an unsaved data.\nDo you want to save it?"); + + widget_add_button_element( + subghz->widget, GuiButtonTypeRight, "Save", subghz_scene_need_saving_callback, subghz); + widget_add_button_element( + subghz->widget, GuiButtonTypeLeft, "Delete", subghz_scene_need_saving_callback, subghz); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); +} + +bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubghzCustomEventSceneNeedSavingYes) { + subghz->txrx->rx_key_state = SubGhzRxKeyStateNeedSave; + scene_manager_previous_scene(subghz->scene_manager); + return true; + } else if(event.event == SubghzCustomEventSceneNeedSavingNo) { + if(subghz->txrx->rx_key_state == SubGhzRxKeyStateExit) { + subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart); + } else { + subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + scene_manager_previous_scene(subghz->scene_manager); + } + + return true; + } + } + return false; +} + +void subghz_scene_need_saving_on_exit(void* context) { + SubGhz* subghz = context; + widget_clear(subghz->widget); +} diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c new file mode 100644 index 00000000..7bf5576e --- /dev/null +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -0,0 +1,183 @@ +#include "../subghz_i.h" +#include "../views/subghz_read_raw.h" +#include +#include + +static void subghz_scene_read_raw_update_statusbar(void* context) { + furi_assert(context); + SubGhz* subghz = context; + + string_t frequency_str; + string_t modulation_str; + + string_init(frequency_str); + string_init(modulation_str); + + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_read_raw_add_data_statusbar( + subghz->subghz_read_raw, string_get_cstr(frequency_str), string_get_cstr(modulation_str)); + + string_clear(frequency_str); + string_clear(modulation_str); +} + +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); +} + +void subghz_scene_read_raw_on_enter(void* context) { + SubGhz* subghz = context; + + if(subghz->txrx->rx_key_state == SubGhzRxKeyStateNeedSave) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubghzCustomEventViewReadRAWMore); + } else { + subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + } + + subghz_scene_read_raw_update_statusbar(subghz); + 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_worker_set_pair_callback( + subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_raw_parse); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewReadRAW); +} + +bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SubghzCustomEventViewReadRAWBack: + if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { + subghz_rx_end(subghz); + subghz_sleep(subghz); + }; + subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; + subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; + subghz_protocol_raw_save_to_file_stop( + (SubGhzProtocolRAW*)subghz->txrx->protocol_result); + subghz->state_notifications = SubGhzNotificationStateIDLE; + + if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) { + subghz->txrx->rx_key_state = SubGhzRxKeyStateExit; + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); + } else { + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart); + } + + return true; + break; + case SubghzCustomEventViewReadRAWConfig: + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerSet); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); + return true; + break; + 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); + subghz->state_notifications = SubGhzNotificationStateIDLE; + + subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; + + return true; + break; + 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); + if(subghz_protocol_raw_save_to_file_init( + (SubGhzProtocolRAW*)subghz->txrx->protocol_result, + "Raw_temp", + subghz->txrx->frequency, + string_get_cstr(subghz->error_str))) { + if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || + (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { + subghz_begin(subghz, subghz->txrx->preset); + subghz_rx(subghz, subghz->txrx->frequency); + } + subghz->state_notifications = SubGhzNotificationStateRX; + } else { + string_set(subghz->error_str, "No SD card"); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + } + + return true; + break; + case SubghzCustomEventViewReadRAWMore: + if(strcmp( + subghz_protocol_raw_get_last_file_name( + (SubGhzProtocolRAW*)subghz->txrx->protocol_result), + "")) { + strlcpy( + subghz->file_name, + subghz_protocol_raw_get_last_file_name( + (SubGhzProtocolRAW*)subghz->txrx->protocol_result), + strlen(subghz_protocol_raw_get_last_file_name( + (SubGhzProtocolRAW*)subghz->txrx->protocol_result)) + + 1); + //set the path to read the file + string_t temp_str; + string_init_printf( + temp_str, + "%s/%s%s", + SUBGHZ_APP_PATH_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); + + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAWMenu); + } + return true; + break; + + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + switch(subghz->state_notifications) { + case SubGhzNotificationStateRX: + notification_message(subghz->notifications, &sequence_blink_blue_10); + subghz_read_raw_update_sample_write( + subghz->subghz_read_raw, + subghz_protocol_raw_get_sample_write( + (SubGhzProtocolRAW*)subghz->txrx->protocol_result)); + subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi()); + break; + default: + break; + } + } + return false; +} + +void subghz_scene_read_raw_on_exit(void* context) { + SubGhz* subghz = context; + + //Stop CC1101 + if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { + subghz_rx_end(subghz); + subghz_sleep(subghz); + }; + subghz->state_notifications = SubGhzNotificationStateIDLE; + + //Сallback restoration + subghz_worker_set_pair_callback( + subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_parse); +} diff --git a/applications/subghz/scenes/subghz_scene_read_raw_menu.c b/applications/subghz/scenes/subghz_scene_read_raw_menu.c new file mode 100644 index 00000000..2c6deefc --- /dev/null +++ b/applications/subghz/scenes/subghz_scene_read_raw_menu.c @@ -0,0 +1,72 @@ +#include "../subghz_i.h" + +enum SubmenuIndex { + SubmenuIndexEmulate, + SubmenuIndexEdit, + SubmenuIndexDelete, +}; + +void subghz_scene_read_raw_menu_submenu_callback(void* context, uint32_t index) { + SubGhz* subghz = context; + view_dispatcher_send_custom_event(subghz->view_dispatcher, index); +} + +void subghz_scene_read_raw_menu_on_enter(void* context) { + SubGhz* subghz = context; + submenu_add_item( + subghz->submenu, + "Emulate", + SubmenuIndexEmulate, + subghz_scene_read_raw_menu_submenu_callback, + subghz); + + submenu_add_item( + subghz->submenu, + "Save", + SubmenuIndexEdit, + subghz_scene_read_raw_menu_submenu_callback, + subghz); + + submenu_add_item( + subghz->submenu, + "Delete", + SubmenuIndexDelete, + subghz_scene_read_raw_menu_submenu_callback, + subghz); + + submenu_set_selected_item( + subghz->submenu, + scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu)); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu); +} + +bool subghz_scene_read_raw_menu_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexEmulate) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAWMenu, SubmenuIndexEmulate); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter); + return true; + } else if(event.event == SubmenuIndexDelete) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAWMenu, SubmenuIndexDelete); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDelete); + return true; + } else if(event.event == SubmenuIndexEdit) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAWMenu, SubghzCustomEventManagerSet); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); + return true; + } + } + return false; +} + +void subghz_scene_read_raw_menu_on_exit(void* context) { + SubGhz* subghz = context; + submenu_clean(subghz->submenu); + subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; +} diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/subghz/scenes/subghz_scene_receiver.c index fe29c0cf..53b72c1a 100644 --- a/applications/subghz/scenes/subghz_scene_receiver.c +++ b/applications/subghz/scenes/subghz_scene_receiver.c @@ -3,38 +3,34 @@ static void subghz_scene_receiver_update_statusbar(void* context) { SubGhz* subghz = context; - char frequency_str[20]; - char preset_str[10]; string_t history_stat_str; string_init(history_stat_str); if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) { - snprintf( - frequency_str, - sizeof(frequency_str), - "%03ld.%02ld", - subghz->txrx->frequency / 1000000 % 1000, - subghz->txrx->frequency / 10000 % 100); - if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || - subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - snprintf(preset_str, sizeof(preset_str), "AM"); - } else if( - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - snprintf(preset_str, sizeof(preset_str), "FM"); - } else { - furi_crash(NULL); - } + string_t frequency_str; + string_t modulation_str; + + string_init(frequency_str); + string_init(modulation_str); + + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_receiver_add_data_statusbar( - subghz->subghz_receiver, frequency_str, preset_str, string_get_cstr(history_stat_str)); + subghz->subghz_receiver, + string_get_cstr(frequency_str), + string_get_cstr(modulation_str), + string_get_cstr(history_stat_str)); + + string_clear(frequency_str); + string_clear(modulation_str); } else { subghz_receiver_add_data_statusbar( subghz->subghz_receiver, string_get_cstr(history_stat_str), "", ""); - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + subghz->state_notifications = SubGhzNotificationStateIDLE; } string_clear(history_stat_str); } -void subghz_scene_receiver_callback(SubghzReceverEvent 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); @@ -49,7 +45,7 @@ void subghz_scene_add_to_history_callback(SubGhzProtocolCommon* parser, void* co if(subghz_history_add_to_history( subghz->txrx->history, parser, subghz->txrx->frequency, subghz->txrx->preset)) { subghz_parser_reset(subghz->txrx->parser); - string_clean(str_buff); + 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( @@ -71,7 +67,7 @@ void subghz_scene_receiver_on_enter(void* context) { //Load history to receiver subghz_receiver_exit(subghz->subghz_receiver); for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { - string_clean(str_buff); + string_reset(str_buff); subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i); subghz_receiver_add_item_to_menu( subghz->subghz_receiver, @@ -83,11 +79,11 @@ void subghz_scene_receiver_on_enter(void* context) { 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->state_notifications = NOTIFICATION_RX_STATE; + subghz->state_notifications = SubGhzNotificationStateRX; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); }; - if((subghz->txrx->txrx_state == SubGhzTxRxStateIdle) || + if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { subghz_begin(subghz, subghz->txrx->preset); subghz_rx(subghz, subghz->txrx->frequency); @@ -102,8 +98,9 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SubghzReceverEventBack: + case SubghzCustomEventViewReceverBack: // Stop CC1101 Rx + subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); subghz_sleep(subghz); @@ -118,13 +115,13 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart); return true; break; - case SubghzReceverEventOK: + case SubghzCustomEventViewReceverOK: subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); return true; break; - case SubghzReceverEventConfig: - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + case SubghzCustomEventViewReceverConfig: + subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); return true; @@ -139,7 +136,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { } switch(subghz->state_notifications) { - case NOTIFICATION_RX_STATE: + case SubGhzNotificationStateRX: notification_message(subghz->notifications, &sequence_blink_blue_10); break; default: diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index 3991a999..4243b09b 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/subghz/scenes/subghz_scene_receiver_config.c @@ -66,6 +66,8 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) { if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) { variable_item_set_current_value_text(item, subghz_frequencies_text[index]); subghz->txrx->frequency = subghz_frequencies[index]; + } else { + variable_item_set_current_value_index(item, subghz_frequencies_433_92); } } @@ -88,22 +90,24 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item) subghz->scene_manager, SubGhzSceneReceiverConfig), subghz_frequencies_text[subghz_frequencies_433_92]); subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + subghz->scene_manager, SubGhzSceneReceiverConfig), + subghz_frequencies_433_92); } else { variable_item_set_current_value_text( (VariableItem*)scene_manager_get_scene_state( subghz->scene_manager, SubGhzSceneReceiverConfig), " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + subghz->scene_manager, SubGhzSceneReceiverConfig), + subghz_frequencies_433_92); } subghz->txrx->hopper_state = hopping_value[index]; } -void subghz_scene_receiver_config_callback(SubghzReceverEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - void subghz_scene_receiver_config_on_enter(void* context) { SubGhz* subghz = context; VariableItem* item; @@ -122,16 +126,19 @@ 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, subghz_frequencies_text[value_index]); - item = variable_item_list_add( - subghz->variable_item_list, - "Hopping:", - HOPPING_COUNT, - subghz_scene_receiver_config_set_hopping_runing, - subghz); - value_index = subghz_scene_receiver_config_hopper_value_index( - subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, hopping_text[value_index]); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubghzCustomEventManagerSet) { + item = variable_item_list_add( + subghz->variable_item_list, + "Hopping:", + HOPPING_COUNT, + subghz_scene_receiver_config_set_hopping_runing, + subghz); + value_index = subghz_scene_receiver_config_hopper_value_index( + subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, hopping_text[value_index]); + } item = variable_item_list_add( subghz->variable_item_list, @@ -155,4 +162,6 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even void subghz_scene_receiver_config_on_exit(void* context) { SubGhz* subghz = context; variable_item_list_clean(subghz->variable_item_list); + scene_manager_set_scene_state( + 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 6b33a686..a8935a1c 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/subghz/scenes/subghz_scene_receiver_info.c @@ -1,10 +1,5 @@ #include "../subghz_i.h" - -typedef enum { - SubGhzSceneReceiverInfoCustomEventTxStart, - SubGhzSceneReceiverInfoCustomEventTxStop, - SubGhzSceneReceiverInfoCustomEventSave, -} SubGhzSceneReceiverInfoCustomEvent; +#include "../helpers/subghz_custom_event.h" void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); @@ -12,13 +7,13 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v if((result == GuiButtonTypeCenter) && (type == InputTypePress)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventTxStart); + subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStart); } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventTxStop); + subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStop); } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventSave); + subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoSave); } } @@ -45,35 +40,42 @@ void subghz_scene_receiver_info_on_enter(void* context) { SubGhz* subghz = context; if(subghz_scene_receiver_info_update_parser(subghz)) { - char buffer_str[16]; - snprintf( - buffer_str, - sizeof(buffer_str), - "%03ld.%02ld", - subghz->txrx->frequency / 1000000 % 1000, - subghz->txrx->frequency / 10000 % 100); - widget_add_string_element( - subghz->widget, 78, 0, AlignLeft, AlignTop, FontSecondary, buffer_str); - if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || - subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - snprintf(buffer_str, sizeof(buffer_str), "AM"); - } else if( - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - snprintf(buffer_str, sizeof(buffer_str), "FM"); - } else { - furi_crash(NULL); - } - widget_add_string_element( - subghz->widget, 113, 0, AlignLeft, AlignTop, FontSecondary, buffer_str); + string_t frequency_str; + string_t modulation_str; string_t text; + + string_init(frequency_str); + string_init(modulation_str); string_init(text); + + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + widget_add_string_element( + subghz->widget, + 78, + 0, + AlignLeft, + AlignTop, + FontSecondary, + string_get_cstr(frequency_str)); + + widget_add_string_element( + subghz->widget, + 113, + 0, + AlignLeft, + AlignTop, + FontSecondary, + string_get_cstr(modulation_str)); + subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, text); widget_add_string_multiline_element( subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); + + string_clear(frequency_str); + string_clear(modulation_str); string_clear(text); - if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_string && + if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_file && strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) { widget_add_button_element( subghz->widget, @@ -101,7 +103,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzSceneReceiverInfoCustomEventTxStart) { + if(event.event == SubghzCustomEventSceneReceiverInfoTxStart) { //CC1101 Stop RX -> Start TX if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { subghz->txrx->hopper_state = SubGhzHopperStatePause; @@ -112,32 +114,33 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) if(!subghz_scene_receiver_info_update_parser(subghz)) { return false; } - if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { + if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE || + subghz->txrx->txrx_state == SubGhzTxRxStateSleep) { if(!subghz_tx_start(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); } else { - subghz->state_notifications = NOTIFICATION_TX_STATE; + subghz->state_notifications = SubGhzNotificationStateTX; } } return true; - } else if(event.event == SubGhzSceneReceiverInfoCustomEventTxStop) { + } else if(event.event == SubghzCustomEventSceneReceiverInfoTxStop) { //CC1101 Stop Tx -> Start RX - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { subghz_tx_stop(subghz); } - if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { + if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { subghz_begin(subghz, subghz->txrx->preset); subghz_rx(subghz, subghz->txrx->frequency); } if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { subghz->txrx->hopper_state = SubGhzHopperStateRunnig; } - subghz->state_notifications = NOTIFICATION_RX_STATE; + subghz->state_notifications = SubGhzNotificationStateRX; return true; - } else if(event.event == SubGhzSceneReceiverInfoCustomEventSave) { + } else if(event.event == SubghzCustomEventSceneReceiverInfoSave) { //CC1101 Stop RX -> Save - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { subghz->txrx->hopper_state = SubGhzHopperStateOFF; } @@ -148,7 +151,7 @@ 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_string && + if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_file && strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) { subghz_file_name_clear(subghz); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); @@ -160,10 +163,10 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_hopper_update(subghz); } switch(subghz->state_notifications) { - case NOTIFICATION_TX_STATE: + case SubGhzNotificationStateTX: notification_message(subghz->notifications, &sequence_blink_red_10); break; - case NOTIFICATION_RX_STATE: + case SubGhzNotificationStateRX: notification_message(subghz->notifications, &sequence_blink_blue_10); break; default: diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/subghz/scenes/subghz_scene_save_name.c index ee21238a..d448119c 100644 --- a/applications/subghz/scenes/subghz_scene_save_name.c +++ b/applications/subghz/scenes/subghz_scene_save_name.c @@ -1,12 +1,11 @@ #include "../subghz_i.h" #include #include "file-worker.h" - -#define SCENE_SAVE_NAME_CUSTOM_EVENT (0UL) +#include "../helpers/subghz_custom_event.h" void subghz_scene_save_name_text_input_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_SAVE_NAME_CUSTOM_EVENT); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneSaveName); } void subghz_scene_save_name_on_enter(void* context) { @@ -20,7 +19,11 @@ void subghz_scene_save_name_on_enter(void* context) { set_random_name(subghz->file_name, sizeof(subghz->file_name)); dev_name_empty = true; } else { - memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name)); + memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name) + 1); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAWMenu) == + SubghzCustomEventManagerSet) { + subghz_get_next_name_file(subghz); + } } text_input_set_header_text(text_input, "Name signal"); @@ -38,12 +41,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SCENE_SAVE_NAME_CUSTOM_EVENT) { - if(strcmp(subghz->file_name, "") && - subghz_save_protocol_to_file(subghz, subghz->file_name)) { + if(event.event == SubghzCustomEventSceneSaveName) { + if(strcmp(subghz->file_name, "")) { if(strcmp(subghz->file_name_tmp, "")) { - subghz_delete_file(subghz); + subghz_rename_file(subghz); + } else { + subghz_save_protocol_to_file(subghz, subghz->file_name); } + subghz_file_name_clear(subghz); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); return true; @@ -62,4 +67,6 @@ void subghz_scene_save_name_on_exit(void* context) { // Clear view text_input_clean(subghz->text_input); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAWMenu, SubghzCustomEventManagerNoSet); } diff --git a/applications/subghz/scenes/subghz_scene_save_success.c b/applications/subghz/scenes/subghz_scene_save_success.c index dc267486..31e7d3ee 100644 --- a/applications/subghz/scenes/subghz_scene_save_success.c +++ b/applications/subghz/scenes/subghz_scene_save_success.c @@ -1,10 +1,9 @@ #include "../subghz_i.h" - -#define SCENE_SAVE_SUCCESS_CUSTOM_EVENT (0UL) +#include "../helpers/subghz_custom_event.h" void subghz_scene_save_success_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_SAVE_SUCCESS_CUSTOM_EVENT); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneSaveSuccess); } void subghz_scene_save_success_on_enter(void* context) { @@ -24,7 +23,7 @@ void subghz_scene_save_success_on_enter(void* context) { bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SCENE_SAVE_SUCCESS_CUSTOM_EVENT) { + if(event.event == SubghzCustomEventSceneSaveSuccess) { if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneReceiver)) { scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/subghz/scenes/subghz_scene_saved_menu.c b/applications/subghz/scenes/subghz_scene_saved_menu.c index 59d7e3e8..a25f8e6f 100644 --- a/applications/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/subghz/scenes/subghz_scene_saved_menu.c @@ -19,12 +19,14 @@ void subghz_scene_saved_menu_on_enter(void* context) { SubmenuIndexEmulate, subghz_scene_saved_menu_submenu_callback, subghz); + submenu_add_item( subghz->submenu, "Edit name", SubmenuIndexEdit, subghz_scene_saved_menu_submenu_callback, subghz); + submenu_add_item( subghz->submenu, "Delete", diff --git a/applications/subghz/scenes/subghz_scene_show_error.c b/applications/subghz/scenes/subghz_scene_show_error.c index 2599f24e..d8f2e050 100644 --- a/applications/subghz/scenes/subghz_scene_show_error.c +++ b/applications/subghz/scenes/subghz_scene_show_error.c @@ -1,10 +1,9 @@ #include "../subghz_i.h" - -#define SCENE_NO_MAN_CUSTOM_EVENT (11UL) +#include "../helpers/subghz_custom_event.h" void subghz_scene_show_error_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_NO_MAN_CUSTOM_EVENT); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneShowError); } void subghz_scene_show_error_on_enter(void* context) { @@ -24,7 +23,7 @@ void subghz_scene_show_error_on_enter(void* context) { bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) { + if(event.event == SubghzCustomEventSceneShowError) { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); return true; @@ -45,5 +44,5 @@ void subghz_scene_show_error_on_exit(void* context) { popup_set_context(popup, NULL); popup_set_timeout(popup, 0); popup_disable_timeout(popup); - string_clean(subghz->error_str); + string_reset(subghz->error_str); } diff --git a/applications/subghz/scenes/subghz_scene_show_only_rx.c b/applications/subghz/scenes/subghz_scene_show_only_rx.c index e59e1d68..a62969d4 100644 --- a/applications/subghz/scenes/subghz_scene_show_only_rx.c +++ b/applications/subghz/scenes/subghz_scene_show_only_rx.c @@ -1,10 +1,9 @@ #include "../subghz_i.h" - -#define SCENE_NO_MAN_CUSTOM_EVENT (11UL) +#include "../helpers/subghz_custom_event.h" void subghz_scene_show_only_rx_popup_callback(void* context) { SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_NO_MAN_CUSTOM_EVENT); + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneShowOnlyRX); } void subghz_scene_show_only_rx_on_enter(void* context) { @@ -30,7 +29,7 @@ void subghz_scene_show_only_rx_on_enter(void* context) { const bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) { + 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 c131a0a8..3b5b5cd1 100644 --- a/applications/subghz/scenes/subghz_scene_start.c +++ b/applications/subghz/scenes/subghz_scene_start.c @@ -1,11 +1,12 @@ #include "../subghz_i.h" enum SubmenuIndex { - SubmenuIndexRead, + SubmenuIndexRead = 10, SubmenuIndexSaved, SubmenuIndexTest, SubmenuIndexAddManualy, SubmenuIndexFrequencyAnalyzer, + SubmenuIndexReadRAW, }; void subghz_scene_start_submenu_callback(void* context, uint32_t index) { @@ -15,11 +16,17 @@ void subghz_scene_start_submenu_callback(void* context, uint32_t index) { void subghz_scene_start_on_enter(void* context) { SubGhz* subghz = context; - if(subghz->state_notifications == NOTIFICATION_STARTING_STATE) { - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + if(subghz->state_notifications == SubGhzNotificationStateStarting) { + subghz->state_notifications = SubGhzNotificationStateIDLE; } submenu_add_item( subghz->submenu, "Read", SubmenuIndexRead, subghz_scene_start_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Read Raw", + SubmenuIndexReadRAW, + subghz_scene_start_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "Saved", SubmenuIndexSaved, subghz_scene_start_submenu_callback, subghz); submenu_add_item( @@ -47,7 +54,12 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexRead) { + if(event.event == SubmenuIndexReadRAW) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); + return true; + } else if(event.event == SubmenuIndexRead) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRead); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver); diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/subghz/scenes/subghz_scene_transmitter.c index 4aa5f87f..16617589 100644 --- a/applications/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/subghz/scenes/subghz_scene_transmitter.c @@ -2,20 +2,23 @@ #include "../views/subghz_transmitter.h" #include -void subghz_scene_transmitter_callback(SubghzTransmitterEvent 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); } -static void subghz_scene_transmitter_update_data_show(void* context) { +bool subghz_scene_transmitter_update_data_show(void* context) { SubGhz* subghz = context; if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->get_upload_protocol) { string_t key_str; + string_t frequency_str; + string_t modulation_str; + string_init(key_str); - char frequency_str[10]; - char preset_str[6]; + 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); @@ -27,78 +30,74 @@ static void subghz_scene_transmitter_update_data_show(void* context) { } else { show_button = 1; } - snprintf( - frequency_str, - sizeof(frequency_str), - "%03ld.%02ld", - subghz->txrx->frequency / 1000000 % 1000, - subghz->txrx->frequency / 10000 % 100); - if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || - subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - snprintf(preset_str, sizeof(preset_str), "AM"); - } else if( - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || - subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - snprintf(preset_str, sizeof(preset_str), "FM"); - } else { - furi_crash(NULL); - } + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); subghz_transmitter_add_data_to_show( subghz->subghz_transmitter, string_get_cstr(key_str), - frequency_str, - preset_str, + string_get_cstr(frequency_str), + string_get_cstr(modulation_str), show_button); + + string_clear(frequency_str); + string_clear(modulation_str); string_clear(key_str); - } else { - string_set(subghz->error_str, "Protocol not found"); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + + return true; } + return false; } void subghz_scene_transmitter_on_enter(void* context) { SubGhz* subghz = context; + if(!subghz_scene_transmitter_update_data_show(subghz)) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubghzCustomEventViewTransmitterError); + } + subghz_transmitter_set_callback( subghz->subghz_transmitter, subghz_scene_transmitter_callback, subghz); - subghz_scene_transmitter_update_data_show(subghz); - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + + subghz->state_notifications = SubGhzNotificationStateIDLE; view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter); } bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubghzTransmitterEventSendStart) { - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + if(event.event == SubghzCustomEventViewTransmitterSendStart) { + subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); } - if((subghz->txrx->txrx_state == SubGhzTxRxStateIdle) || + if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { if(!subghz_tx_start(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); } else { - subghz->state_notifications = NOTIFICATION_TX_STATE; + subghz->state_notifications = SubGhzNotificationStateTX; subghz_scene_transmitter_update_data_show(subghz); } } return true; - } else if(event.event == SubghzTransmitterEventSendStop) { - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + } 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 == SubghzTransmitterEventBack) { - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + } 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) { + string_set(subghz->error_str, "Protocol not found"); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } } else if(event.type == SceneManagerEventTypeTick) { - if(subghz->state_notifications == NOTIFICATION_TX_STATE) { + if(subghz->state_notifications == SubGhzNotificationStateTX) { notification_message(subghz->notifications, &sequence_blink_red_10); } return true; @@ -109,5 +108,5 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { void subghz_scene_transmitter_on_exit(void* context) { SubGhz* subghz = context; - subghz->state_notifications = NOTIFICATION_IDLE_STATE; + subghz->state_notifications = SubGhzNotificationStateIDLE; } diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index 7c71e25f..5ae24b7b 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -1,4 +1,5 @@ #include "subghz_i.h" +#include const char* const subghz_frequencies_text[] = { "300.00", @@ -119,6 +120,9 @@ SubGhz* subghz_alloc() { view_dispatcher_add_view( subghz->view_dispatcher, SubGhzViewWidget, widget_get_view(subghz->widget)); + //Dialog + subghz->dialogs = furi_record_open("dialogs"); + // Transmitter subghz->subghz_transmitter = subghz_transmitter_alloc(); view_dispatcher_add_view( @@ -140,6 +144,13 @@ SubGhz* subghz_alloc() { SubGhzViewFrequencyAnalyzer, 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, + subghz_read_raw_get_view(subghz->subghz_read_raw)); + // Carrier Test Module subghz->subghz_test_carrier = subghz_test_carrier_alloc(); view_dispatcher_add_view( @@ -167,6 +178,7 @@ SubGhz* subghz_alloc() { subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; subghz->txrx->txrx_state = SubGhzTxRxStateSleep; subghz->txrx->hopper_state = SubGhzHopperStateOFF; + subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; subghz->txrx->history = subghz_history_alloc(); subghz->txrx->worker = subghz_worker_alloc(); subghz->txrx->parser = subghz_parser_alloc(); @@ -180,7 +192,9 @@ SubGhz* subghz_alloc() { string_init(subghz->error_str); subghz_parser_load_keeloq_file(subghz->txrx->parser, "/ext/subghz/keeloq_mfcodes"); - subghz_parser_load_nice_flor_s_file(subghz->txrx->parser, "/ext/subghz/nice_floor_s_rx"); + subghz_parser_load_keeloq_file(subghz->txrx->parser, "/ext/subghz/keeloq_mfcodes_user"); + subghz_parser_load_nice_flor_s_file(subghz->txrx->parser, "/ext/subghz/nice_flor_s_rx"); + subghz_parser_load_came_atomo_file(subghz->txrx->parser, "/ext/subghz/came_atomo"); //subghz_parser_enable_dump_text(subghz->protocol, subghz_text_callback, subghz); @@ -214,6 +228,9 @@ void subghz_free(SubGhz* subghz) { view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewWidget); widget_free(subghz->widget); + //Dialog + furi_record_close("dialogs"); + // Transmitter view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTransmitter); subghz_transmitter_free(subghz->subghz_transmitter); @@ -226,6 +243,10 @@ void subghz_free(SubGhz* subghz) { view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer); subghz_frequency_analyzer_free(subghz->subghz_frequency_analyzer); + // Read RAW + view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewReadRAW); + subghz_read_raw_free(subghz->subghz_read_raw); + // Submenu view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu); submenu_free(subghz->submenu); @@ -266,6 +287,14 @@ int32_t subghz_app(void* p) { // Check argument and run corresponding scene if(p && subghz_key_load(subghz, p)) { + string_t filename; + string_init(filename); + + path_extract_filename_no_ext(p, filename); + strlcpy( + subghz->file_name, string_get_cstr(filename), strlen(string_get_cstr(filename)) + 1); + string_clear(filename); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter); } else { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index 94c2cd03..62d070ef 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -3,26 +3,16 @@ #include #include #include + +#include #include +#include #include #include #define SUBGHZ_FREQUENCY_RANGE_STR \ "299999755...348000000 or 386999938...464000000 or 778999847...928000000" -void subghz_cli_init() { - Cli* cli = furi_record_open("cli"); - - cli_add_command( - cli, "subghz_tx_carrier", CliCommandFlagDefault, subghz_cli_command_tx_carrier, NULL); - cli_add_command( - cli, "subghz_rx_carrier", CliCommandFlagDefault, subghz_cli_command_rx_carrier, NULL); - cli_add_command(cli, "subghz_tx", CliCommandFlagDefault, subghz_cli_command_tx, NULL); - cli_add_command(cli, "subghz_rx", CliCommandFlagDefault, subghz_cli_command_rx, NULL); - - furi_record_close("cli"); -} - void subghz_cli_command_tx_carrier(Cli* cli, string_t args, void* context) { uint32_t frequency = 433920000; @@ -222,7 +212,9 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { SubGhzParser* parser = subghz_parser_alloc(); subghz_parser_load_keeloq_file(parser, "/ext/subghz/keeloq_mfcodes"); - subghz_parser_load_nice_flor_s_file(parser, "/ext/subghz/nice_floor_s_rx"); + subghz_parser_load_keeloq_file(parser, "/ext/subghz/keeloq_mfcodes_user"); + subghz_parser_load_nice_flor_s_file(parser, "/ext/subghz/nice_flor_s_rx"); + subghz_parser_load_came_atomo_file(parser, "/ext/subghz/came_atomo"); subghz_parser_enable_dump_text(parser, subghz_cli_command_rx_text_callback, instance); // Configure radio @@ -267,3 +259,131 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { vStreamBufferDelete(instance->stream); free(instance); } + +void subghz_cli_command_print_usage() { + printf("Usage:\r\n"); + printf("subghz \r\n"); + printf("Cmd list:\r\n"); + printf( + "\tencrypt_keeloq \t - Encrypt keeloq manufacture keys\r\n"); + printf( + "\tencrypt_raw \t - Encrypt RAW data\r\n"); +} + +void subghz_cli_command_encrypt_keeloq(Cli* cli, string_t args) { + uint8_t iv[16]; + + string_t source; + string_t destination; + string_init(source); + string_init(destination); + + SubGhzKeystore* keystore = subghz_keystore_alloc(); + + do { + if(!args_read_string_and_trim(args, source)) { + subghz_cli_command_print_usage(); + break; + } + + if(!args_read_string_and_trim(args, destination)) { + subghz_cli_command_print_usage(); + break; + } + + if(!args_read_hex_bytes(args, iv, 16)) { + subghz_cli_command_print_usage(); + break; + } + + if(!subghz_keystore_load(keystore, string_get_cstr(source))) { + printf("Failed to load Keystore"); + break; + } + + if(!subghz_keystore_save(keystore, string_get_cstr(destination), iv)) { + printf("Failed to save Keystore"); + break; + } + } while(false); + + subghz_keystore_free(keystore); + string_clear(destination); + string_clear(source); +} + +void subghz_cli_command_encrypt_raw(Cli* cli, string_t args) { + uint8_t iv[16]; + + string_t source; + string_t destination; + string_init(source); + string_init(destination); + + do { + if(!args_read_string_and_trim(args, source)) { + subghz_cli_command_print_usage(); + break; + } + + if(!args_read_string_and_trim(args, destination)) { + subghz_cli_command_print_usage(); + break; + } + + if(!args_read_hex_bytes(args, iv, 16)) { + subghz_cli_command_print_usage(); + break; + } + + if(!subghz_keystore_raw_encrypted_save( + string_get_cstr(source), string_get_cstr(destination), iv)) { + printf("Failed to save Keystore"); + break; + } + + } while(false); + + string_clear(destination); + string_clear(source); +} + +void subghz_cli_command(Cli* cli, string_t args, void* context) { + string_t cmd; + string_init(cmd); + + do { + if(!args_read_string_and_trim(args, cmd)) { + subghz_cli_command_print_usage(); + break; + } + + if(string_cmp_str(cmd, "encrypt_keeloq") == 0) { + subghz_cli_command_encrypt_keeloq(cli, args); + break; + } + + if(string_cmp_str(cmd, "encrypt_raw") == 0) { + subghz_cli_command_encrypt_raw(cli, args); + break; + } + + subghz_cli_command_print_usage(); + } while(false); + + string_clear(cmd); +} + +void subghz_cli_init() { + Cli* cli = furi_record_open("cli"); + + cli_add_command( + cli, "subghz_tx_carrier", CliCommandFlagDefault, subghz_cli_command_tx_carrier, NULL); + cli_add_command( + cli, "subghz_rx_carrier", CliCommandFlagDefault, subghz_cli_command_rx_carrier, NULL); + cli_add_command(cli, "subghz_tx", CliCommandFlagDefault, subghz_cli_command_tx, NULL); + cli_add_command(cli, "subghz_rx", CliCommandFlagDefault, subghz_cli_command_rx, NULL); + cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL); + + furi_record_close("cli"); +} diff --git a/applications/subghz/subghz_cli.h b/applications/subghz/subghz_cli.h index e6dc16a5..c70cbd21 100644 --- a/applications/subghz/subghz_cli.h +++ b/applications/subghz/subghz_cli.h @@ -3,15 +3,3 @@ #include void subghz_cli_init(); - -void subghz_cli_command_tx_carrier(Cli* cli, string_t args, void* context); - -void subghz_cli_command_rx_carrier(Cli* cli, string_t args, void* context); - -void subghz_cli_command_tx_pt(Cli* cli, string_t args, void* context); - -void subghz_cli_command_rx_pt(Cli* cli, string_t args, void* context); - -void subghz_cli_command_tx(Cli* cli, string_t args, void* context); - -void subghz_cli_command_rx(Cli* cli, string_t args, void* context); diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index 2f849e41..ff1596e8 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -6,17 +6,81 @@ #include #include #include -#include "file-worker.h" +#include #include "../notification/notification.h" #include "views/subghz_receiver.h" +bool subghz_set_pteset(SubGhz* subghz, const char* preset) { + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + subghz->txrx->preset = FuriHalSubGhzPresetOok270Async; + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + subghz->txrx->preset = FuriHalSubGhzPreset2FSKDev238Async; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + subghz->txrx->preset = FuriHalSubGhzPreset2FSKDev476Async; + } else { + FURI_LOG_E(SUBGHZ_PARSER_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; + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unknown preset"); + default: + 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) { + string_printf( + frequency, + "%03ld.%02ld", + subghz->txrx->frequency / 1000000 % 1000, + subghz->txrx->frequency / 10000 % 100); + } + + if(modulation != NULL) { + if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || + subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { + string_set(modulation, "AM"); + } else if( + subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || + subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { + string_set(modulation, "FM"); + } else { + furi_crash(NULL); + } + } +} + void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset) { furi_assert(subghz); furi_hal_subghz_reset(); furi_hal_subghz_idle(); furi_hal_subghz_load_preset(preset); hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - subghz->txrx->txrx_state = SubGhzTxRxStateIdle; + subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { @@ -59,7 +123,7 @@ void subghz_idle(SubGhz* subghz) { furi_assert(subghz); furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); furi_hal_subghz_idle(); - subghz->txrx->txrx_state = SubGhzTxRxStateIdle; + subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } void subghz_rx_end(SubGhz* subghz) { @@ -70,7 +134,7 @@ void subghz_rx_end(SubGhz* subghz) { furi_hal_subghz_stop_async_rx(); } furi_hal_subghz_idle(); - subghz->txrx->txrx_state = SubGhzTxRxStateIdle; + subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } void subghz_sleep(SubGhz* subghz) { @@ -79,17 +143,6 @@ void subghz_sleep(SubGhz* subghz) { subghz->txrx->txrx_state = SubGhzTxRxStateSleep; } -static void subghz_frequency_preset_to_str(SubGhz* subghz, string_t output) { - furi_assert(subghz); - - string_cat_printf( - output, - "Frequency: %d\n" - "Preset: %d\n", - (int)subghz->txrx->frequency, - (int)subghz->txrx->preset); -} - bool subghz_tx_start(SubGhz* subghz) { furi_assert(subghz); @@ -144,75 +197,111 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { furi_assert(subghz); furi_assert(file_path); - FileWorker* file_worker = file_worker_alloc(false); + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); + // Load device data bool loaded = false; string_t path; string_init_set_str(path, file_path); string_t temp_str; string_init(temp_str); - int res = 0; - int data = 0; + uint32_t version; do { - if(!file_worker_open(file_worker, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + if(!flipper_file_open_existing(flipper_file, string_get_cstr(path))) { + FURI_LOG_E( + SUBGHZ_PARSER_TAG, "Unable to open file for read: %s", string_get_cstr(path)); + break; + } + if(!flipper_file_read_header(flipper_file, temp_str, &version)) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing or incorrect header"); break; } - // Read and parse frequency from 1st line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { + if(((!strcmp(string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || + (!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"); break; } - res = sscanf(string_get_cstr(temp_str), "Frequency: %d\n", &data); - if(res != 1) { - break; - } - subghz->txrx->frequency = (uint32_t)data; - // Read and parse preset from 2st line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { + if(!flipper_file_read_uint32( + flipper_file, "Frequency", (uint32_t*)&subghz->txrx->frequency, 1)) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Frequency"); break; } - res = sscanf(string_get_cstr(temp_str), "Preset: %d\n", &data); - if(res != 1) { - break; - } - subghz->txrx->preset = (FuriHalSubGhzPreset)data; - // Read and parse name protocol from 2st line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { + if(!flipper_file_read_string(flipper_file, "Preset", temp_str)) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Preset"); break; } - // strlen("Protocol: ") = 10 - string_right(temp_str, 10); + if(!subghz_set_pteset(subghz, string_get_cstr(temp_str))) { + break; + } + + if(!flipper_file_read_string(flipper_file, "Protocol", temp_str)) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing Protocol"); + break; + } + 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( - file_worker, subghz->txrx->protocol_result)) { + flipper_file, subghz->txrx->protocol_result, string_get_cstr(path))) { break; } loaded = true; } while(0); if(!loaded) { - file_worker_show_error(file_worker, "Cannot parse\nfile"); + dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); } string_clear(temp_str); string_clear(path); - file_worker_close(file_worker); - file_worker_free(file_worker); + + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + + furi_record_close("storage"); return loaded; } +bool subghz_get_next_name_file(SubGhz* subghz) { + furi_assert(subghz); + + Storage* storage = furi_record_open("storage"); + string_t temp_str; + string_init(temp_str); + bool res = false; + + if(strcmp(subghz->file_name, "")) { + //get the name of the next free file + storage_get_next_filename( + storage, SUBGHZ_RAW_PATH_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION, temp_str); + + memcpy(subghz->file_name, string_get_cstr(temp_str), strlen(string_get_cstr(temp_str))); + res = true; + } + + string_clear(temp_str); + furi_record_close("storage"); + + return res; +} + bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) { furi_assert(subghz); furi_assert(subghz->txrx->protocol_result); - FileWorker* file_worker = file_worker_alloc(false); + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); string_t dev_file_name; string_init(dev_file_name); string_t temp_str; @@ -220,43 +309,70 @@ bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) { 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; + } // Create subghz folder directory if necessary - if(!file_worker_mkdir(file_worker, SUBGHZ_APP_FOLDER)) { + if(!storage_simply_mkdir(storage, SUBGHZ_APP_FOLDER)) { + dialog_message_show_storage_error(subghz->dialogs, "Cannot create\nfolder"); break; } // Create saved directory if necessary - if(!file_worker_mkdir(file_worker, SUBGHZ_APP_PATH_FOLDER)) { + if(!storage_simply_mkdir(storage, SUBGHZ_APP_FOLDER)) { + dialog_message_show_storage_error(subghz->dialogs, "Cannot create\nfolder"); break; } + // First remove subghz device file if it was saved string_printf( dev_file_name, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); - if(!file_worker_remove(file_worker, string_get_cstr(dev_file_name))) { + + if(!storage_simply_remove(storage, string_get_cstr(dev_file_name))) { break; } + // Open file - if(!file_worker_open( - file_worker, string_get_cstr(dev_file_name), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + if(!flipper_file_open_always(flipper_file, string_get_cstr(dev_file_name))) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to open file for write: %s", dev_file_name); break; } - //Get string frequency preset protocol - subghz_frequency_preset_to_str(subghz, temp_str); - if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_size(temp_str))) { + + if(!flipper_file_write_header_cstr( + flipper_file, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add header"); break; } - //Get string save - subghz->txrx->protocol_result->to_save_string(subghz->txrx->protocol_result, temp_str); - // Prepare and write data to file - if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_size(temp_str))) { + + if(!flipper_file_write_uint32(flipper_file, "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_file_write_string_cstr(flipper_file, "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_file)) { + break; + } + saved = true; } while(0); string_clear(temp_str); string_clear(dev_file_name); - file_worker_close(file_worker); - file_worker_free(file_worker); + + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + + furi_record_close("storage"); return saved; } @@ -264,17 +380,12 @@ bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) { bool subghz_load_protocol_from_file(SubGhz* subghz) { furi_assert(subghz); - FileWorker* file_worker = file_worker_alloc(false); - string_t protocol_file_name; - string_init(protocol_file_name); - string_t temp_str; - string_init(temp_str); - int sscanf_res = 0; - int data = 0; + string_t file_name; + string_init(file_name); // Input events and views are managed by file_select - bool res = file_worker_file_select( - file_worker, + bool res = dialog_file_select_show( + subghz->dialogs, SUBGHZ_APP_PATH_FOLDER, SUBGHZ_APP_EXTENSION, subghz->file_name, @@ -282,103 +393,58 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { NULL); if(res) { - // Get key file path string_printf( - protocol_file_name, - "%s/%s%s", - SUBGHZ_APP_PATH_FOLDER, - subghz->file_name, - SUBGHZ_APP_EXTENSION); - } else { - string_clear(temp_str); - string_clear(protocol_file_name); + file_name, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION); - file_worker_close(file_worker); - file_worker_free(file_worker); - return res; - } - res = false; - do { - if(!file_worker_open( - file_worker, string_get_cstr(protocol_file_name), FSAM_READ, FSOM_OPEN_EXISTING)) { - return res; - } - // Read and parse frequency from 1st line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - sscanf_res = sscanf(string_get_cstr(temp_str), "Frequency: %d\n", &data); - if(sscanf_res != 1) { - break; - } - subghz->txrx->frequency = (uint32_t)data; - - // Read and parse preset from 2st line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - sscanf_res = sscanf(string_get_cstr(temp_str), "Preset: %d\n", &data); - if(sscanf_res != 1) { - break; - } - subghz->txrx->preset = (FuriHalSubGhzPreset)data; - - // Read and parse name protocol from 3st line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - // strlen("Protocol: ") = 10 - string_right(temp_str, 10); - subghz->txrx->protocol_result = - subghz_parser_get_by_name(subghz->txrx->parser, string_get_cstr(temp_str)); - if(subghz->txrx->protocol_result == NULL) { - break; - } - if(!subghz->txrx->protocol_result->to_load_protocol_from_file( - file_worker, subghz->txrx->protocol_result)) { - break; - } - res = true; - } while(0); - - if(!res) { - file_worker_show_error(file_worker, "Cannot parse\nfile"); + res = subghz_key_load(subghz, string_get_cstr(file_name)); } - string_clear(temp_str); - string_clear(protocol_file_name); - - file_worker_close(file_worker); - file_worker_free(file_worker); + string_clear(file_name); return res; } +bool subghz_rename_file(SubGhz* subghz) { + furi_assert(subghz); + bool ret = true; + string_t old_path; + string_t new_path; + + Storage* storage = furi_record_open("storage"); + + string_init_printf( + old_path, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); + + string_init_printf( + new_path, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION); + + FS_Error fs_result = + storage_common_rename(storage, string_get_cstr(old_path), string_get_cstr(new_path)); + + if(fs_result != FSE_OK && fs_result != FSE_EXIST) { + dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); + ret = false; + } + + string_clear(old_path); + string_clear(new_path); + furi_record_close("storage"); + + return ret; +} + bool subghz_delete_file(SubGhz* subghz) { furi_assert(subghz); - bool result = true; - FileWorker* file_worker = file_worker_alloc(false); + Storage* storage = furi_record_open("storage"); string_t file_path; + string_init_printf( + file_path, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); + bool result = storage_simply_remove(storage, string_get_cstr(file_path)); + furi_record_close("storage"); - do { - // Get key file path - string_init_printf( - file_path, - "%s/%s%s", - SUBGHZ_APP_PATH_FOLDER, - subghz->file_name_tmp, - SUBGHZ_APP_EXTENSION); - // Delete original file - if(!file_worker_remove(file_worker, string_get_cstr(file_path))) { - result = false; - break; - } - } while(0); + subghz_file_name_clear(subghz); - string_clear(file_path); - file_worker_close(file_worker); - file_worker_free(file_worker); return result; } @@ -442,7 +508,7 @@ void subghz_hopper_update(SubGhz* subghz) { if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); }; - if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { + if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { subghz_parser_reset(subghz->txrx->parser); 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 88be9203..ef27721d 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -4,6 +4,7 @@ #include "views/subghz_receiver.h" #include "views/subghz_transmitter.h" #include "views/subghz_frequency_analyzer.h" +#include "views/subghz_read_raw.h" #include "views/subghz_test_static.h" #include "views/subghz_test_carrier.h" @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -32,11 +34,6 @@ #define SUBGHZ_TEXT_STORE_SIZE 40 -#define NOTIFICATION_STARTING_STATE 0u -#define NOTIFICATION_IDLE_STATE 1u -#define NOTIFICATION_TX_STATE 2u -#define NOTIFICATION_RX_STATE 3u - extern const char* const subghz_frequencies_text[]; extern const uint32_t subghz_frequencies[]; extern const uint32_t subghz_hopper_frequencies[]; @@ -44,9 +41,17 @@ extern const uint32_t subghz_frequencies_count; extern const uint32_t subghz_hopper_frequencies_count; extern const uint32_t subghz_frequencies_433_92; +/** SubGhzNotification state */ +typedef enum { + SubGhzNotificationStateStarting, + SubGhzNotificationStateIDLE, + SubGhzNotificationStateTX, + SubGhzNotificationStateRX, +} SubGhzNotificationState; + /** SubGhzTxRx state */ typedef enum { - SubGhzTxRxStateIdle, + SubGhzTxRxStateIDLE, SubGhzTxRxStateRx, SubGhzTxRxStateTx, SubGhzTxRxStateSleep, @@ -60,6 +65,15 @@ typedef enum { SubGhzHopperStateRSSITimeOut, } SubGhzHopperState; +/** SubGhzRxKeyState state */ +typedef enum { + SubGhzRxKeyStateIDLE, + SubGhzRxKeyStateNoSave, + SubGhzRxKeyStateNeedSave, + SubGhzRxKeyStateAddKey, + SubGhzRxKeyStateExit, +} SubGhzRxKeyState; + struct SubGhzTxRx { SubGhzWorker* worker; SubGhzParser* parser; @@ -70,10 +84,10 @@ struct SubGhzTxRx { SubGhzHistory* history; uint16_t idx_menu_chosen; SubGhzTxRxState txrx_state; - //bool hopper_runing; SubGhzHopperState hopper_state; uint8_t hopper_timeout; uint8_t hopper_idx_frequency; + SubGhzRxKeyState rx_key_state; }; typedef struct SubGhzTxRx SubGhzTxRx; @@ -91,15 +105,17 @@ struct SubGhz { Popup* popup; TextInput* text_input; Widget* widget; + DialogsApp* dialogs; char file_name[SUBGHZ_TEXT_STORE_SIZE + 1]; char file_name_tmp[SUBGHZ_TEXT_STORE_SIZE + 1]; - uint8_t state_notifications; + SubGhzNotificationState state_notifications; SubghzReceiver* subghz_receiver; SubghzTransmitter* 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; @@ -116,11 +132,15 @@ typedef enum { SubGhzViewTransmitter, SubGhzViewVariableItemList, SubGhzViewFrequencyAnalyzer, + SubGhzViewReadRAW, SubGhzViewStatic, SubGhzViewTestCarrier, SubGhzViewTestPacket, } SubGhzView; +bool subghz_set_pteset(SubGhz* subghz, const char* preset); +bool subghz_get_preset_name(SubGhz* subghz, string_t 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); @@ -128,8 +148,10 @@ void subghz_sleep(SubGhz* subghz); bool subghz_tx_start(SubGhz* subghz); 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_load_protocol_from_file(SubGhz* subghz); +bool subghz_rename_file(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); void subghz_file_name_clear(SubGhz* subghz); uint32_t subghz_random_serial(void); diff --git a/applications/subghz/views/subghz_frequency_analyzer.h b/applications/subghz/views/subghz_frequency_analyzer.h index ebfcb173..78280503 100644 --- a/applications/subghz/views/subghz_frequency_analyzer.h +++ b/applications/subghz/views/subghz_frequency_analyzer.h @@ -1,14 +1,11 @@ #pragma once #include - -typedef enum { - SubghzFrequencyAnalyzerEventOnlyRx, -} SubghzFrequencyAnalyzerEvent; +#include "../helpers/subghz_custom_event.h" typedef struct SubghzFrequencyAnalyzer SubghzFrequencyAnalyzer; -typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzFrequencyAnalyzerEvent event, void* context); +typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzCustomEvent event, void* context); void subghz_frequency_analyzer_set_callback( SubghzFrequencyAnalyzer* subghz_frequency_analyzer, diff --git a/applications/subghz/views/subghz_read_raw.c b/applications/subghz/views/subghz_read_raw.c new file mode 100644 index 00000000..fb9d694b --- /dev/null +++ b/applications/subghz/views/subghz_read_raw.c @@ -0,0 +1,274 @@ +#include "subghz_read_raw.h" +#include "../subghz_i.h" + +#include +#include +#include +#include +#include +#include + +#include +#define SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE 100 + +typedef enum { + SubghzReadRAWStatusStart, + SubghzReadRAWStatusIDLE, + SubghzReadRAWStatusREC, + //SubghzReadRAWStatusShowName, +} SubghzReadRAWStatus; + +struct SubghzReadRAW { + View* view; + SubghzReadRAWCallback callback; + void* context; +}; + +typedef struct { + string_t frequency_str; + string_t preset_str; + string_t sample_write; + uint8_t* rssi_history; + bool rssi_history_end; + uint8_t ind_write; + SubghzReadRAWStatus satus; +} SubghzReadRAWModel; + +void subghz_read_raw_set_callback( + SubghzReadRAW* subghz_read_raw, + SubghzReadRAWCallback callback, + void* context) { + furi_assert(subghz_read_raw); + furi_assert(callback); + subghz_read_raw->callback = callback; + subghz_read_raw->context = context; +} + +void subghz_read_raw_add_data_statusbar( + SubghzReadRAW* instance, + const char* frequency_str, + const char* preset_str) { + furi_assert(instance); + with_view_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) { + furi_assert(instance); + uint8_t u_rssi = 0; + + if(rssi < -90) { + u_rssi = 0; + } else { + u_rssi = (uint8_t)((rssi + 90) / 2.7); + } + //if(u_rssi > 34) u_rssi = 34; + + with_view_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; + model->ind_write = 0; + } + return true; + }); +} + +void subghz_read_raw_update_sample_write(SubghzReadRAW* instance, size_t sample) { + furi_assert(instance); + + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + string_printf(model->sample_write, "%d spl.", sample); + return false; + }); +} + +void subghz_read_raw_draw_rssi(Canvas* canvas, SubghzReadRAWModel* model) { + int ind = 0; + int base = 0; + if(model->rssi_history_end == false) { + for(int i = model->ind_write; i >= 0; i--) { + canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[i]); + } + if(model->ind_write > 3) { + canvas_draw_line(canvas, model->ind_write, 47, model->ind_write, 13); + canvas_draw_line(canvas, model->ind_write - 2, 12, model->ind_write + 2, 12); + canvas_draw_line(canvas, model->ind_write - 1, 13, model->ind_write + 1, 13); + } + } else { + base = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - model->ind_write; + for(int i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i >= 0; i--) { + ind = i - base; + if(ind < 0) ind += SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; + canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[ind]); + } + canvas_draw_line( + canvas, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 47, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 13); + canvas_draw_line( + canvas, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 2, + 12, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 2, + 12); + canvas_draw_line( + canvas, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1, + 13, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1, + 13); + } +} + +void subghz_read_raw_draw(Canvas* canvas, SubghzReadRAWModel* model) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 5, 8, string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 40, 8, string_get_cstr(model->preset_str)); + canvas_draw_str_aligned( + canvas, 126, 0, AlignRight, AlignTop, string_get_cstr(model->sample_write)); + + canvas_draw_line(canvas, 0, 14, 115, 14); + subghz_read_raw_draw_rssi(canvas, model); + canvas_draw_line(canvas, 0, 48, 115, 48); + canvas_draw_line(canvas, 115, 14, 115, 48); + + if(model->satus == SubghzReadRAWStatusIDLE) { + elements_button_left(canvas, "Config"); + elements_button_center(canvas, "REC"); + elements_button_right(canvas, "More"); + } else if(model->satus == SubghzReadRAWStatusStart) { + elements_button_left(canvas, "Config"); + elements_button_center(canvas, "REC"); + } else { + elements_button_center(canvas, "Stop"); + } + + canvas_set_font_direction(canvas, 3); + canvas_draw_str(canvas, 126, 40, "RSSI"); + canvas_set_font_direction(canvas, 0); +} + +bool subghz_read_raw_input(InputEvent* event, void* context) { + furi_assert(context); + SubghzReadRAW* instance = context; + + if(event->key == InputKeyBack && event->type == InputTypeShort) { + instance->callback(SubghzCustomEventViewReadRAWBack, instance->context); + } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + if(model->satus == SubghzReadRAWStatusIDLE || + model->satus == SubghzReadRAWStatusStart) { + instance->callback(SubghzCustomEventViewReadRAWConfig, instance->context); + } + return true; + }); + } else if(event->key == InputKeyRight && event->type == InputTypeShort) { + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + if(model->satus == SubghzReadRAWStatusIDLE) { + 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 == SubghzReadRAWStatusIDLE || + model->satus == SubghzReadRAWStatusStart) { + instance->callback(SubghzCustomEventViewReadRAWREC, instance->context); + model->satus = SubghzReadRAWStatusREC; + model->ind_write = 0; + model->rssi_history_end = false; + } else { + instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context); + model->satus = SubghzReadRAWStatusIDLE; + } + return true; + }); + } + + return true; +} + +void subghz_read_raw_enter(void* context) { + furi_assert(context); + SubghzReadRAW* instance = context; + + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + model->satus = SubghzReadRAWStatusStart; + model->rssi_history = furi_alloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t)); + model->rssi_history_end = false; + model->ind_write = 0; + string_set(model->sample_write, "0 spl."); + return true; + }); +} + +void subghz_read_raw_exit(void* context) { + furi_assert(context); + SubghzReadRAW* instance = context; + + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + if(model->satus != SubghzReadRAWStatusIDLE && + model->satus != SubghzReadRAWStatusStart) { + instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context); + model->satus = SubghzReadRAWStatusStart; + } + string_reset(model->frequency_str); + string_reset(model->preset_str); + string_reset(model->sample_write); + free(model->rssi_history); + return true; + }); +} + +SubghzReadRAW* subghz_read_raw_alloc() { + SubghzReadRAW* instance = furi_alloc(sizeof(SubghzReadRAW)); + + // View allocation and configuration + instance->view = view_alloc(); + 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); + view_set_enter_callback(instance->view, subghz_read_raw_enter); + view_set_exit_callback(instance->view, subghz_read_raw_exit); + + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + string_init(model->frequency_str); + string_init(model->preset_str); + string_init(model->sample_write); + return true; + }); + + return instance; +} + +void subghz_read_raw_free(SubghzReadRAW* instance) { + furi_assert(instance); + + with_view_model( + instance->view, (SubghzReadRAWModel * model) { + string_clear(model->frequency_str); + string_clear(model->preset_str); + string_clear(model->sample_write); + return true; + }); + view_free(instance->view); + free(instance); +} + +View* subghz_read_raw_get_view(SubghzReadRAW* instance) { + furi_assert(instance); + return instance->view; +} \ No newline at end of file diff --git a/applications/subghz/views/subghz_read_raw.h b/applications/subghz/views/subghz_read_raw.h new file mode 100644 index 00000000..d6a2337f --- /dev/null +++ b/applications/subghz/views/subghz_read_raw.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "../helpers/subghz_custom_event.h" + +typedef struct SubghzReadRAW SubghzReadRAW; + +typedef void (*SubghzReadRAWCallback)(SubghzCustomEvent event, void* context); + +void subghz_read_raw_set_callback( + SubghzReadRAW* subghz_read_raw, + SubghzReadRAWCallback callback, + void* context); + +SubghzReadRAW* subghz_read_raw_alloc(); + +void subghz_read_raw_free(SubghzReadRAW* subghz_static); + +void subghz_read_raw_add_data_statusbar( + 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_add_data_rssi(SubghzReadRAW* instance, float rssi); + +View* subghz_read_raw_get_view(SubghzReadRAW* subghz_static); diff --git a/applications/subghz/views/subghz_receiver.c b/applications/subghz/views/subghz_receiver.c index 26aec5e7..b158d870 100644 --- a/applications/subghz/views/subghz_receiver.c +++ b/applications/subghz/views/subghz_receiver.c @@ -168,7 +168,7 @@ void subghz_receiver_draw(Canvas* canvas, SubghzReceiverModel* model) { } canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); - string_clean(str_buff); + string_reset(str_buff); } if(scrollbar) { elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); @@ -181,7 +181,7 @@ bool subghz_receiver_input(InputEvent* event, void* context) { SubghzReceiver* subghz_receiver = context; if(event->key == InputKeyBack && event->type == InputTypeShort) { - subghz_receiver->callback(SubghzReceverEventBack, subghz_receiver->context); + subghz_receiver->callback(SubghzCustomEventViewReceverBack, subghz_receiver->context); } else if( event->key == InputKeyUp && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { @@ -199,12 +199,13 @@ bool subghz_receiver_input(InputEvent* event, void* context) { return true; }); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { - subghz_receiver->callback(SubghzReceverEventConfig, 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) { if(model->history_item != 0) { - subghz_receiver->callback(SubghzReceverEventOK, subghz_receiver->context); + subghz_receiver->callback( + SubghzCustomEventViewReceverOK, subghz_receiver->context); } return false; }); @@ -225,15 +226,15 @@ void subghz_receiver_exit(void* context) { SubghzReceiver* subghz_receiver = context; with_view_model( subghz_receiver->view, (SubghzReceiverModel * model) { - string_clean(model->frequency_str); - string_clean(model->preset_str); - string_clean(model->history_stat_str); + string_reset(model->frequency_str); + string_reset(model->preset_str); + string_reset(model->history_stat_str); for M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { string_clear(item_menu->item_str); item_menu->type = 0; } - SubGhzReceiverMenuItemArray_clean(model->history->data); + SubGhzReceiverMenuItemArray_reset(model->history->data); model->idx = 0; model->list_offset = 0; model->history_item = 0; diff --git a/applications/subghz/views/subghz_receiver.h b/applications/subghz/views/subghz_receiver.h index be9a9ef1..e26ee2c2 100644 --- a/applications/subghz/views/subghz_receiver.h +++ b/applications/subghz/views/subghz_receiver.h @@ -1,16 +1,11 @@ #pragma once #include - -typedef enum { - SubghzReceverEventOK, - SubghzReceverEventConfig, - SubghzReceverEventBack, -} SubghzReceverEvent; +#include "../helpers/subghz_custom_event.h" typedef struct SubghzReceiver SubghzReceiver; -typedef void (*SubghzReceiverCallback)(SubghzReceverEvent event, void* context); +typedef void (*SubghzReceiverCallback)(SubghzCustomEvent event, void* context); void subghz_receiver_set_callback( SubghzReceiver* subghz_receiver, diff --git a/applications/subghz/views/subghz_test_static.c b/applications/subghz/views/subghz_test_static.c index aa701464..bc672406 100644 --- a/applications/subghz/views/subghz_test_static.c +++ b/applications/subghz/views/subghz_test_static.c @@ -8,6 +8,8 @@ #include #include +#define TAG "SubGhzTestStatic" + typedef enum { SubghzTestStaticStatusIDLE, SubghzTestStaticStatusTX, @@ -99,7 +101,7 @@ bool subghz_test_static_input(InputEvent* event, void* context) { } else { notification_message_block(notification, &sequence_set_red_255); - FURI_LOG_I("SubghzTestStatic", "TX Start"); + FURI_LOG_I(TAG, "TX Start"); subghz_encoder_princeton_set( instance->encoder, subghz_test_static_keys[model->button], 10000); @@ -110,7 +112,7 @@ bool subghz_test_static_input(InputEvent* event, void* context) { } } else if(event->type == InputTypeRelease) { if(instance->satus_tx == SubghzTestStaticStatusTX) { - FURI_LOG_I("SubghzTestStatic", "TX Stop"); + FURI_LOG_I(TAG, "TX Stop"); subghz_encoder_princeton_print_log(instance->encoder); furi_hal_subghz_stop_async_tx(); notification_message(notification, &sequence_reset_red); diff --git a/applications/subghz/views/subghz_transmitter.c b/applications/subghz/views/subghz_transmitter.c index 6e29f5dd..b9421e4f 100644 --- a/applications/subghz/views/subghz_transmitter.c +++ b/applications/subghz/views/subghz_transmitter.c @@ -93,9 +93,9 @@ bool subghz_transmitter_input(InputEvent* event, void* context) { if(event->key == InputKeyBack && event->type == InputTypeShort) { with_view_model( subghz_transmitter->view, (SubghzTransmitterModel * model) { - string_clean(model->frequency_str); - string_clean(model->preset_str); - string_clean(model->key_str); + string_reset(model->frequency_str); + string_reset(model->preset_str); + string_reset(model->key_str); model->show_button = 0; return false; }); @@ -111,10 +111,12 @@ bool subghz_transmitter_input(InputEvent* event, void* context) { }); if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) { - subghz_transmitter->callback(SubghzTransmitterEventSendStart, subghz_transmitter->context); + subghz_transmitter->callback( + SubghzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; } else if(can_be_sent && event->key == InputKeyOk && event->type == InputTypeRelease) { - subghz_transmitter->callback(SubghzTransmitterEventSendStop, subghz_transmitter->context); + subghz_transmitter->callback( + SubghzCustomEventViewTransmitterSendStop, subghz_transmitter->context); return true; } diff --git a/applications/subghz/views/subghz_transmitter.h b/applications/subghz/views/subghz_transmitter.h index 8ddeaaf8..995e08f6 100644 --- a/applications/subghz/views/subghz_transmitter.h +++ b/applications/subghz/views/subghz_transmitter.h @@ -1,17 +1,11 @@ #pragma once #include - -typedef enum { - SubghzTransmitterEventSendStart, - SubghzTransmitterEventSendStop, - SubghzTransmitterEventBack, - SubghzTransmitterEventNoMan, -} SubghzTransmitterEvent; +#include "../helpers/subghz_custom_event.h" typedef struct SubghzTransmitter SubghzTransmitter; -typedef void (*SubghzTransmitterCallback)(SubghzTransmitterEvent event, void* context); +typedef void (*SubghzTransmitterCallback)(SubghzCustomEvent event, void* context); void subghz_transmitter_set_callback( SubghzTransmitter* subghz_transmitter, diff --git a/applications/tests/flipper_file/flipper_file_test.c b/applications/tests/flipper_file/flipper_file_test.c new file mode 100644 index 00000000..8d21bb42 --- /dev/null +++ b/applications/tests/flipper_file/flipper_file_test.c @@ -0,0 +1,500 @@ +#include +#include +#include "../minunit.h" + +#define TEST_DIR TEST_DIR_NAME "/" +#define TEST_DIR_NAME "/ext/unit_tests_tmp" + +static const char* test_filetype = "Flipper File test"; +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_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 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 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 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}; + +#define READ_TEST_WIN "ff_win.test" +static const char* test_data_win = "Filetype: Flipper File test\n" + "Version: 666\n" + "# This is comment\n" + "String data: String\n" + "Int32 data: 1234 -6345 7813 0\n" + "Uint32 data: 1234 0 5678 9098 7654321\n" + "Float data: 1.5 1000.0\n" + "Hex data: DE AD BE"; + +#define READ_TEST_NIX "ff_nix.test" +static const char* test_data_nix = "Filetype: Flipper File test\r\n" + "Version: 666\r\n" + "# This is comment\r\n" + "String data: String\r\n" + "Int32 data: 1234 -6345 7813 0\r\n" + "Uint32 data: 1234 0 5678 9098 7654321\r\n" + "Float data: 1.5 1000.0\r\n" + "Hex data: DE AD BE"; + +#define READ_TEST_FLP "ff_flp.test" + +// data created by user on linux machine +const char* test_file_linux = TEST_DIR READ_TEST_WIN; +// data created by user on windows machine +const char* test_file_windows = TEST_DIR READ_TEST_NIX; +// data created by flipper itself +const char* test_file_flipper = TEST_DIR READ_TEST_FLP; + +static bool storage_write_string(const char* path, const char* data) { + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + bool result = false; + + do { + if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break; + if(storage_file_write(file, data, strlen(data)) != strlen(data)) break; + + result = true; + } while(false); + + storage_file_close(file); + storage_file_free(file); + furi_record_close("storage"); + + return result; +} + +static void tests_setup() { + Storage* storage = furi_record_open("storage"); + mu_assert(storage_simply_remove_recursive(storage, TEST_DIR_NAME), "Cannot clean data"); + mu_assert(storage_simply_mkdir(storage, TEST_DIR_NAME), "Cannot create dir"); + furi_record_close("storage"); +} + +static void tests_teardown() { + Storage* storage = furi_record_open("storage"); + mu_assert(storage_simply_remove_recursive(storage, TEST_DIR_NAME), "Cannot clean data"); + furi_record_close("storage"); +} + +static bool test_read(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + + FlipperFile* file = flipper_file_alloc(storage); + string_t string_value; + string_init(string_value); + uint32_t uint32_value; + void* scratchpad = malloc(512); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + + if(!flipper_file_read_header(file, string_value, &uint32_value)) break; + if(string_cmp_str(string_value, test_filetype) != 0) break; + if(uint32_value != test_version) break; + + if(!flipper_file_read_string(file, test_string_key, string_value)) break; + if(string_cmp_str(string_value, test_string_data) != 0) break; + + if(!flipper_file_get_value_count(file, test_int_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_int_data)) break; + if(!flipper_file_read_int32(file, test_int_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_int_data, sizeof(int32_t) * COUNT_OF(test_int_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_uint_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_uint_data)) break; + if(!flipper_file_read_uint32(file, test_uint_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_uint_data, sizeof(uint32_t) * COUNT_OF(test_uint_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_float_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_float_data)) break; + if(!flipper_file_read_float(file, test_float_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_float_data, sizeof(float) * COUNT_OF(test_float_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_hex_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_hex_data)) break; + if(!flipper_file_read_hex(file, test_hex_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_hex_data, sizeof(uint8_t) * COUNT_OF(test_hex_data)) != 0) + break; + + result = true; + } while(false); + + free(scratchpad); + string_clear(string_value); + flipper_file_close(file); + flipper_file_free(file); + + furi_record_close("storage"); + + return result; +} + +static bool test_read_updated(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + + FlipperFile* file = flipper_file_alloc(storage); + string_t string_value; + string_init(string_value); + uint32_t uint32_value; + void* scratchpad = malloc(512); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + + if(!flipper_file_read_header(file, string_value, &uint32_value)) break; + if(string_cmp_str(string_value, test_filetype) != 0) break; + if(uint32_value != test_version) break; + + if(!flipper_file_read_string(file, test_string_key, string_value)) break; + if(string_cmp_str(string_value, test_string_updated_data) != 0) break; + + if(!flipper_file_get_value_count(file, test_int_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_int_updated_data)) break; + if(!flipper_file_read_int32(file, test_int_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_int_updated_data, + sizeof(int32_t) * COUNT_OF(test_int_updated_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_uint_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_uint_updated_data)) break; + if(!flipper_file_read_uint32(file, test_uint_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_uint_updated_data, + sizeof(uint32_t) * COUNT_OF(test_uint_updated_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_float_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_float_updated_data)) break; + if(!flipper_file_read_float(file, test_float_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_float_updated_data, + sizeof(float) * COUNT_OF(test_float_updated_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_hex_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_hex_updated_data)) break; + if(!flipper_file_read_hex(file, test_hex_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_hex_updated_data, + sizeof(uint8_t) * COUNT_OF(test_hex_updated_data)) != 0) + break; + + result = true; + } while(false); + + free(scratchpad); + string_clear(string_value); + flipper_file_close(file); + flipper_file_free(file); + + furi_record_close("storage"); + + return result; +} + +static bool test_write(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_always(file, file_name)) break; + if(!flipper_file_write_header_cstr(file, test_filetype, test_version)) break; + if(!flipper_file_write_comment_cstr(file, "This is comment")) break; + if(!flipper_file_write_string_cstr(file, test_string_key, test_string_data)) break; + if(!flipper_file_write_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data))) + break; + if(!flipper_file_write_uint32( + file, test_uint_key, test_uint_data, COUNT_OF(test_uint_data))) + break; + if(!flipper_file_write_float( + file, test_float_key, test_float_data, COUNT_OF(test_float_data))) + break; + if(!flipper_file_write_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) + break; + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_delete_last_key(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_delete_key(file, test_hex_key)) break; + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_append_key(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_append(file, file_name)) break; + if(!flipper_file_write_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) + break; + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_update(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_update_string_cstr(file, test_string_key, test_string_updated_data)) + break; + if(!flipper_file_update_int32( + file, test_int_key, test_int_updated_data, COUNT_OF(test_int_updated_data))) + break; + if(!flipper_file_update_uint32( + file, test_uint_key, test_uint_updated_data, COUNT_OF(test_uint_updated_data))) + break; + if(!flipper_file_update_float( + file, test_float_key, test_float_updated_data, COUNT_OF(test_float_updated_data))) + break; + if(!flipper_file_update_hex( + file, test_hex_key, test_hex_updated_data, COUNT_OF(test_hex_updated_data))) + break; + + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_update_backward(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_update_string_cstr(file, test_string_key, test_string_data)) break; + if(!flipper_file_update_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data))) + break; + if(!flipper_file_update_uint32( + file, test_uint_key, test_uint_data, COUNT_OF(test_uint_data))) + break; + if(!flipper_file_update_float( + file, test_float_key, test_float_data, COUNT_OF(test_float_data))) + break; + if(!flipper_file_update_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) + break; + + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_write_multikey(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_always(file, file_name)) break; + if(!flipper_file_write_header_cstr(file, test_filetype, test_version)) break; + + bool error = false; + for(uint8_t index = 0; index < 100; index++) { + if(!flipper_file_write_hex(file, test_hex_key, &index, 1)) { + error = true; + break; + } + } + if(error) break; + + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_read_multikey(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + string_t string_value; + string_init(string_value); + uint32_t uint32_value; + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_read_header(file, string_value, &uint32_value)) break; + if(string_cmp_str(string_value, test_filetype) != 0) break; + if(uint32_value != test_version) break; + + bool error = false; + uint8_t uint8_value; + for(uint8_t index = 0; index < 100; index++) { + if(!flipper_file_read_hex(file, test_hex_key, &uint8_value, 1)) { + error = true; + break; + } + + if(uint8_value != index) { + error = true; + break; + } + } + if(error) break; + + result = true; + } while(false); + + string_clear(string_value); + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +MU_TEST(flipper_file_write_test) { + mu_assert(storage_write_string(test_file_linux, test_data_nix), "Write test error [Linux]"); + mu_assert( + storage_write_string(test_file_windows, test_data_win), "Write test error [Windows]"); + mu_assert(test_write(test_file_flipper), "Write test error [Flipper]"); +} + +MU_TEST(flipper_file_read_test) { + mu_assert(test_read(test_file_linux), "Read test error [Linux]"); + mu_assert(test_read(test_file_windows), "Read test error [Windows]"); + mu_assert(test_read(test_file_flipper), "Read test error [Flipper]"); +} + +MU_TEST(flipper_file_delete_test) { + mu_assert(test_delete_last_key(test_file_linux), "Cannot delete key [Linux]"); + mu_assert(test_delete_last_key(test_file_windows), "Cannot delete key [Windows]"); + mu_assert(test_delete_last_key(test_file_flipper), "Cannot delete key [Flipper]"); +} + +MU_TEST(flipper_file_delete_result_test) { + mu_assert(!test_read(test_file_linux), "Key deleted incorrectly [Linux]"); + mu_assert(!test_read(test_file_windows), "Key deleted incorrectly [Windows]"); + mu_assert(!test_read(test_file_flipper), "Key deleted incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_append_test) { + mu_assert(test_append_key(test_file_linux), "Cannot append data [Linux]"); + mu_assert(test_append_key(test_file_windows), "Cannot append data [Windows]"); + mu_assert(test_append_key(test_file_flipper), "Cannot append data [Flipper]"); +} + +MU_TEST(flipper_file_append_result_test) { + mu_assert(test_read(test_file_linux), "Data appended incorrectly [Linux]"); + mu_assert(test_read(test_file_windows), "Data appended incorrectly [Windows]"); + mu_assert(test_read(test_file_flipper), "Data appended incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_update_1_test) { + mu_assert(test_update(test_file_linux), "Cannot update data #1 [Linux]"); + mu_assert(test_update(test_file_windows), "Cannot update data #1 [Windows]"); + mu_assert(test_update(test_file_flipper), "Cannot update data #1 [Flipper]"); +} + +MU_TEST(flipper_file_update_1_result_test) { + mu_assert(test_read_updated(test_file_linux), "Data #1 updated incorrectly [Linux]"); + mu_assert(test_read_updated(test_file_windows), "Data #1 updated incorrectly [Windows]"); + mu_assert(test_read_updated(test_file_flipper), "Data #1 updated incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_update_2_test) { + mu_assert(test_update_backward(test_file_linux), "Cannot update data #2 [Linux]"); + mu_assert(test_update_backward(test_file_windows), "Cannot update data #2 [Windows]"); + mu_assert(test_update_backward(test_file_flipper), "Cannot update data #2 [Flipper]"); +} + +MU_TEST(flipper_file_update_2_result_test) { + mu_assert(test_read(test_file_linux), "Data #2 updated incorrectly [Linux]"); + mu_assert(test_read(test_file_windows), "Data #2 updated incorrectly [Windows]"); + mu_assert(test_read(test_file_flipper), "Data #2 updated incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_multikey_test) { + mu_assert(test_write_multikey(TEST_DIR "ff_multiline.test"), "Multikey write test error"); + mu_assert(test_read_multikey(TEST_DIR "ff_multiline.test"), "Multikey read test error"); +} + +MU_TEST_SUITE(flipper_file) { + tests_setup(); + MU_RUN_TEST(flipper_file_write_test); + MU_RUN_TEST(flipper_file_read_test); + MU_RUN_TEST(flipper_file_delete_test); + MU_RUN_TEST(flipper_file_delete_result_test); + MU_RUN_TEST(flipper_file_append_test); + MU_RUN_TEST(flipper_file_append_result_test); + MU_RUN_TEST(flipper_file_update_1_test); + MU_RUN_TEST(flipper_file_update_1_result_test); + MU_RUN_TEST(flipper_file_update_2_test); + MU_RUN_TEST(flipper_file_update_2_result_test); + MU_RUN_TEST(flipper_file_multikey_test); + tests_teardown(); +} + +int run_minunit_test_flipper_file() { + MU_RUN_SUITE(flipper_file); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/tests/furi_pubsub_test.c b/applications/tests/furi_pubsub_test.c index d52b6bd3..010a00ab 100644 --- a/applications/tests/furi_pubsub_test.c +++ b/applications/tests/furi_pubsub_test.c @@ -16,39 +16,30 @@ void test_pubsub_handler(const void* arg, void* ctx) { } void test_furi_pubsub() { - bool result; - PubSub test_pubsub; - PubSubItem* test_pubsub_item; + FuriPubSub* test_pubsub = NULL; + FuriPubSubSubscription* test_pubsub_subscription = NULL; // init pubsub case - result = init_pubsub(&test_pubsub); - mu_assert(result, "init pubsub failed"); + test_pubsub = furi_pubsub_alloc(); + mu_assert_pointers_not_eq(test_pubsub, NULL); // subscribe pubsub case - test_pubsub_item = subscribe_pubsub(&test_pubsub, test_pubsub_handler, (void*)&context_value); - mu_assert_pointers_not_eq(test_pubsub_item, NULL); + test_pubsub_subscription = + furi_pubsub_subscribe(test_pubsub, test_pubsub_handler, (void*)&context_value); + mu_assert_pointers_not_eq(test_pubsub_subscription, NULL); /// notify pubsub case - result = notify_pubsub(&test_pubsub, (void*)¬ify_value_0); - mu_assert(result, "notify pubsub failed"); + furi_pubsub_publish(test_pubsub, (void*)¬ify_value_0); mu_assert_int_eq(pubsub_value, notify_value_0); mu_assert_int_eq(pubsub_context_value, context_value); // unsubscribe pubsub case - result = unsubscribe_pubsub(test_pubsub_item); - mu_assert(result, "unsubscribe pubsub failed"); - - result = unsubscribe_pubsub(test_pubsub_item); - mu_assert(!result, "unsubscribe pubsub not failed"); + furi_pubsub_unsubscribe(test_pubsub, test_pubsub_subscription); /// notify unsubscribed pubsub case - result = notify_pubsub(&test_pubsub, (void*)¬ify_value_1); - mu_assert(result, "notify pubsub failed"); + furi_pubsub_publish(test_pubsub, (void*)¬ify_value_1); mu_assert_int_not_eq(pubsub_value, notify_value_1); // delete pubsub case - result = delete_pubsub(&test_pubsub); - mu_assert(result, "unsubscribe pubsub failed"); - - // TODO test case that the pubsub_delete will remove pubsub from heap + furi_pubsub_free(test_pubsub); } \ No newline at end of file diff --git a/applications/tests/furi_record_test.c b/applications/tests/furi_record_test.c index 7f8a0507..e7eebe4b 100644 --- a/applications/tests/furi_record_test.c +++ b/applications/tests/furi_record_test.c @@ -11,7 +11,10 @@ void test_furi_create_open() { // 2. Open it void* record = furi_record_open("test/holding"); mu_assert_pointers_eq(record, &test_data); + + // 3. Close it furi_record_close("test/holding"); + // 4. Clean up furi_record_destroy("test/holding"); } diff --git a/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c b/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c index 908e196d..80f036b0 100644 --- a/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c +++ b/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c @@ -325,7 +325,6 @@ MU_TEST_SUITE(test_irda_decoder_encoder) { int run_minunit_test_irda_decoder_encoder() { MU_RUN_SUITE(test_irda_decoder_encoder); - MU_REPORT(); return MU_EXIT_CODE; } diff --git a/applications/tests/minunit.h b/applications/tests/minunit.h index 466cf9c1..b12a87ca 100644 --- a/applications/tests/minunit.h +++ b/applications/tests/minunit.h @@ -79,6 +79,9 @@ extern "C" { __attribute__((unused)) static void (*minunit_setup)(void) = NULL; __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; +void minunit_print_progress(void); +void minunit_print_fail(const char* error); + /* Definitions */ #define MU_TEST(method_name) static void method_name(void) #define MU_TEST_SUITE(suite_name) static void suite_name(void) @@ -108,8 +111,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_run++; \ if(minunit_status) { \ minunit_fail++; \ - printf("F"); \ - printf("\n%s\n", minunit_last_message); \ + minunit_print_fail(minunit_last_message); \ } fflush(stdout); \ if(minunit_teardown)(*minunit_teardown)();) @@ -142,7 +144,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; #test); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_fail(message) \ MU__SAFE_BLOCK(minunit_assert++; snprintf( \ @@ -169,7 +171,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; message); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -187,7 +189,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_not_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -204,7 +206,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_greater_than(val, result) \ MU__SAFE_BLOCK( \ @@ -222,7 +224,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_less_than(val, result) \ MU__SAFE_BLOCK( \ @@ -240,7 +242,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_between(expected_lower, expected_upper, result) \ MU__SAFE_BLOCK( \ @@ -261,7 +263,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_m); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_in(expected, array_length, result) \ MU__SAFE_BLOCK( \ @@ -288,7 +290,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -309,7 +311,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_greater_than(val, result) \ MU__SAFE_BLOCK( \ @@ -327,7 +329,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_less_than(val, result) \ MU__SAFE_BLOCK( \ @@ -345,7 +347,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_between(expected_lower, expected_upper, result) \ MU__SAFE_BLOCK( \ @@ -366,7 +368,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_m); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_string_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -386,39 +388,39 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) -#define mu_assert_null(result) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(result == NULL) { printf("."); } else { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected result was not NULL", \ - __func__, \ - __FILE__, \ - __LINE__); \ - minunit_status = 1; \ - return; \ +#define mu_assert_null(result) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(result == NULL) { minunit_print_progress(); } else { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\n\t%s:%d: Expected result was not NULL", \ + __func__, \ + __FILE__, \ + __LINE__); \ + minunit_status = 1; \ + return; \ }) -#define mu_assert_not_null(result) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(result != NULL) { printf("."); } else { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected result was not NULL", \ - __func__, \ - __FILE__, \ - __LINE__); \ - minunit_status = 1; \ - return; \ +#define mu_assert_not_null(result) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(result != NULL) { minunit_print_progress(); } else { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\n\t%s:%d: Expected result was not NULL", \ + __func__, \ + __FILE__, \ + __LINE__); \ + minunit_status = 1; \ + return; \ }) #define mu_assert_pointers_eq(pointer1, pointer2) \ MU__SAFE_BLOCK( \ - minunit_assert++; if(pointer1 == pointer2) { printf("."); } else { \ + minunit_assert++; if(pointer1 == pointer2) { minunit_print_progress(); } else { \ snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ @@ -432,7 +434,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; #define mu_assert_pointers_not_eq(pointer1, pointer2) \ MU__SAFE_BLOCK( \ - minunit_assert++; if(pointer1 != pointer2) { printf("."); } else { \ + minunit_assert++; if(pointer1 != pointer2) { minunit_print_progress(); } else { \ snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ @@ -603,4 +605,4 @@ __attribute__((unused)) static double mu_timer_cpu(void) { } #endif -#endif /* MINUNIT_MINUNIT_H */ \ No newline at end of file +#endif /* MINUNIT_MINUNIT_H */ diff --git a/applications/tests/minunit_test.c b/applications/tests/minunit_test.c index 8c94c845..1e71fd07 100644 --- a/applications/tests/minunit_test.c +++ b/applications/tests/minunit_test.c @@ -62,7 +62,6 @@ MU_TEST_SUITE(test_suite) { int run_minunit() { MU_RUN_SUITE(test_suite); - MU_REPORT(); return MU_EXIT_CODE; } diff --git a/applications/tests/rpc/rpc_test.c b/applications/tests/rpc/rpc_test.c index 18f1c7a8..af99be4a 100644 --- a/applications/tests/rpc/rpc_test.c +++ b/applications/tests/rpc/rpc_test.c @@ -29,7 +29,7 @@ static RpcSession* session = NULL; static StreamBufferHandle_t output_stream = NULL; static uint32_t command_id = 0; -#define TEST_RPC_TAG "TEST_RPC" +#define TAG "UnitTestsRpc" #define MAX_RECEIVE_OUTPUT_TIMEOUT 3000 #define MAX_NAME_LENGTH 255 #define MAX_DATA_SIZE 512 // have to be exact as in rpc_storage.c @@ -56,25 +56,40 @@ 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_free_msg_list(MsgList_t msg_list); -static void test_rpc_storage_setup(void) { +static void test_rpc_setup(void) { furi_assert(!rpc); furi_assert(!session); furi_assert(!output_stream); rpc = furi_record_open("rpc"); for(int i = 0; !session && (i < 10000); ++i) { - session = rpc_open_session(rpc); + session = rpc_session_open(rpc); delay(1); } furi_assert(session); + output_stream = xStreamBufferCreate(1000, 1); + mu_assert(session, "failed to start session"); + rpc_session_set_send_bytes_callback(session, output_bytes_callback); + rpc_session_set_context(session, output_stream); +} + +static void test_rpc_teardown(void) { + rpc_session_close(session); + furi_record_close("rpc"); + vStreamBufferDelete(output_stream); + ++command_id; + output_stream = NULL; + rpc = NULL; + session = NULL; +} + +static void test_rpc_storage_setup(void) { + test_rpc_setup(); + Storage* fs_api = furi_record_open("storage"); clean_directory(fs_api, TEST_DIR_NAME); furi_record_close("storage"); - - output_stream = xStreamBufferCreate(1000, 1); - mu_assert(session, "failed to start session"); - rpc_set_send_bytes_callback(session, output_bytes_callback, output_stream); } static void test_rpc_storage_teardown(void) { @@ -82,13 +97,7 @@ static void test_rpc_storage_teardown(void) { clean_directory(fs_api, TEST_DIR_NAME); furi_record_close("storage"); - rpc_close_session(session); - furi_record_close("rpc"); - vStreamBufferDelete(output_stream); - ++command_id; - output_stream = NULL; - rpc = NULL; - session = NULL; + test_rpc_teardown(); } static void clean_directory(Storage* fs_api, const char* clean_dir) { @@ -197,16 +206,21 @@ static void test_rpc_create_simple_message( const char* str, uint32_t command_id) { furi_assert(message); - furi_assert(str); - char* str_copy = furi_alloc(strlen(str) + 1); - strcpy(str_copy, str); + char* str_copy = NULL; + if(str) { + str_copy = furi_alloc(strlen(str) + 1); + strcpy(str_copy, str); + } message->command_id = command_id; message->command_status = PB_CommandStatus_OK; message->cb_content.funcs.encode = NULL; message->which_content = tag; message->has_next = false; switch(tag) { + case PB_Main_storage_stat_request_tag: + message->content.storage_stat_request.path = str_copy; + break; case PB_Main_storage_list_request_tag: message->content.storage_list_request.path = str_copy; break; @@ -292,7 +306,7 @@ 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_feed_bytes(session, buffer_ptr, bytes_left, 1000); + size_t bytes_sent = rpc_session_feed(session, buffer_ptr, bytes_left, 1000); mu_check(bytes_sent > 0); bytes_left -= bytes_sent; @@ -358,6 +372,19 @@ static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected) { mu_check(result_locked == expected_locked); break; } + case PB_Main_storage_stat_response_tag: { + bool result_has_msg_file = result->content.storage_stat_response.has_file; + bool expected_has_msg_file = expected->content.storage_stat_response.has_file; + mu_check(result_has_msg_file == expected_has_msg_file); + + if(result_has_msg_file) { + PB_Storage_File* result_msg_file = &result->content.storage_stat_response.file; + PB_Storage_File* expected_msg_file = &expected->content.storage_stat_response.file; + test_rpc_compare_file(result_msg_file, expected_msg_file); + } else { + mu_check(0); + } + } break; case PB_Main_storage_read_response_tag: { bool result_has_msg_file = result->content.storage_read_response.has_file; bool expected_has_msg_file = expected->content.storage_read_response.has_file; @@ -402,6 +429,38 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_ return (count == bytes_received); } +static void + test_rpc_storage_list_create_expected_list_root(MsgList_t msg_list, uint32_t command_id) { + PB_Main* message = MsgList_push_new(msg_list); + message->has_next = false; + message->cb_content.funcs.encode = NULL; + message->command_id = command_id; + message->which_content = PB_Main_storage_list_response_tag; + + message->content.storage_list_response.file_count = 3; + message->content.storage_list_response.file[0].data = NULL; + message->content.storage_list_response.file[1].data = NULL; + message->content.storage_list_response.file[2].data = NULL; + + message->content.storage_list_response.file[0].size = 0; + message->content.storage_list_response.file[1].size = 0; + message->content.storage_list_response.file[2].size = 0; + + message->content.storage_list_response.file[0].type = PB_Storage_File_FileType_DIR; + message->content.storage_list_response.file[1].type = PB_Storage_File_FileType_DIR; + message->content.storage_list_response.file[2].type = PB_Storage_File_FileType_DIR; + + char* str = furi_alloc(4); + strcpy(str, "any"); + message->content.storage_list_response.file[0].name = str; + str = furi_alloc(4); + strcpy(str, "int"); + message->content.storage_list_response.file[1].name = str; + str = furi_alloc(4); + strcpy(str, "ext"); + message->content.storage_list_response.file[2].name = str; +} + static void test_rpc_storage_list_create_expected_list( MsgList_t msg_list, const char* path, @@ -412,11 +471,10 @@ static void test_rpc_storage_list_create_expected_list( PB_Main response = { .command_id = command_id, .has_next = false, - .which_content = PB_Main_storage_list_request_tag, + .which_content = PB_Main_storage_list_response_tag, /* other fields (e.g. msg_files ptrs) explicitly initialized by 0 */ }; PB_Storage_ListResponse* list = &response.content.storage_list_response; - response.which_content = PB_Main_storage_list_response_tag; bool finish = false; int i = 0; @@ -505,7 +563,11 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { MsgList_init(expected_msg_list); test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id); - test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id); + if(!strcmp(path, "/")) { + test_rpc_storage_list_create_expected_list_root(expected_msg_list, 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); @@ -514,6 +576,7 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { } MU_TEST(test_storage_list) { + test_rpc_storage_list_run("/", ++command_id); test_rpc_storage_list_run("/ext/nfc", ++command_id); test_rpc_storage_list_run("/int", ++command_id); @@ -597,12 +660,22 @@ static void test_storage_read_run(const char* path, uint32_t command_id) { test_rpc_free_msg_list(expected_msg_list); } +static bool test_is_exists(const char* path) { + Storage* fs_api = furi_record_open("storage"); + FileInfo fileinfo; + FS_Error result = storage_common_stat(fs_api, path, &fileinfo); + furi_check((result == FSE_OK) || (result == FSE_NOT_EXIST)); + furi_record_close("storage"); + return result == FSE_OK; +} + static void test_create_dir(const char* path) { Storage* fs_api = furi_record_open("storage"); FS_Error error = storage_common_mkdir(fs_api, path); (void)error; furi_assert((error == FSE_OK) || (error == FSE_EXIST)); furi_record_close("storage"); + furi_check(test_is_exists(path)); } static void test_create_file(const char* path, size_t size) { @@ -625,6 +698,60 @@ static void test_create_file(const char* path, size_t size) { storage_file_free(file); furi_record_close("storage"); + furi_check(test_is_exists(path)); +} + +static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) { + PB_Main request; + MsgList_t expected_msg_list; + MsgList_init(expected_msg_list); + + test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id); + + Storage* fs_api = furi_record_open("storage"); + FileInfo fileinfo; + FS_Error error = storage_common_stat(fs_api, path, &fileinfo); + furi_record_close("storage"); + + PB_Main* response = MsgList_push_new(expected_msg_list); + response->command_id = command_id; + response->command_status = rpc_system_storage_get_error(error); + response->has_next = false; + response->which_content = PB_Main_empty_tag; + + if(error == FSE_OK) { + response->which_content = PB_Main_storage_stat_response_tag; + response->content.storage_stat_response.has_file = true; + response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ? + PB_Storage_File_FileType_DIR : + PB_Storage_File_FileType_FILE; + response->content.storage_stat_response.file.size = fileinfo.size; + } + + test_rpc_encode_and_feed_one(&request); + test_rpc_decode_and_compare(expected_msg_list); + + pb_release(&PB_Main_msg, &request); + test_rpc_free_msg_list(expected_msg_list); +} + +#define TEST_DIR_STAT_NAME TEST_DIR "stat_dir" +#define TEST_DIR_STAT TEST_DIR_STAT_NAME "/" +MU_TEST(test_storage_stat) { + test_create_dir(TEST_DIR_STAT_NAME); + test_create_file(TEST_DIR_STAT "empty.txt", 0); + test_create_file(TEST_DIR_STAT "l33t.txt", 1337); + + test_rpc_storage_stat_run("/", ++command_id); + test_rpc_storage_stat_run("/int", ++command_id); + test_rpc_storage_stat_run("/ext", ++command_id); + + test_rpc_storage_stat_run(TEST_DIR_STAT "empty.txt", ++command_id); + test_rpc_storage_stat_run(TEST_DIR_STAT "l33t.txt", ++command_id); + test_rpc_storage_stat_run(TEST_DIR_STAT "missing", ++command_id); + test_rpc_storage_stat_run(TEST_DIR_STAT_NAME, ++command_id); + + test_rpc_storage_stat_run(TEST_DIR_STAT, ++command_id); } MU_TEST(test_storage_read) { @@ -829,12 +956,17 @@ MU_TEST(test_storage_interrupt_continuous_another_system) { test_rpc_free_msg_list(expected_msg_list); } -static void test_storage_delete_run(const char* path, size_t command_id, PB_CommandStatus status) { +static void test_storage_delete_run( + const char* path, + size_t command_id, + PB_CommandStatus status, + bool recursive) { PB_Main request; MsgList_t expected_msg_list; MsgList_init(expected_msg_list); test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id); + 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); @@ -844,16 +976,69 @@ static void test_storage_delete_run(const char* path, size_t command_id, PB_Comm test_rpc_free_msg_list(expected_msg_list); } -MU_TEST(test_storage_delete) { - test_create_file(TEST_DIR "empty.txt", 0); - test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK); - test_storage_delete_run( - TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST); +#define TEST_DIR_RMRF_NAME TEST_DIR "rmrf_test" +#define TEST_DIR_RMRF TEST_DIR_RMRF_NAME "/" +MU_TEST(test_storage_delete_recursive) { + test_create_dir(TEST_DIR_RMRF_NAME); + + test_create_dir(TEST_DIR_RMRF "dir1"); + test_create_file(TEST_DIR_RMRF "dir1/file1", 1); + + test_create_dir(TEST_DIR_RMRF "dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir2"); + test_create_file(TEST_DIR_RMRF "dir1/dir2/file1", 1); + test_create_file(TEST_DIR_RMRF "dir1/dir2/file2", 1); + test_create_dir(TEST_DIR_RMRF "dir1/dir3"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1/dir1"); + + test_create_dir(TEST_DIR_RMRF "dir2"); + test_create_dir(TEST_DIR_RMRF "dir2/dir1"); + test_create_dir(TEST_DIR_RMRF "dir2/dir2"); + test_create_file(TEST_DIR_RMRF "dir2/dir2/file1", 1); + + test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1"); + test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1"); + test_create_file(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1/file1", 1); - test_create_dir(TEST_DIR "dir1"); - test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK); test_storage_delete_run( - TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST); + TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY, false); + mu_check(test_is_exists(TEST_DIR_RMRF_NAME)); + test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true); + mu_check(!test_is_exists(TEST_DIR_RMRF_NAME)); + test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR_RMRF_NAME)); + + test_create_dir(TEST_DIR_RMRF_NAME); + test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true); + mu_check(!test_is_exists(TEST_DIR_RMRF_NAME)); + + test_create_dir(TEST_DIR "file1"); + test_storage_delete_run(TEST_DIR "file1", ++command_id, PB_CommandStatus_OK, true); + mu_check(!test_is_exists(TEST_DIR "file1")); +} + +MU_TEST(test_storage_delete) { + test_storage_delete_run(NULL, ++command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS, false); + + furi_check(!test_is_exists(TEST_DIR "empty.txt")); + test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "empty.txt")); + + test_create_file(TEST_DIR "empty.txt", 0); + test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "empty.txt")); + + furi_check(!test_is_exists(TEST_DIR "dir1")); + test_create_dir(TEST_DIR "dir1"); + test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "dir1")); + + test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "dir1")); } static void test_storage_mkdir_run(const char* path, size_t command_id, PB_CommandStatus status) { @@ -872,18 +1057,17 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma } MU_TEST(test_storage_mkdir) { + furi_check(!test_is_exists(TEST_DIR "dir1")); test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK); + mu_check(test_is_exists(TEST_DIR "dir1")); + test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST); + mu_check(test_is_exists(TEST_DIR "dir1")); + + furi_check(!test_is_exists(TEST_DIR "dir2")); test_create_dir(TEST_DIR "dir2"); test_storage_mkdir_run(TEST_DIR "dir2", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST); - - Storage* fs_api = furi_record_open("storage"); - FS_Error error = storage_common_remove(fs_api, TEST_DIR "dir1"); - (void)error; - furi_assert(error == FSE_OK); - furi_record_close("storage"); - - test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK); + mu_check(test_is_exists(TEST_DIR "dir2")); } static void test_storage_calculate_md5sum(const char* path, char* md5sum) { @@ -1013,7 +1197,7 @@ MU_TEST(test_ping) { // 4) test for fill buffer till end (great varint) and close connection MU_TEST_SUITE(test_rpc_status) { - MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown); + MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown); MU_RUN_TEST(test_ping); } @@ -1021,11 +1205,13 @@ MU_TEST_SUITE(test_rpc_status) { MU_TEST_SUITE(test_rpc_storage) { MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown); + MU_RUN_TEST(test_storage_stat); MU_RUN_TEST(test_storage_list); MU_RUN_TEST(test_storage_read); MU_RUN_TEST(test_storage_write_read); MU_RUN_TEST(test_storage_write); MU_RUN_TEST(test_storage_delete); + MU_RUN_TEST(test_storage_delete_recursive); MU_RUN_TEST(test_storage_mkdir); MU_RUN_TEST(test_storage_md5sum); MU_RUN_TEST(test_storage_interrupt_continuous_same_system); @@ -1040,23 +1226,23 @@ static void test_app_create_request( request->command_id = command_id; request->command_status = PB_CommandStatus_OK; request->cb_content.funcs.encode = NULL; - request->which_content = PB_Main_app_start_tag; + request->which_content = PB_Main_app_start_request_tag; request->has_next = false; if(app_name) { char* msg_app_name = furi_alloc(strlen(app_name) + 1); strcpy(msg_app_name, app_name); - request->content.app_start.name = msg_app_name; + request->content.app_start_request.name = msg_app_name; } else { - request->content.app_start.name = NULL; + request->content.app_start_request.name = NULL; } if(app_args) { char* msg_app_args = furi_alloc(strlen(app_args) + 1); strcpy(msg_app_args, app_args); - request->content.app_start.args = msg_app_args; + request->content.app_start_request.args = msg_app_args; } else { - request->content.app_start.args = NULL; + request->content.app_start_request.args = NULL; } } @@ -1112,20 +1298,19 @@ MU_TEST(test_app_start_and_lock_status) { "skynet_destroy_world_app", NULL, PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id); test_app_get_status_lock_run(false, ++command_id); - test_app_start_run("Delay Test App", "0", PB_CommandStatus_OK, ++command_id); + test_app_start_run("Delay Test", "0", PB_CommandStatus_OK, ++command_id); delay(100); test_app_get_status_lock_run(false, ++command_id); - test_app_start_run("Delay Test App", "200", PB_CommandStatus_OK, ++command_id); + test_app_start_run("Delay Test", "200", PB_CommandStatus_OK, ++command_id); test_app_get_status_lock_run(true, ++command_id); delay(100); test_app_get_status_lock_run(true, ++command_id); - test_app_start_run( - "Delay Test App", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id); + test_app_start_run("Delay Test", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id); delay(200); test_app_get_status_lock_run(false, ++command_id); - test_app_start_run("Delay Test App", "500", PB_CommandStatus_OK, ++command_id); + test_app_start_run("Delay Test", "500", PB_CommandStatus_OK, ++command_id); delay(100); test_app_get_status_lock_run(true, ++command_id); test_app_start_run("Infrared", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id); @@ -1140,16 +1325,22 @@ MU_TEST(test_app_start_and_lock_status) { } MU_TEST_SUITE(test_rpc_app) { - MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown); + MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown); MU_RUN_TEST(test_app_start_and_lock_status); } int run_minunit_test_rpc() { - MU_RUN_SUITE(test_rpc_storage); + Storage* storage = furi_record_open("storage"); + furi_record_close("storage"); + if(storage_sd_status(storage) != FSE_OK) { + FURI_LOG_E(TAG, "SD card not mounted - skip storage tests"); + } else { + MU_RUN_SUITE(test_rpc_storage); + } + MU_RUN_SUITE(test_rpc_status); MU_RUN_SUITE(test_rpc_app); - MU_REPORT(); return MU_EXIT_CODE; } diff --git a/applications/tests/test_index.c b/applications/tests/test_index.c index 7158b304..f5607566 100644 --- a/applications/tests/test_index.c +++ b/applications/tests/test_index.c @@ -1,4 +1,5 @@ #include "m-string.h" + #include #include #include @@ -7,9 +8,27 @@ #include #include +#define TAG "UnitTests" + int run_minunit(); int run_minunit_test_irda_decoder_encoder(); int run_minunit_test_rpc(); +int run_minunit_test_flipper_file(); + +void minunit_print_progress(void) { + static char progress[] = {'\\', '|', '/', '-'}; + static uint8_t progress_counter = 0; + static TickType_t last_tick = 0; + TickType_t current_tick = xTaskGetTickCount(); + if(current_tick - last_tick > 20) { + last_tick = current_tick; + printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); + } +} + +void minunit_print_fail(const char* str) { + printf("%s\n", str); +} void unit_tests_cli(Cli* cli, string_t args, void* context) { uint32_t test_result = 0; @@ -19,33 +38,50 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { minunit_status = 0; Loader* loader = furi_record_open("loader"); - furi_record_close("loader"); - NotificationApp* notification = furi_record_open("notification"); - furi_record_close("notification"); + // TODO: lock device while test running if(loader_is_locked(loader)) { - FURI_LOG_E("UNIT_TESTS", "RPC: stop all applications to run tests"); + FURI_LOG_E(TAG, "RPC: stop all applications to run tests"); notification_message(notification, &sequence_blink_magenta_100); } else { notification_message_block(notification, &sequence_set_only_blue_255); + uint32_t heap_before = memmgr_get_free_heap(); + uint32_t cycle_counter = DWT->CYCCNT; + test_result |= run_minunit(); test_result |= run_minunit_test_irda_decoder_encoder(); test_result |= run_minunit_test_rpc(); + test_result |= run_minunit_test_flipper_file(); + cycle_counter = (DWT->CYCCNT - cycle_counter); + + FURI_LOG_I(TAG, "Consumed: %0.2fs", (float)cycle_counter / (SystemCoreClock)); if(test_result == 0) { + delay(200); /* wait for tested services and apps to deallocate */ + uint32_t heap_after = memmgr_get_free_heap(); notification_message(notification, &sequence_success); - FURI_LOG_I("UNIT_TESTS", "PASSED"); + if(heap_after != heap_before) { + FURI_LOG_E(TAG, "Leaked: %d", heap_before - heap_after); + } else { + FURI_LOG_I(TAG, "No leaks"); + } + FURI_LOG_I(TAG, "PASSED"); } else { notification_message(notification, &sequence_error); - FURI_LOG_E("UNIT_TESTS", "FAILED"); + FURI_LOG_E(TAG, "FAILED"); } } + + furi_record_close("notification"); + furi_record_close("loader"); } void unit_tests_cli_init() { Cli* cli = furi_record_open("cli"); + + // We need to launch apps from tests, so we cannot lock loader cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); furi_record_close("cli"); } diff --git a/assets/compiled/application.pb.c b/assets/compiled/application.pb.c index 097a57b0..a50850b2 100644 --- a/assets/compiled/application.pb.c +++ b/assets/compiled/application.pb.c @@ -6,7 +6,7 @@ #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(PB_App_Start, PB_App_Start, AUTO) +PB_BIND(PB_App_StartRequest, PB_App_StartRequest, AUTO) PB_BIND(PB_App_LockStatusRequest, PB_App_LockStatusRequest, AUTO) diff --git a/assets/compiled/application.pb.h b/assets/compiled/application.pb.h index b7a053f3..4b05c46b 100644 --- a/assets/compiled/application.pb.h +++ b/assets/compiled/application.pb.h @@ -14,10 +14,10 @@ typedef struct _PB_App_LockStatusRequest { char dummy_field; } PB_App_LockStatusRequest; -typedef struct _PB_App_Start { +typedef struct _PB_App_StartRequest { char *name; char *args; -} PB_App_Start; +} PB_App_StartRequest; typedef struct _PB_App_LockStatusResponse { bool locked; @@ -29,24 +29,24 @@ extern "C" { #endif /* Initializer values for message structs */ -#define PB_App_Start_init_default {NULL, NULL} +#define PB_App_StartRequest_init_default {NULL, NULL} #define PB_App_LockStatusRequest_init_default {0} #define PB_App_LockStatusResponse_init_default {0} -#define PB_App_Start_init_zero {NULL, NULL} +#define PB_App_StartRequest_init_zero {NULL, NULL} #define PB_App_LockStatusRequest_init_zero {0} #define PB_App_LockStatusResponse_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ -#define PB_App_Start_name_tag 1 -#define PB_App_Start_args_tag 2 +#define PB_App_StartRequest_name_tag 1 +#define PB_App_StartRequest_args_tag 2 #define PB_App_LockStatusResponse_locked_tag 1 /* Struct field encoding specification for nanopb */ -#define PB_App_Start_FIELDLIST(X, a) \ +#define PB_App_StartRequest_FIELDLIST(X, a) \ X(a, POINTER, SINGULAR, STRING, name, 1) \ X(a, POINTER, SINGULAR, STRING, args, 2) -#define PB_App_Start_CALLBACK NULL -#define PB_App_Start_DEFAULT NULL +#define PB_App_StartRequest_CALLBACK NULL +#define PB_App_StartRequest_DEFAULT NULL #define PB_App_LockStatusRequest_FIELDLIST(X, a) \ @@ -58,17 +58,17 @@ X(a, STATIC, SINGULAR, BOOL, locked, 1) #define PB_App_LockStatusResponse_CALLBACK NULL #define PB_App_LockStatusResponse_DEFAULT NULL -extern const pb_msgdesc_t PB_App_Start_msg; +extern const pb_msgdesc_t PB_App_StartRequest_msg; extern const pb_msgdesc_t PB_App_LockStatusRequest_msg; extern const pb_msgdesc_t PB_App_LockStatusResponse_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define PB_App_Start_fields &PB_App_Start_msg +#define PB_App_StartRequest_fields &PB_App_StartRequest_msg #define PB_App_LockStatusRequest_fields &PB_App_LockStatusRequest_msg #define PB_App_LockStatusResponse_fields &PB_App_LockStatusResponse_msg /* Maximum encoded size of messages (where known) */ -/* PB_App_Start_size depends on runtime parameters */ +/* PB_App_StartRequest_size depends on runtime parameters */ #define PB_App_LockStatusRequest_size 0 #define PB_App_LockStatusResponse_size 2 diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index a94686f4..a9fe403d 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -2,12 +2,12 @@ #include -const uint8_t _I_Certification1_103x23_0[] = {0x01,0x00,0x98,0x00,0x9f,0xff,0xbe,0x30,0x38,0x04,0xf2,0x01,0xe0,0x80,0x82,0x87,0xf9,0x01,0x06,0x24,0xfe,0x01,0xf8,0x80,0xfe,0x21,0xff,0xf8,0x3c,0xff,0x9c,0x0c,0x1e,0x00,0x30,0x7f,0xc0,0xc1,0xe3,0xc0,0xe3,0xd0,0x7e,0x75,0xc4,0x46,0x30,0x70,0xd9,0x46,0x3c,0x10,0x09,0xc0,0x30,0xfe,0x10,0x1c,0x04,0x3c,0x18,0x37,0x08,0x05,0xc0,0x18,0x77,0x88,0x07,0x00,0x6e,0x31,0x89,0x87,0xe2,0x00,0x0c,0x39,0xc0,0x30,0x49,0x83,0x18,0x8c,0x7f,0xa0,0x60,0xc3,0x2c,0xa0,0x30,0x60,0xe0,0x01,0x06,0x14,0x70,0x18,0x26,0x51,0x8c,0x43,0x20,0x70,0x20,0x64,0xe3,0x03,0xa2,0x74,0x10,0x62,0x5f,0xce,0xc3,0x8f,0x06,0x78,0x31,0xc4,0xc6,0x33,0xc2,0x6f,0x99,0xf5,0x03,0x89,0xb7,0xb0,0x2d,0x7d,0x9f,0x2e,0x98,0x8c,0x0a,0x86,0x3c,0x0c,0x30,0xb9,0x7e,0x20,0x30,0x88,0x07,0xfe,0x0e,0x0c,0x42,0xda,0x40,0x3f,0x90,0x10,}; -const uint8_t *_I_Certification1_103x23[] = {_I_Certification1_103x23_0}; - const uint8_t _I_Certification2_119x30_0[] = {0x01,0x00,0x3c,0x01,0x00,0x5c,0x06,0x01,0x40,0x07,0x5e,0x0b,0xff,0x20,0x07,0x5d,0x92,0x01,0x13,0x03,0xa4,0x70,0x06,0x5f,0xe0,0x10,0xc6,0x20,0x10,0xc8,0x1c,0xce,0x70,0x07,0x19,0xf0,0x08,0x70,0x10,0x18,0x1c,0x03,0xe1,0xff,0x83,0x83,0x84,0x34,0x57,0xf0,0x10,0xd8,0x03,0x23,0x00,0x1c,0x8c,0x08,0x1c,0x30,0xf0,0xc8,0xf8,0xc1,0xc3,0x10,0x00,0x90,0x48,0x60,0x70,0x3d,0x98,0x90,0x70,0x1c,0x10,0x70,0xc2,0x03,0x65,0xa0,0xc0,0x07,0x47,0xe6,0x6d,0x1e,0x07,0x04,0x06,0x20,0xe3,0x90,0x5f,0x41,0xc9,0xe0,0xc0,0x08,0x46,0x09,0x18,0x41,0x0c,0x82,0x44,0x0e,0x11,0x61,0x5c,0x27,0xd0,0x70,0x70,0xc5,0xc1,0xc3,0x40,0x8a,0x17,0x84,0x94,0x53,0x0a,0x0c,0x1a,0x01,0xe2,0x88,0x7e,0x01,0xc3,0x08,0x80,0xff,0xcd,0x05,0xb8,0x80,0x43,0xa0,0x11,0xe8,0x80,0x84,0x43,0xa5,0xfe,0x98,0xa1,0x86,0xb9,0x00,0x8e,0x9c,0x47,0xe0,0x5d,0x1a,0x04,0x88,0x8e,0x20,0x02,0x97,0x60,0x27,0x40,0xe1,0x03,0x95,0x02,0x82,0x0e,0x49,0x35,0x0a,0x65,0x00,0xe1,0x28,0x04,0xfe,0x38,0x05,0xc8,0xf8,0xe3,0xf0,0x09,0x3c,0x8a,0xe4,0x0e,0x4e,0x02,0xe0,0x7f,0xff,0x39,0xfe,0x02,0x47,0x14,0xf1,0x0b,0x13,0x41,0xc0,0x52,0x0c,0xc2,0x61,0xc0,0x90,0xc3,0x38,0x50,0x1e,0x27,0xfe,0x8f,0x00,0xc8,0x48,0x20,0xc0,0xe3,0x40,0x0e,0x04,0x1c,0x98,0x8d,0x04,0x28,0x1c,0x4a,0x31,0x00,0x0c,0x0e,0x11,0x38,0x59,0x8c,0x12,0x7f,0x12,0x81,0xfc,0x27,0xf7,0x08,0x05,0x06,0x01,0x07,0x07,0x1c,0x08,0x6c,0x20,0xe2,0x98,0x40,0x27,0xd0,0x08,0x34,0x42,0x70,0xef,0x13,0xa8,0xd0,0x05,0x84,0x1d,0x10,0x00,0xc1,0xdf,0x43,0x0c,0x41,0x1a,0xcc,0x47,0x63,0xff,0x00,0x0c,0x0c,0xe4,0x2d,0x91,0x00,0x17,0xf8,0x1c,0x3c,0x00,0x71,0x09,0xc7,0xfc,0x0d,0x30,0x06,0x7f,0x3f,0xea,0x11,0x07,0x78,0x3b,0xc1,0xd6,}; const uint8_t *_I_Certification2_119x30[] = {_I_Certification2_119x30_0}; +const uint8_t _I_Certification1_103x23_0[] = {0x01,0x00,0x98,0x00,0x9f,0xff,0xbe,0x30,0x38,0x04,0xf2,0x01,0xe0,0x80,0x82,0x87,0xf9,0x01,0x06,0x24,0xfe,0x01,0xf8,0x80,0xfe,0x21,0xff,0xf8,0x3c,0xff,0x9c,0x0c,0x1e,0x00,0x30,0x7f,0xc0,0xc1,0xe3,0xc0,0xe3,0xd0,0x7e,0x75,0xc4,0x46,0x30,0x70,0xd9,0x46,0x3c,0x10,0x09,0xc0,0x30,0xfe,0x10,0x1c,0x04,0x3c,0x18,0x37,0x08,0x05,0xc0,0x18,0x77,0x88,0x07,0x00,0x6e,0x31,0x89,0x87,0xe2,0x00,0x0c,0x39,0xc0,0x30,0x49,0x83,0x18,0x8c,0x7f,0xa0,0x60,0xc3,0x2c,0xa0,0x30,0x60,0xe0,0x01,0x06,0x14,0x70,0x18,0x26,0x51,0x8c,0x43,0x20,0x70,0x20,0x64,0xe3,0x03,0xa2,0x74,0x10,0x62,0x5f,0xce,0xc3,0x8f,0x06,0x78,0x31,0xc4,0xc6,0x33,0xc2,0x6f,0x99,0xf5,0x03,0x89,0xb7,0xb0,0x2d,0x7d,0x9f,0x2e,0x98,0x8c,0x0a,0x86,0x3c,0x0c,0x30,0xb9,0x7e,0x20,0x30,0x88,0x07,0xfe,0x0e,0x0c,0x42,0xda,0x40,0x3f,0x90,0x10,}; +const uint8_t *_I_Certification1_103x23[] = {_I_Certification1_103x23_0}; + const uint8_t _A_WatchingTV_128x64_0[] = {0x01,0x00,0x32,0x02,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x07,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x78,0x03,0xc0,0x1f,0x80,0x01,0x15,0x04,0x21,0x70,0x18,0x05,0x02,0x42,0x09,0x5f,0xfc,0x7c,0x0a,0x70,0x20,0x78,0xc4,0x41,0xc9,0xc0,0x80,0x58,0x01,0xf9,0x20,0x1d,0x98,0x00,0x60,0x80,0x81,0x84,0x83,0xd2,0x20,0x3b,0x30,0x00,0xc2,0x01,0xe3,0x06,0x07,0xa0,0x20,0x45,0x64,0x16,0x30,0x7b,0x0a,0x04,0x04,0x60,0xf3,0x85,0x03,0xd0,0x38,0x42,0x22,0x23,0x14,0x80,0x9e,0xa0,0xf2,0x21,0x11,0x74,0x60,0x1a,0x00,0x90,0xa2,0x14,0x1e,0x86,0x41,0xa8,0x04,0x84,0x1e,0xc1,0x83,0x07,0x8c,0xc0,0xee,0x80,0xf7,0x03,0x88,0x3c,0x6f,0xfe,0x00,0xb9,0x40,0xf0,0x10,0xf0,0x7a,0x40,0x86,0xa3,0x80,0xcf,0x83,0xcc,0x06,0x2b,0x04,0x82,0x46,0x1c,0xaa,0x0f,0x22,0x42,0x41,0x22,0x80,0x84,0x07,0x8f,0x82,0x7e,0x1f,0x48,0x44,0x90,0x1e,0x9c,0x08,0x0c,0x82,0xce,0x1f,0x48,0x84,0x88,0x06,0x33,0x01,0xd9,0xd8,0x34,0xfa,0x00,0x79,0xfe,0x28,0xe0,0x31,0x86,0x00,0x84,0x8a,0x3c,0x0a,0x9f,0x04,0x1e,0x70,0x6a,0xc0,0x0c,0x49,0x64,0x12,0x1c,0x06,0xbc,0x3e,0x90,0x11,0x48,0xfc,0x02,0xc6,0x02,0x1a,0x87,0x41,0x3e,0x94,0x0f,0xc4,0x3c,0x0e,0x42,0x22,0x64,0x8b,0x3d,0x30,0x0f,0x63,0x57,0x1c,0x00,0x3e,0x5f,0xff,0xfc,0x3c,0x1e,0x83,0x22,0x40,0x8e,0xa0,0x08,0x35,0x5a,0xaf,0xd4,0x24,0x21,0x31,0x80,0x6b,0x8e,0x25,0x04,0xea,0x01,0xc7,0x54,0x00,0x48,0x76,0x03,0xaa,0x0f,0x18,0xe4,0x02,0xf1,0x35,0x0f,0x90,0x00,0xe1,0xfc,0x0d,0x57,0xff,0xc2,0x51,0x18,0x65,0xa8,0x3e,0xbe,0xa8,0x55,0x83,0x03,0x01,0x8f,0x1d,0xc6,0x0d,0xd4,0x0f,0xad,0xd6,0x1a,0xf9,0x10,0xe8,0xbd,0xc4,0xa0,0x30,0x10,0xfa,0x6b,0xa1,0x40,0xf1,0x25,0x0c,0xbb,0x01,0x01,0xa8,0x40,0xc3,0xe9,0x57,0x9e,0xcf,0xb0,0x06,0x61,0x82,0xd0,0x20,0x3a,0x88,0x10,0xf9,0x35,0x5c,0xa9,0x0e,0x00,0x0c,0x30,0x24,0xf0,0x87,0xc4,0xf8,0x5f,0xfb,0xfd,0x54,0x7e,0x38,0x01,0x28,0xc0,0x53,0xc3,0xe8,0x83,0xe2,0x00,0x05,0xb8,0xd5,0x0f,0xc6,0x80,0x1e,0x13,0x58,0xbd,0x06,0x30,0x3e,0x40,0xf9,0x31,0x85,0x56,0x50,0x08,0x24,0x82,0x44,0x00,0x1d,0x16,0xcc,0x3e,0x34,0x00,0x79,0x60,0x11,0xa3,0x02,0x90,0x1f,0x4c,0x04,0x30,0xdc,0x00,0x3c,0xa8,0x17,0xd7,0x20,0xd0,0x07,0xc5,0x98,0x1f,0x94,0x02,0x42,0xfd,0x2a,0x3e,0x00,0x26,0x23,0xe4,0x0f,0x8c,0x02,0x7c,0xfd,0x2a,0x00,0x3c,0x8d,0xc5,0x23,0xd9,0x07,0xc8,0x00,0x56,0xa2,0x21,0x28,0x84,0x04,0x22,0x22,0x0f,0x88,0xf8,0xa6,0xa0,0xf6,0x3f,0x98,0x3c,0xb4,0x11,0xaa,0x03,0x14,0x43,0xf5,0x54,0x8a,0x83,0xce,0x63,0xfc,0xc7,0xc8,0x87,0xe8,0xc0,0x14,0x20,0xbc,0x47,0x01,0x49,0x81,0x64,0x03,0xeb,0x50,0x42,0x10,0x3d,0x47,0xe5,0x2a,0x0f,0x16,0xaa,0x1d,0x80,0x84,0x0b,0xc8,0x3e,0x95,0xd6,0x31,0x92,0x86,0x0e,0x4f,0x20,0x78,0x8f,0xcb,0xed,0xaa,0xf5,0x21,0xc4,0x1e,0x30,0x43,0xa0,0xc4,0x49,0xe2,0x1a,0x2c,0x2f,0x5e,0x3e,0x41,0x18,0xf0,0x3c,0xe5,0x27,0xf4,0x83,0x5b,0x0e,0x04,0x6d,0x10,0x78,0xc8,0x01,0xe4,0x1f,0x28,0x2c,0xe1,0x40,0x60,0xf3,0x8a,0x83,0xc4,0x7e,0x50,0x63,0x48,0xa0,0x48,0x1e,0x70,0xb0,0xfa,0x83,0xce,0x01,0x03,0x07,0x8c,0x40,}; const uint8_t _A_WatchingTV_128x64_1[] = {0x01,0x00,0x4a,0x02,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x07,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x78,0x03,0xc0,0x1f,0x80,0x01,0x0f,0x02,0x80,0x10,0xb8,0x0c,0x03,0x41,0x20,0x04,0xaf,0xfe,0x3e,0x05,0x38,0x14,0x03,0xc1,0x10,0x07,0x27,0x02,0xab,0x75,0x07,0xae,0x80,0x1e,0xba,0x0d,0x56,0xa8,0x0c,0x70,0x48,0x05,0x42,0x12,0x0f,0x40,0xa8,0xd4,0x07,0x62,0x00,0x18,0x44,0x03,0x61,0x05,0x07,0xa0,0x20,0x74,0x02,0xb1,0x0b,0x20,0x18,0xc5,0x07,0x40,0x0c,0x18,0x3c,0x76,0x10,0x70,0x7a,0x05,0x47,0x01,0x0a,0x03,0x11,0xb0,0x47,0xec,0x25,0x28,0xa8,0x18,0x91,0x83,0xb0,0x2f,0xa9,0x30,0xa3,0x4a,0x04,0xac,0x23,0xd4,0x1e,0x53,0x40,0x7a,0x4a,0x38,0x1f,0xf0,0x7b,0x4a,0x00,0xe9,0x38,0x01,0x8e,0x0e,0x06,0x17,0x18,0x1e,0x02,0x1f,0x30,0x23,0xa4,0x60,0x06,0x3f,0xc2,0xe1,0x04,0x86,0x01,0x60,0x3b,0xa0,0x3c,0x8d,0x86,0x0f,0x2a,0x80,0x3d,0x22,0x83,0xc4,0x07,0x8f,0x83,0x7e,0x1f,0x48,0x44,0x90,0xa8,0x6d,0xe1,0x80,0xf8,0x2c,0xf5,0x16,0x3a,0x48,0x90,0x86,0x3b,0x2b,0x06,0x9f,0x40,0x0f,0x3f,0xc5,0x1c,0x06,0x25,0x82,0x22,0x8f,0x02,0xa7,0xd1,0x07,0x9c,0x1a,0xb0,0x03,0x18,0x60,0x08,0xf0,0x1a,0xf0,0xfa,0x40,0x6c,0x1f,0x0f,0xf9,0x6c,0xa0,0xc5,0xe2,0xe8,0x27,0xd2,0x80,0xf9,0x10,0x01,0x04,0x8a,0x01,0xa8,0x67,0xa6,0x01,0xf2,0x2a,0xe5,0x80,0x75,0x00,0x43,0xff,0xff,0xc3,0xc1,0xe9,0xf0,0x14,0x94,0x0f,0x54,0x04,0x1a,0xad,0x57,0xea,0x12,0x10,0x98,0xc0,0x35,0xc7,0x12,0x82,0x75,0x00,0xe3,0xaa,0x00,0x24,0x3b,0x01,0xd5,0x07,0x8c,0x72,0x01,0x7a,0x9a,0x87,0xc8,0x00,0x70,0xfe,0x06,0xab,0xff,0xe1,0x28,0x8e,0xb6,0xd4,0x1f,0x5f,0x54,0x2a,0xc1,0xd5,0x80,0xc7,0xac,0x18,0x0a,0xb0,0x94,0x43,0xc9,0x75,0x86,0xe8,0x35,0x41,0xd1,0xa8,0x50,0x30,0x7a,0xa8,0x08,0x7d,0x35,0xc1,0xf1,0xa0,0x12,0x86,0x5d,0xa0,0x80,0xd5,0x60,0x61,0xf4,0xab,0xcf,0x67,0xd8,0x03,0x30,0xc5,0x6a,0x01,0xd1,0x81,0x0f,0x93,0x55,0xca,0x90,0xe0,0x00,0xc3,0x0a,0x4f,0x08,0x7c,0x4f,0x85,0xff,0xbf,0xd5,0x61,0xb2,0x0c,0x00,0x94,0x60,0xa4,0xd1,0xf5,0x41,0xf1,0x00,0x02,0xdc,0x06,0x86,0x40,0x70,0x1d,0x56,0x09,0x3c,0x31,0xd8,0xc0,0xf9,0x03,0xe5,0x40,0x0f,0x08,0x08,0x5c,0x83,0x20,0x91,0x00,0x07,0x45,0xb3,0x0f,0x8d,0x01,0xac,0x30,0x0d,0x02,0x34,0x60,0x72,0x03,0xe9,0x80,0x86,0x1b,0x80,0x07,0x95,0x42,0xfa,0xe4,0x1a,0x00,0xf8,0xb3,0x03,0xf2,0x80,0x48,0x5f,0xa5,0x47,0xc0,0x3f,0x44,0x7c,0x81,0xf1,0x80,0x4f,0x81,0xe3,0xd5,0xa0,0x03,0xc8,0xdc,0x52,0x3d,0x90,0x7c,0x87,0xc6,0x44,0x2c,0x04,0x04,0x04,0x22,0x22,0x0f,0x88,0xf8,0xa6,0xa0,0xf6,0x3f,0x98,0x3c,0xb5,0x51,0xaa,0x04,0x80,0x3f,0x44,0x91,0x8a,0x83,0xce,0x63,0xfc,0xc7,0xc8,0x87,0xe9,0xa8,0x42,0x14,0x40,0x1e,0x34,0x98,0x16,0x40,0x3e,0x51,0xd3,0x41,0xa1,0x04,0x1e,0xa3,0xf2,0x00,0x24,0x3b,0x01,0x08,0x17,0x90,0x7d,0x2b,0xac,0x63,0x25,0x0c,0x1c,0x9e,0x40,0xf1,0x1f,0x97,0xdb,0x55,0xea,0x43,0x88,0x3c,0x60,0x83,0x61,0x88,0x93,0xc4,0x34,0x58,0x5e,0xbc,0x7c,0xa2,0x31,0xe0,0x79,0xca,0x4f,0xe9,0x06,0xb6,0x20,0x08,0xda,0x20,0xf1,0x90,0x03,0xc8,0x3e,0x50,0x59,0xc0,0x3c,0x93,0xa2,0x0f,0x28,0xa8,0x3c,0x47,0xe5,0x06,0x34,0x88,0x00,0x59,0xa2,0x0f,0x28,0x58,0x7d,0x41,0xe7,0x00,0x81,0x83,0xc6,0x20,}; const uint8_t _A_WatchingTV_128x64_2[] = {0x01,0x00,0x37,0x02,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x07,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x78,0x03,0xc0,0x1f,0x80,0x01,0x0c,0x62,0x80,0x10,0xb8,0x0c,0x02,0x29,0x20,0x04,0xaf,0xfe,0x3e,0x05,0x38,0x14,0x02,0x39,0x10,0x07,0x27,0x02,0x01,0x60,0x07,0xac,0x58,0x1e,0xa2,0x51,0x1d,0x90,0x00,0x60,0x90,0x09,0x54,0x20,0x1e,0x81,0x52,0x1d,0x88,0x00,0x41,0x83,0x36,0x09,0x08,0x00,0xc2,0xa4,0x2b,0x10,0xb1,0xd8,0x80,0xc6,0x28,0x30,0x11,0x83,0xcb,0x8d,0x03,0x07,0xa0,0x54,0x87,0x06,0x46,0x18,0x54,0x1c,0x1e,0xe5,0x83,0x46,0x0e,0x18,0x9e,0xa4,0xc2,0x07,0x99,0x90,0x69,0x40,0xf8,0x4c,0x18,0x3c,0x64,0x80,0xfc,0x03,0x8c,0xf3,0xe1,0x3f,0xc0,0x03,0x07,0x01,0x03,0xc0,0x43,0xc1,0xe9,0x02,0x8c,0x2a,0x86,0xc0,0x0f,0x30,0x50,0xac,0x12,0x09,0x38,0x01,0x8c,0x7c,0x1e,0xae,0x84,0x82,0x45,0x1e,0xa8,0x0f,0x1f,0x04,0xfc,0x3e,0x90,0x80,0x79,0x06,0x0b,0x81,0x01,0x90,0x59,0xc3,0xe9,0x10,0x91,0x16,0x10,0x36,0x36,0x0d,0x3e,0x80,0x1e,0x7f,0x89,0x38,0x0c,0x4a,0x42,0x02,0x2e,0x05,0x4f,0x82,0x0f,0x38,0x35,0x60,0x06,0x40,0x21,0x86,0x10,0x47,0x5e,0x1f,0x48,0x0d,0x83,0x01,0xff,0x45,0x90,0x48,0xaa,0x1d,0x04,0xfa,0x50,0x2f,0x01,0x63,0x72,0x18,0x7c,0xca,0x63,0x80,0x7b,0x1a,0xb8,0xe0,0x01,0xf2,0xff,0xff,0xe1,0xe0,0xf4,0xf8,0x20,0x4f,0x50,0x04,0x1a,0xad,0x57,0xea,0x12,0x10,0x98,0xc0,0x35,0xc7,0x12,0x82,0x75,0x00,0xe3,0xaa,0x00,0x24,0x3b,0x01,0xd5,0x07,0x8c,0x72,0x01,0x78,0x9a,0x87,0xc8,0x00,0x70,0xfe,0x06,0xab,0xff,0xe1,0x28,0x8c,0x32,0xd4,0x1f,0x5f,0x54,0x2a,0xc1,0x81,0x80,0xc7,0x94,0x23,0x06,0xea,0x07,0xd6,0xeb,0x0d,0x58,0x08,0x74,0x62,0x00,0x31,0xd4,0x40,0x43,0xe9,0xae,0x85,0x03,0xc4,0x94,0x32,0xec,0x04,0x06,0xa1,0x03,0x0f,0xa5,0x5e,0x7b,0x3e,0xc0,0x19,0x86,0x0b,0x40,0x80,0xea,0x20,0x43,0xe4,0xd5,0x72,0xa4,0x38,0x00,0x30,0xc0,0x93,0xc2,0x1f,0x13,0xe1,0x7f,0xef,0xf5,0x51,0xf8,0xe0,0x04,0xa3,0x01,0x4f,0x0f,0xa2,0x0f,0x88,0x00,0x16,0xe3,0x54,0x3f,0x1a,0x00,0x78,0x4d,0x62,0xf4,0x18,0xc0,0xf9,0x03,0xe4,0xc6,0x15,0x59,0x40,0x20,0x92,0x09,0x10,0x00,0x74,0x5b,0x30,0xf8,0xd0,0x01,0xe5,0x80,0x46,0x8c,0x0a,0x40,0x7d,0x30,0x10,0xc3,0x70,0x00,0xf2,0xa0,0x5f,0x5c,0x83,0x40,0x1f,0x16,0x60,0x7e,0x50,0x09,0x0b,0xf4,0xa8,0xf8,0x00,0x98,0x8f,0x90,0x3e,0x30,0x09,0xf3,0xf4,0xa8,0x00,0xf2,0x37,0x14,0x8f,0x64,0x1f,0x20,0x01,0x5a,0x88,0x84,0xa2,0x10,0x10,0x88,0x88,0x3e,0x23,0xe2,0x9a,0x83,0xd8,0xfe,0x60,0xf2,0xd0,0x46,0xa8,0x0c,0x51,0x0f,0xd5,0x52,0x2a,0x0f,0x39,0x8f,0xf3,0x1f,0x22,0x1f,0xa3,0x00,0x50,0x82,0xf1,0x1c,0x05,0x26,0x05,0x90,0x0f,0xad,0x41,0x08,0x40,0xf5,0x1f,0x94,0xa8,0x3c,0x5a,0xa8,0x76,0x02,0x10,0x2f,0x20,0xfa,0x57,0x58,0xc6,0x4a,0x18,0x39,0x3c,0x81,0xe2,0x3f,0x2f,0xb6,0xab,0xd4,0x87,0x10,0x78,0xc1,0x0e,0x83,0x11,0x27,0x88,0x68,0xb0,0xbd,0x78,0xf9,0x04,0x63,0xc0,0xf3,0x94,0x9f,0xd2,0x0d,0x6c,0x38,0x11,0xb4,0x41,0xe3,0x20,0x07,0x90,0x7c,0xa0,0xb3,0x85,0x01,0x83,0xce,0x2a,0x0f,0x11,0xf9,0x41,0x8d,0x22,0x81,0x20,0x79,0xc2,0xc3,0xea,0x0f,0x38,0x04,0x0c,0x1e,0x31,0x00,}; @@ -25,23 +25,20 @@ const uint8_t _A_Wink_128x64_7[] = {0x01,0x00,0x9a,0x01,0x00,0x78,0x03,0xc0,0x1e const uint8_t _A_Wink_128x64_8[] = {0x01,0x00,0x95,0x01,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x07,0x80,0x3c,0x01,0xe0,0x0f,0x00,0x78,0x03,0xc0,0x1e,0x00,0xf0,0x00,0xf8,0x3f,0xf1,0xf0,0x7e,0x4e,0x02,0x23,0x01,0x07,0xdc,0x1e,0x01,0xf0,0x87,0x03,0xab,0x81,0xff,0xdf,0xc7,0xae,0x00,0xfa,0x98,0x40,0x2a,0x90,0x7e,0xc0,0xc2,0xa3,0x10,0x0d,0x54,0x0f,0x1d,0x03,0x07,0xcc,0x12,0x01,0x55,0x81,0xc4,0xc8,0x16,0x1f,0x1e,0x0d,0x86,0x10,0x0f,0xbe,0xad,0xc3,0x08,0x34,0x10,0x03,0xe0,0x00,0x43,0xea,0x8c,0x42,0x22,0x3e,0x08,0x78,0x3d,0xa8,0x00,0x21,0xaa,0xe3,0x22,0x13,0x88,0xe5,0xe0,0x1e,0xd2,0x00,0x10,0xda,0xa0,0x92,0x19,0x64,0x1f,0x80,0x7e,0x7c,0x07,0xfe,0x7f,0x0c,0x81,0x79,0xa0,0x38,0x04,0x13,0x44,0x03,0x03,0x18,0x8c,0x61,0x24,0x6b,0x31,0x07,0xb4,0x22,0xc1,0xfe,0x99,0xcc,0x3c,0x11,0x08,0x07,0xe0,0x1e,0xd0,0x49,0x84,0x06,0xc9,0x20,0x9c,0x43,0x20,0x1f,0xe1,0xfb,0x40,0xb2,0x10,0x0e,0x41,0xb0,0x60,0x58,0x0b,0xf8,0x3d,0xa0,0x34,0x8f,0xe6,0x44,0x0a,0x5e,0x09,0xfa,0x41,0xe5,0x1f,0xed,0x1c,0x04,0xa6,0x3f,0x08,0xf8,0x3d,0xe4,0x9f,0xf9,0x3c,0x05,0xe6,0x3f,0xc4,0xfb,0xc0,0x22,0x9f,0xfa,0x3c,0x05,0x9c,0x3f,0xe8,0x38,0x3d,0xf2,0x9e,0x7a,0x7c,0x06,0x0c,0x94,0x18,0x18,0x3e,0xb0,0x24,0x01,0xff,0x9f,0x98,0x1e,0x5f,0xfa,0x7d,0xc6,0x01,0xe0,0xff,0xbc,0x20,0x1e,0x51,0xd2,0xf0,0x9f,0x9c,0x1e,0x84,0xb1,0xfc,0x1f,0xa3,0x01,0x96,0xff,0x86,0xcb,0xf8,0x7e,0x8a,0x04,0x97,0xff,0x41,0x02,0x8f,0xf8,0xfd,0x1a,0x09,0x55,0xf8,0x0a,0x5f,0xf3,0xf4,0x54,0x29,0xb1,0xe1,0xa1,0x1f,0xa7,0x51,0x9a,0x81,0x01,0x04,0xfc,0x58,0x01,0x0b,0x54,0x32,0xa8,0x92,0xf8,0x7f,0xca,0x83,0x0e,0x0f,0xb7,0xa8,0x08,0x5f,0x88,0x09,0x7c,0x61,0x21,0xf6,0xaa,0x81,0x0b,0xf9,0x00,0xb8,0x70,0x1a,0x62,0x1f,0x59,0x50,0x10,0xa7,0xcb,0x01,0x9c,0x83,0xda,0xa1,0x15,0x80,0x58,0x30,0x02,0xd1,0xc0,0x43,0xe0,0x81,0xf6,0x85,0x17,0x47,0xe0,0xad,0x1f,0x84,0x00,0x1e,0xd5,0x08,0x2a,0x34,0x80,0x02,0x21,0x13,0xb1,0x07,0xd8,0x00,0xa7,0x62,0x0f,0xbb,0x5d,0x17,0xee,0x1f,0x6a,0x02,0xc1,0xc0,0x0d,0x3c,0x07,0x6f,0x01,0xa1,0x00,0x05,0x98,0x03,0xb5,0x1c,0x20,0xfd,0xb8,0x13,0x79,0xd8,0xc0,0xff,0x1f,0x78,0x01,0xfe,0x10,0x70,0x7e,0xff,0x0f,0xbd,0xfe,0x07,0xc8,}; const uint8_t *_A_Wink_128x64[] = {_A_Wink_128x64_0,_A_Wink_128x64_1,_A_Wink_128x64_2,_A_Wink_128x64_3,_A_Wink_128x64_4,_A_Wink_128x64_5,_A_Wink_128x64_6,_A_Wink_128x64_7,_A_Wink_128x64_8}; -const uint8_t _I_dir_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0c,0xfe,0x01,0x41,0x80,0x7f,0xe0,0x70,0x18,0x10,0x05,0x7f,0xd0,0x10,0x88,0x80,}; -const uint8_t *_I_dir_10px[] = {_I_dir_10px_0}; - -const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; -const uint8_t *_I_Nfc_10px[] = {_I_Nfc_10px_0}; - const uint8_t _I_sub1_10px_0[] = {0x01,0x00,0x12,0x00,0x81,0x40,0x69,0x30,0x2c,0x2c,0x0b,0x6a,0x01,0x28,0x0c,0x0a,0x65,0x01,0x98,0x40,0x00,0x26,}; const uint8_t *_I_sub1_10px[] = {_I_sub1_10px_0}; const uint8_t _I_ir_10px_0[] = {0x00,0xFC,0x00,0x02,0x01,0x79,0x02,0x84,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x58,0x00,0x78,0x00,0xFF,0x03,}; const uint8_t *_I_ir_10px[] = {_I_ir_10px_0}; +const uint8_t _I_unknown_10px_0[] = {0x01,0x00,0x12,0x00,0xbc,0x40,0x39,0x90,0x0c,0x24,0x03,0x81,0x00,0xb0,0x40,0x26,0x00,0x12,0x00,0x08,0x14,0xc0,}; +const uint8_t *_I_unknown_10px[] = {_I_unknown_10px_0}; + const uint8_t _I_ibutt_10px_0[] = {0x00,0x80,0x03,0x40,0x02,0x20,0x02,0x10,0x01,0x8E,0x00,0x41,0x00,0x2D,0x00,0x2D,0x00,0x21,0x00,0x1E,0x00,}; const uint8_t *_I_ibutt_10px[] = {_I_ibutt_10px_0}; -const uint8_t _I_unknown_10px_0[] = {0x01,0x00,0x12,0x00,0xbc,0x40,0x39,0x90,0x0c,0x24,0x03,0x81,0x00,0xb0,0x40,0x26,0x00,0x12,0x00,0x08,0x14,0xc0,}; -const uint8_t *_I_unknown_10px[] = {_I_unknown_10px_0}; +const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; +const uint8_t *_I_Nfc_10px[] = {_I_Nfc_10px_0}; const uint8_t _I_ble_10px_0[] = {0x00,0x04,0x00,0x8C,0x00,0x15,0x01,0x56,0x02,0x8C,0x02,0x8C,0x02,0x56,0x02,0x15,0x01,0x8C,0x00,0x04,0x00,}; const uint8_t *_I_ble_10px[] = {_I_ble_10px_0}; @@ -49,41 +46,38 @@ const uint8_t *_I_ble_10px[] = {_I_ble_10px_0}; const uint8_t _I_125_10px_0[] = {0x00,0xE0,0x00,0x00,0x01,0x0E,0x02,0x31,0x02,0x45,0x02,0x91,0x00,0xAA,0x00,0x92,0x00,0x44,0x00,0x38,0x00,}; const uint8_t *_I_125_10px[] = {_I_125_10px_0}; +const uint8_t _I_dir_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0c,0xfe,0x01,0x41,0x80,0x7f,0xe0,0x70,0x18,0x10,0x05,0x7f,0xd0,0x10,0x88,0x80,}; +const uint8_t *_I_dir_10px[] = {_I_dir_10px_0}; + const uint8_t _I_BLE_Pairing_128x64_0[] = {0x01,0x00,0xb7,0x01,0x00,0x6c,0x38,0x1f,0xd0,0x10,0x76,0xe0,0x03,0xdd,0x40,0x07,0xf4,0x82,0x01,0x08,0x07,0xf4,0xc0,0x1f,0x91,0x08,0x07,0x00,0x1f,0xc0,0x0d,0x1e,0xe8,0x3f,0xc0,0x03,0x58,0x80,0xcf,0x11,0xd9,0xaf,0x85,0x77,0x01,0xf7,0x60,0xf8,0x45,0xff,0x05,0xed,0x9e,0x7c,0x09,0xdb,0xe0,0x2f,0x78,0x03,0x3c,0x8e,0xee,0x8a,0x43,0x81,0xfb,0x0c,0x66,0xe8,0xfc,0x59,0xba,0x6f,0x28,0x1b,0xfb,0xa3,0x80,0xfc,0xa0,0x1f,0xc6,0x86,0xbf,0xc3,0x78,0xce,0x04,0x19,0x26,0x77,0xfa,0x43,0xbe,0x12,0xa0,0x7e,0xf8,0x2a,0xa2,0x02,0xff,0x89,0x27,0x01,0xbf,0x99,0x38,0x8a,0xfc,0x0f,0x8e,0x07,0xfe,0x0e,0x94,0x2c,0x07,0xfc,0x7f,0x1f,0xf5,0x00,0xc3,0x00,0xe4,0x31,0x13,0xd1,0x00,0x0a,0xb8,0x19,0x25,0x91,0xc0,0x81,0xe2,0xb9,0x4d,0x5d,0x78,0x64,0x2e,0x84,0x80,0x61,0x07,0x02,0x3e,0x2a,0xa4,0xa2,0x00,0xf2,0x40,0x20,0xe3,0x21,0xa0,0x62,0x9f,0x60,0x05,0x02,0x3e,0x36,0x41,0x66,0x23,0x20,0x51,0xfc,0x40,0x68,0x0f,0x15,0x90,0x60,0x20,0x1b,0x09,0x89,0x70,0x46,0x42,0x07,0x14,0x99,0x41,0xe8,0x1f,0x18,0x0c,0x07,0xc1,0x19,0xff,0xc3,0xce,0x6b,0x54,0x8f,0xe0,0x3f,0x90,0x78,0x17,0x02,0x1a,0x70,0x39,0x01,0xa0,0xb1,0x53,0xb5,0x88,0xc7,0xe0,0x98,0x08,0x3a,0xd5,0xe8,0x97,0xd0,0x78,0xcf,0xe1,0x07,0xf1,0x0d,0x08,0x00,0x74,0x10,0x80,0x18,0xe8,0x97,0xc3,0xf2,0xff,0xc4,0x03,0xe3,0x04,0x8c,0x19,0xcc,0x00,0x35,0x0c,0x3c,0x03,0xf9,0x3f,0xb0,0x8f,0xc6,0x31,0x0e,0x0f,0x90,0x90,0xb5,0x45,0xc1,0xf8,0x4f,0xf0,0xde,0x18,0xcc,0x82,0x08,0x1f,0x22,0x20,0xd0,0x3a,0xab,0xd1,0xe0,0x5f,0xa1,0x1b,0x19,0x8d,0x02,0x04,0x9a,0x1d,0x04,0x28,0x26,0x36,0xa8,0x05,0xf0,0xe0,0x3f,0x04,0xf8,0xd0,0x30,0x55,0xfa,0xad,0x54,0x3e,0x35,0x09,0xab,0xac,0xbf,0x2b,0xf2,0x0a,0x0e,0xfb,0x55,0xaa,0x0f,0x94,0x68,0x04,0x30,0x6f,0xd3,0x7c,0xb0,0x15,0x0f,0xfd,0x7f,0xeb,0x05,0x4f,0x0b,0x60,0xa3,0x1f,0x28,0x0b,0xfc,0xbc,0x30,0x1f,0xf7,0xfe,0x54,0x2c,0x18,0x30,0x3c,0x6f,0x00,0xf2,0x1c,0x8c,0xf8,0x10,0x3c,0x00,0xf8,0xd5,0x5c,0x05,0xb8,0xb0,0xaa,0xdb,0x01,0x2b,0x31,0x0a,0xdc,0xa7,0x00,0xe6,0x00,0x0c,0x56,0x00,0x7e,0x10,0x00,0xcc,0x01,0xf0,0x1f,0x1b,0x40,0x2e,0x00,0x07,0x16,0x10,0x90,0x02,0xe5,0x90,0x06,0x29,0x00,0x2a,0xa9,0x00,0x2f,0x10,0x02,0xa5,0x10,0x02,0xf1,0x00,0x2a,0xa0,0x0d,0xc0,0x00,0xec,0x01,0xfd,0x60,0x17,0x6a,0xc0,0x60,0x40,0xfd,0xc0,0x30,0x04,0x01,0xb0,0xb0,0x7f,0x45,0x80,}; const uint8_t *_I_BLE_Pairing_128x64[] = {_I_BLE_Pairing_128x64_0}; -const uint8_t _I_ButtonRightSmall_3x5_0[] = {0x00,0x01,0x03,0x07,0x03,0x01,}; -const uint8_t *_I_ButtonRightSmall_3x5[] = {_I_ButtonRightSmall_3x5_0}; - -const uint8_t _I_ButtonLeft_4x7_0[] = {0x00,0x08,0x0C,0x0E,0x0F,0x0E,0x0C,0x08,}; -const uint8_t *_I_ButtonLeft_4x7[] = {_I_ButtonLeft_4x7_0}; - -const uint8_t _I_ButtonLeftSmall_3x5_0[] = {0x00,0x04,0x06,0x07,0x06,0x04,}; -const uint8_t *_I_ButtonLeftSmall_3x5[] = {_I_ButtonLeftSmall_3x5_0}; - -const uint8_t _I_DFU_128x50_0[] = {0x01,0x00,0x2e,0x02,0x00,0x57,0xfe,0x0e,0x0e,0xcf,0x84,0x02,0x70,0x0f,0xc8,0x74,0x03,0x80,0x0e,0xbc,0x7c,0x04,0x06,0x30,0x30,0x74,0xe0,0x2f,0xe0,0x42,0x82,0x03,0xe7,0x81,0xff,0x02,0x14,0x20,0x1f,0x3e,0x00,0x79,0xc4,0x01,0xfd,0x20,0x07,0xd5,0xd4,0xe2,0x53,0xf2,0x74,0xff,0xe1,0x40,0x41,0x87,0xd8,0x01,0xf1,0x60,0xf0,0x43,0xca,0x43,0xe0,0xa7,0x83,0xe2,0x30,0x01,0x29,0x84,0x7b,0x20,0x0f,0x88,0x30,0x3c,0xb1,0x90,0x1d,0x00,0xfa,0x30,0x3f,0xf8,0xcc,0x02,0xc6,0x31,0x1f,0x83,0x49,0xa8,0x16,0x0a,0xf4,0x7f,0x00,0x21,0x1f,0x04,0x38,0x06,0x20,0x04,0x90,0x46,0x35,0xf0,0xfa,0x00,0xcc,0x7f,0x10,0x14,0x0b,0x46,0x20,0xd5,0x70,0x50,0xb4,0x06,0xf1,0x00,0x9f,0x03,0xd7,0x09,0x81,0xd7,0xc0,0x8b,0x85,0x38,0xc0,0x50,0x41,0xeb,0x63,0xc0,0x07,0xc6,0x90,0xbf,0x2b,0x05,0x01,0xb8,0xb1,0x0c,0x06,0xae,0x01,0x24,0x6f,0x94,0x42,0x80,0xb2,0x49,0xc4,0x33,0x80,0x1f,0x18,0x93,0xfc,0xa1,0x14,0x0e,0x02,0x9c,0x43,0xc3,0x07,0x81,0xfc,0x03,0xe2,0xc0,0x28,0x14,0x10,0x5e,0x3f,0x03,0xc0,0xcf,0xf8,0x10,0x0f,0xe5,0x56,0x03,0x05,0xf0,0x40,0x20,0x20,0xf2,0x42,0x0d,0xfd,0x72,0x30,0x0f,0xf8,0x7c,0x41,0xe3,0x80,0x10,0x0d,0x00,0x5c,0x4a,0xd1,0x87,0xf8,0x39,0xf5,0x5c,0x0c,0x0b,0xe0,0x1c,0x10,0x78,0xfc,0x02,0x04,0x20,0x1f,0xf7,0x0f,0x57,0x80,0x81,0x5e,0x13,0x83,0x01,0x1f,0x97,0xff,0xfe,0x03,0x2e,0x07,0x57,0x03,0x01,0xbf,0x1d,0x45,0x70,0x27,0xe4,0xff,0x8c,0x07,0xf5,0x83,0xe0,0xcf,0xe1,0x00,0xf6,0x10,0x8c,0x07,0xb1,0x07,0xc1,0xfc,0x63,0xe5,0xd2,0x07,0x8f,0x80,0x1a,0x21,0xe1,0xc0,0x71,0xe0,0x20,0xf1,0x24,0x88,0x34,0x62,0x00,0xe3,0x3f,0x8d,0xfe,0x81,0x80,0xc1,0xf8,0x5b,0xe2,0x0f,0x18,0xc7,0xf0,0x1e,0x50,0x35,0xa0,0xc8,0x3f,0x98,0x30,0x70,0x87,0x44,0x1e,0x21,0xe3,0xf8,0x02,0x4b,0xaf,0x01,0x81,0xb3,0xca,0x01,0x1c,0x25,0x94,0x01,0x04,0x58,0x8d,0x5c,0x0b,0xc6,0x08,0x10,0x78,0xc3,0x3f,0xf0,0x72,0x88,0x98,0x8b,0x89,0x55,0x82,0xc7,0x9b,0xe5,0x00,0x87,0x26,0xc4,0x46,0x20,0xf2,0xd1,0x87,0xc6,0x0c,0xdf,0x21,0x50,0x8a,0xc7,0x00,0x38,0x2e,0x04,0x42,0xaf,0x05,0x06,0x0a,0xb8,0x70,0x0f,0x91,0x80,0x5c,0x03,0xc5,0x30,0x84,0x6a,0xe1,0x40,0xf1,0x7b,0x0f,0x00,0x7a,0x24,0x21,0x07,0x94,0x33,0x09,0x57,0x8a,0x93,0x85,0xec,0x3e,0x00,0x79,0x0b,0x88,0x06,0x3c,0x3f,0xfc,0xa8,0x1e,0x21,0x91,0x76,0x90,0x90,0x40,0x03,0xe0,0xe0,0x78,0x3f,0xd5,0x58,0x0e,0x08,0x32,0x3f,0x88,0xa8,0x90,0x8c,0x25,0x30,0xbc,0x7f,0xb5,0x50,0x1b,0xe0,0x20,0x7f,0x92,0x33,0x88,0x97,0x4a,0x07,0x0c,0x9e,0x5f,0xeb,0xaa,0xf2,0x74,0x8d,0x17,0x80,0x06,0x29,0xf1,0xe0,0x71,0xfb,0xfd,0x71,0xd8,0xff,0xf8,0x21,0x71,0x04,0x87,0x01,0xc1,0xa1,0xff,0x83,0xe7,0xf0,0xff,0xc1,0x51,0xe4,0xdd,0x1b,0x07,0xc2,0x63,0xf6,0x0f,0x9f,0xeb,0x5f,0x02,0x77,0x8a,0xc4,0xa3,0x17,0xc8,0x44,0x8c,0x34,0x20,0x71,0xfe,0x99,0x04,0x88,0x40,0x01,0xc3,0x47,0xf0,0x93,0x0f,0xf4,0x28,0x0e,0x3a,0xad,0x50,0x39,0x30,0x1f,0x18,0x3d,0x0e,0x31,0xff,0x3d,0x0c,0x02,0xa8,0x03,0x20,0x01,0x7e,0x3f,0xf8,0x09,0x06,0x33,0xfe,0x1b,0x50,}; -const uint8_t *_I_DFU_128x50[] = {_I_DFU_128x50_0}; - -const uint8_t _I_Warning_30x23_0[] = {0x01,0x00,0x47,0x00,0x80,0x70,0x00,0x65,0xe0,0x80,0x80,0xc7,0xe1,0x03,0x01,0xaf,0xe2,0x0e,0x03,0x19,0xe4,0x3c,0x06,0xb3,0xe8,0xf8,0x0c,0x67,0xf3,0xf0,0x1a,0x60,0x27,0xf7,0xf1,0x50,0xcf,0xff,0xe0,0x34,0xf0,0x00,0xc6,0x03,0xf0,0x01,0x8c,0x0c,0x06,0x7f,0x80,0x18,0xc1,0xff,0x9f,0xff,0xfc,0x3c,0x06,0x7f,0xe0,0x58,0xc7,0xff,0xe0,0x31,0x00,0x88,0x00,0x67,0xff,0xe0,0x18,0xc7,0xc0,}; -const uint8_t *_I_Warning_30x23[] = {_I_Warning_30x23_0}; - const uint8_t _I_ButtonDown_7x4_0[] = {0x00,0x7F,0x3E,0x1C,0x08,}; const uint8_t *_I_ButtonDown_7x4[] = {_I_ButtonDown_7x4_0}; -const uint8_t _I_ButtonRight_4x7_0[] = {0x00,0x01,0x03,0x07,0x0F,0x07,0x03,0x01,}; -const uint8_t *_I_ButtonRight_4x7[] = {_I_ButtonRight_4x7_0}; - const uint8_t _I_ButtonCenter_7x7_0[] = {0x00,0x1C,0x22,0x5D,0x5D,0x5D,0x22,0x1C,}; const uint8_t *_I_ButtonCenter_7x7[] = {_I_ButtonCenter_7x7_0}; +const uint8_t _I_ButtonLeft_4x7_0[] = {0x00,0x08,0x0C,0x0E,0x0F,0x0E,0x0C,0x08,}; +const uint8_t *_I_ButtonLeft_4x7[] = {_I_ButtonLeft_4x7_0}; + const uint8_t _I_ButtonUp_7x4_0[] = {0x00,0x08,0x1C,0x3E,0x7F,}; const uint8_t *_I_ButtonUp_7x4[] = {_I_ButtonUp_7x4_0}; -const uint8_t _I_DolphinOkay_41x43_0[] = {0x01,0x00,0xa0,0x00,0x00,0x0f,0x82,0x3e,0x05,0x38,0xf7,0x80,0x08,0x58,0x08,0x0c,0x02,0x0e,0x05,0x1b,0x00,0x08,0x63,0x00,0x21,0x88,0x00,0x86,0x40,0x02,0x18,0x40,0x08,0x68,0x00,0x21,0x82,0x06,0x88,0x0a,0xf0,0x21,0x39,0x09,0x84,0x02,0x20,0x57,0x09,0x98,0x15,0x67,0xc0,0x54,0xbe,0x81,0x4f,0x01,0xfe,0x02,0x9d,0x03,0xc4,0x20,0x10,0x29,0x7c,0x80,0xa9,0xfe,0x02,0xac,0x14,0x0a,0x77,0xc8,0x58,0x8c,0xf0,0x11,0x51,0x79,0xff,0x61,0x44,0x93,0x81,0x02,0xc4,0x9e,0x60,0xb2,0xf0,0xa0,0x46,0x0c,0x17,0x14,0x99,0x1a,0x07,0x80,0x59,0x49,0x82,0x21,0xc0,0xa4,0x82,0x24,0xb9,0x20,0x88,0x1c,0x47,0xc2,0x07,0x11,0x54,0xa0,0x60,0x53,0xb8,0x0a,0x4b,0xf3,0x03,0x87,0x81,0x4a,0x0d,0xfc,0x1a,0x98,0x68,0xb8,0x01,0x51,0x13,0x15,0xe0,0x82,0x7f,0x8d,0x78,0x38,0xbf,0xff,0xfa,0xb8,0x60,0xbf,0x1b,0xf9,0x50,0x14,0xea,0xe7,0x02,0x02,0x8e,0xac,0x94,0x40,}; -const uint8_t *_I_DolphinOkay_41x43[] = {_I_DolphinOkay_41x43_0}; +const uint8_t _I_DFU_128x50_0[] = {0x01,0x00,0x2e,0x02,0x00,0x57,0xfe,0x0e,0x0e,0xcf,0x84,0x02,0x70,0x0f,0xc8,0x74,0x03,0x80,0x0e,0xbc,0x7c,0x04,0x06,0x30,0x30,0x74,0xe0,0x2f,0xe0,0x42,0x82,0x03,0xe7,0x81,0xff,0x02,0x14,0x20,0x1f,0x3e,0x00,0x79,0xc4,0x01,0xfd,0x20,0x07,0xd5,0xd4,0xe2,0x53,0xf2,0x74,0xff,0xe1,0x40,0x41,0x87,0xd8,0x01,0xf1,0x60,0xf0,0x43,0xca,0x43,0xe0,0xa7,0x83,0xe2,0x30,0x01,0x29,0x84,0x7b,0x20,0x0f,0x88,0x30,0x3c,0xb1,0x90,0x1d,0x00,0xfa,0x30,0x3f,0xf8,0xcc,0x02,0xc6,0x31,0x1f,0x83,0x49,0xa8,0x16,0x0a,0xf4,0x7f,0x00,0x21,0x1f,0x04,0x38,0x06,0x20,0x04,0x90,0x46,0x35,0xf0,0xfa,0x00,0xcc,0x7f,0x10,0x14,0x0b,0x46,0x20,0xd5,0x70,0x50,0xb4,0x06,0xf1,0x00,0x9f,0x03,0xd7,0x09,0x81,0xd7,0xc0,0x8b,0x85,0x38,0xc0,0x50,0x41,0xeb,0x63,0xc0,0x07,0xc6,0x90,0xbf,0x2b,0x05,0x01,0xb8,0xb1,0x0c,0x06,0xae,0x01,0x24,0x6f,0x94,0x42,0x80,0xb2,0x49,0xc4,0x33,0x80,0x1f,0x18,0x93,0xfc,0xa1,0x14,0x0e,0x02,0x9c,0x43,0xc3,0x07,0x81,0xfc,0x03,0xe2,0xc0,0x28,0x14,0x10,0x5e,0x3f,0x03,0xc0,0xcf,0xf8,0x10,0x0f,0xe5,0x56,0x03,0x05,0xf0,0x40,0x20,0x20,0xf2,0x42,0x0d,0xfd,0x72,0x30,0x0f,0xf8,0x7c,0x41,0xe3,0x80,0x10,0x0d,0x00,0x5c,0x4a,0xd1,0x87,0xf8,0x39,0xf5,0x5c,0x0c,0x0b,0xe0,0x1c,0x10,0x78,0xfc,0x02,0x04,0x20,0x1f,0xf7,0x0f,0x57,0x80,0x81,0x5e,0x13,0x83,0x01,0x1f,0x97,0xff,0xfe,0x03,0x2e,0x07,0x57,0x03,0x01,0xbf,0x1d,0x45,0x70,0x27,0xe4,0xff,0x8c,0x07,0xf5,0x83,0xe0,0xcf,0xe1,0x00,0xf6,0x10,0x8c,0x07,0xb1,0x07,0xc1,0xfc,0x63,0xe5,0xd2,0x07,0x8f,0x80,0x1a,0x21,0xe1,0xc0,0x71,0xe0,0x20,0xf1,0x24,0x88,0x34,0x62,0x00,0xe3,0x3f,0x8d,0xfe,0x81,0x80,0xc1,0xf8,0x5b,0xe2,0x0f,0x18,0xc7,0xf0,0x1e,0x50,0x35,0xa0,0xc8,0x3f,0x98,0x30,0x70,0x87,0x44,0x1e,0x21,0xe3,0xf8,0x02,0x4b,0xaf,0x01,0x81,0xb3,0xca,0x01,0x1c,0x25,0x94,0x01,0x04,0x58,0x8d,0x5c,0x0b,0xc6,0x08,0x10,0x78,0xc3,0x3f,0xf0,0x72,0x88,0x98,0x8b,0x89,0x55,0x82,0xc7,0x9b,0xe5,0x00,0x87,0x26,0xc4,0x46,0x20,0xf2,0xd1,0x87,0xc6,0x0c,0xdf,0x21,0x50,0x8a,0xc7,0x00,0x38,0x2e,0x04,0x42,0xaf,0x05,0x06,0x0a,0xb8,0x70,0x0f,0x91,0x80,0x5c,0x03,0xc5,0x30,0x84,0x6a,0xe1,0x40,0xf1,0x7b,0x0f,0x00,0x7a,0x24,0x21,0x07,0x94,0x33,0x09,0x57,0x8a,0x93,0x85,0xec,0x3e,0x00,0x79,0x0b,0x88,0x06,0x3c,0x3f,0xfc,0xa8,0x1e,0x21,0x91,0x76,0x90,0x90,0x40,0x03,0xe0,0xe0,0x78,0x3f,0xd5,0x58,0x0e,0x08,0x32,0x3f,0x88,0xa8,0x90,0x8c,0x25,0x30,0xbc,0x7f,0xb5,0x50,0x1b,0xe0,0x20,0x7f,0x92,0x33,0x88,0x97,0x4a,0x07,0x0c,0x9e,0x5f,0xeb,0xaa,0xf2,0x74,0x8d,0x17,0x80,0x06,0x29,0xf1,0xe0,0x71,0xfb,0xfd,0x71,0xd8,0xff,0xf8,0x21,0x71,0x04,0x87,0x01,0xc1,0xa1,0xff,0x83,0xe7,0xf0,0xff,0xc1,0x51,0xe4,0xdd,0x1b,0x07,0xc2,0x63,0xf6,0x0f,0x9f,0xeb,0x5f,0x02,0x77,0x8a,0xc4,0xa3,0x17,0xc8,0x44,0x8c,0x34,0x20,0x71,0xfe,0x99,0x04,0x88,0x40,0x01,0xc3,0x47,0xf0,0x93,0x0f,0xf4,0x28,0x0e,0x3a,0xad,0x50,0x39,0x30,0x1f,0x18,0x3d,0x0e,0x31,0xff,0x3d,0x0c,0x02,0xa8,0x03,0x20,0x01,0x7e,0x3f,0xf8,0x09,0x06,0x33,0xfe,0x1b,0x50,}; +const uint8_t *_I_DFU_128x50[] = {_I_DFU_128x50_0}; -const uint8_t _I_DolphinFirstStart4_67x53_0[] = {0x01,0x00,0x1f,0x01,0x00,0x17,0xc3,0xfe,0x08,0x68,0x74,0x02,0x0e,0x07,0x4c,0x04,0x06,0x01,0x18,0x04,0x25,0x00,0x04,0x36,0x00,0x42,0x48,0x02,0x88,0x00,0x28,0x80,0x0c,0xa0,0x40,0x83,0x84,0x00,0xca,0x08,0x08,0x30,0x21,0x83,0x0c,0x2c,0x81,0xe3,0x04,0x20,0xc0,0x80,0x02,0x31,0x32,0x11,0x02,0x27,0x00,0x5d,0x40,0x45,0x87,0x90,0x3e,0x7c,0x00,0x43,0x84,0x4e,0x60,0x43,0x30,0x89,0x82,0x12,0x80,0x15,0x20,0x40,0x99,0xc8,0x22,0x7b,0x88,0x10,0x20,0x82,0x27,0x7c,0x82,0x9d,0x48,0x22,0x5f,0x0d,0xfc,0x08,0x10,0x41,0x12,0xf8,0x57,0xc2,0x28,0x30,0x1e,0x07,0x9e,0x06,0x87,0x25,0x79,0xc4,0x20,0x40,0x83,0x21,0x14,0x22,0x08,0x08,0x38,0x2a,0xb8,0xd9,0x47,0x0a,0x14,0x09,0xf0,0x54,0x47,0x1f,0x81,0x82,0x1a,0xde,0x8e,0x33,0xd1,0xc7,0x81,0x0f,0x0e,0x45,0x18,0x20,0xa1,0xe6,0xf2,0x10,0x89,0xa0,0x70,0x11,0x00,0x41,0x46,0x03,0x86,0x55,0x10,0x40,0xc1,0x82,0x25,0x20,0x04,0x11,0x94,0x80,0x43,0x10,0x84,0x01,0x46,0xc0,0xbd,0x38,0x40,0x20,0x8f,0x49,0x08,0xc4,0x1c,0xc8,0x22,0x50,0x38,0x20,0x20,0x86,0xe4,0x83,0x10,0x41,0x8b,0x87,0xf9,0x03,0x81,0xc0,0x81,0x05,0x81,0xc0,0x40,0xf3,0x90,0x60,0x41,0x70,0x2c,0x17,0x01,0xc0,0xc1,0x41,0x05,0x30,0x98,0x43,0x04,0x65,0x01,0x04,0x0c,0x32,0x38,0x91,0x18,0x04,0x14,0x10,0x38,0x18,0x1e,0xac,0x7c,0x41,0x11,0x88,0x5f,0xfc,0x17,0x55,0xa9,0x82,0x06,0x05,0xbc,0x85,0x02,0x08,0xc6,0x32,0x0f,0xe5,0x5e,0x1a,0x08,0x5c,0x06,0xaa,0x34,0x08,0x4a,0x06,0x02,0xab,0x75,0xf0,0x4f,0xc1,0x05,0x80,0x08,0x8e,0xab,0x7f,0xea,0x04,0x11,0x80,0x6a,0xa0,0x02,0x03,0x08,}; -const uint8_t *_I_DolphinFirstStart4_67x53[] = {_I_DolphinFirstStart4_67x53_0}; +const uint8_t _I_ButtonLeftSmall_3x5_0[] = {0x00,0x04,0x06,0x07,0x06,0x04,}; +const uint8_t *_I_ButtonLeftSmall_3x5[] = {_I_ButtonLeftSmall_3x5_0}; + +const uint8_t _I_ButtonRightSmall_3x5_0[] = {0x00,0x01,0x03,0x07,0x03,0x01,}; +const uint8_t *_I_ButtonRightSmall_3x5[] = {_I_ButtonRightSmall_3x5_0}; + +const uint8_t _I_ButtonRight_4x7_0[] = {0x00,0x01,0x03,0x07,0x0F,0x07,0x03,0x01,}; +const uint8_t *_I_ButtonRight_4x7[] = {_I_ButtonRight_4x7_0}; + +const uint8_t _I_Warning_30x23_0[] = {0x01,0x00,0x47,0x00,0x80,0x70,0x00,0x65,0xe0,0x80,0x80,0xc7,0xe1,0x03,0x01,0xaf,0xe2,0x0e,0x03,0x19,0xe4,0x3c,0x06,0xb3,0xe8,0xf8,0x0c,0x67,0xf3,0xf0,0x1a,0x60,0x27,0xf7,0xf1,0x50,0xcf,0xff,0xe0,0x34,0xf0,0x00,0xc6,0x03,0xf0,0x01,0x8c,0x0c,0x06,0x7f,0x80,0x18,0xc1,0xff,0x9f,0xff,0xfc,0x3c,0x06,0x7f,0xe0,0x58,0xc7,0xff,0xe0,0x31,0x00,0x88,0x00,0x67,0xff,0xe0,0x18,0xc7,0xc0,}; +const uint8_t *_I_Warning_30x23[] = {_I_Warning_30x23_0}; const uint8_t _I_DolphinFirstStart2_59x51_0[] = {0x01,0x00,0x2e,0x01,0x00,0x1f,0xfe,0x06,0x05,0x3f,0xc7,0xfe,0x01,0x1c,0x03,0x16,0x02,0xaf,0x0f,0x80,0x58,0x01,0xc7,0xaa,0x80,0x82,0xc4,0x0e,0x55,0x6b,0x28,0x10,0x81,0x45,0xab,0x8d,0x01,0xca,0x04,0x1a,0x1a,0xac,0x1c,0x0e,0x50,0x48,0x06,0xc0,0x3c,0x40,0x01,0x84,0x40,0x2b,0x15,0x51,0xd9,0xc4,0x20,0x1a,0xc9,0x50,0x1c,0xe4,0x02,0xe1,0x8a,0x81,0xd7,0x55,0x0a,0x03,0x9d,0x02,0x01,0x5c,0x82,0x81,0xd7,0xc0,0x3a,0x10,0x3a,0x12,0x88,0xc8,0x60,0x11,0x07,0xa0,0x1c,0x68,0x00,0xf6,0xe0,0x22,0x50,0x0e,0x36,0x00,0x7b,0x68,0x00,0x83,0xa0,0x11,0x08,0x1c,0x6a,0x03,0x42,0x44,0x1e,0xc0,0x28,0x50,0x61,0xf9,0x56,0x00,0xe3,0x60,0x40,0x88,0x1c,0x75,0x01,0x42,0x07,0x9d,0x50,0x5e,0x4b,0x01,0x37,0x8e,0xb0,0x0e,0x51,0xd8,0x04,0xc2,0x01,0xd4,0x5d,0x1c,0x02,0x30,0x7f,0x14,0x99,0x5c,0x20,0x11,0x48,0x07,0x58,0x0e,0x20,0x81,0xd0,0x23,0x04,0x1e,0x30,0x80,0x38,0xd4,0x11,0x82,0x0f,0x18,0x40,0xb0,0xb0,0x50,0x3d,0x58,0x1c,0x52,0x85,0xf1,0x83,0x75,0x58,0x64,0x49,0x1a,0xfc,0x17,0x57,0x01,0x88,0x25,0x0b,0x55,0x02,0xaa,0xc0,0x64,0x14,0x08,0x1e,0x02,0xaa,0x1f,0x18,0x0f,0x00,0xbe,0x20,0xf1,0x80,0x82,0x46,0x01,0x03,0x82,0xe0,0x04,0xa3,0xab,0x46,0x0e,0x32,0x15,0x80,0xb5,0x40,0x2a,0xa4,0x21,0x98,0x43,0x70,0x13,0x58,0x04,0xac,0xa4,0x3c,0x08,0xd6,0x02,0x35,0x00,0x8a,0xcd,0x06,0xa3,0x1d,0xa0,0x24,0x46,0x57,0xe8,0x26,0x8c,0xdb,0x80,0x84,0x18,0xad,0x42,0x07,0x5f,0xbf,0xb9,0x8a,0x17,0x80,0xff,0x6a,0xb0,0x46,0x91,0x07,0x88,0xc4,0x4a,0x43,0x1f,0x07,0x92,0xc4,0x49,0x82,0x9b,0x25,0x98,0xc0,0x28,0xa0,0x73,0x1f,0x0b,0x50,0x81,0xea,0x07,0x40,0x7b,0xac,0x44,0x0e,0xa0,}; const uint8_t *_I_DolphinFirstStart2_59x51[] = {_I_DolphinFirstStart2_59x51_0}; @@ -91,110 +85,113 @@ const uint8_t *_I_DolphinFirstStart2_59x51[] = {_I_DolphinFirstStart2_59x51_0}; const uint8_t _I_DolphinFirstStart5_54x49_0[] = {0x01,0x00,0x0b,0x01,0x00,0x0f,0xf2,0xfe,0x06,0x48,0x1e,0x02,0x06,0x05,0x2e,0x00,0x08,0x61,0x80,0x62,0x98,0x00,0x86,0x20,0x06,0x28,0x40,0x08,0x64,0x00,0x62,0x82,0x00,0x86,0x80,0x06,0x28,0x14,0x72,0x01,0x80,0x03,0x14,0x06,0x44,0x03,0x20,0x49,0x00,0xc4,0x0c,0x61,0x13,0x81,0x07,0x90,0x0c,0xff,0xa8,0x18,0xcc,0xe0,0x10,0x78,0x60,0x18,0xc9,0xe3,0x10,0x03,0x0e,0x02,0x02,0x4f,0x19,0x00,0x18,0x78,0x10,0x12,0x78,0xc8,0x0a,0xc3,0xf8,0x80,0xc1,0x80,0xc5,0xe0,0xff,0x8f,0x47,0xe1,0x27,0x03,0x0d,0xfc,0x80,0x3b,0xc9,0x74,0x43,0x81,0x0f,0xb0,0x40,0x2b,0xd2,0xd3,0x71,0x07,0x87,0x5f,0x16,0x84,0x54,0x23,0xe3,0x21,0xab,0xc5,0x61,0x1a,0x82,0xf0,0xf0,0x35,0x70,0xa8,0x45,0x50,0x2a,0x3e,0x0a,0xac,0x1e,0x11,0x28,0x03,0x0f,0xc3,0xfe,0x06,0x19,0xa0,0x18,0x6f,0x9f,0x08,0x7c,0x22,0x30,0x06,0x1d,0xfc,0x3e,0x21,0x08,0x00,0x8f,0x01,0x7a,0x31,0x08,0x24,0x42,0x21,0xf0,0x5e,0x08,0x18,0x44,0xe3,0x0f,0x59,0x92,0xb4,0x96,0x66,0x06,0x58,0x10,0x19,0x60,0x20,0x64,0x46,0x08,0x19,0x27,0x00,0x65,0x9f,0x81,0x93,0xd1,0x2b,0x03,0x17,0x82,0x3f,0x50,0x9a,0x81,0x87,0x51,0x1e,0xf0,0x68,0x69,0x40,0x61,0xea,0x9d,0x86,0x1d,0x45,0x80,0x61,0x2d,0x48,0xc2,0x67,0x8d,0x12,0x3a,0x06,0x19,0x02,0x88,0x74,0x4b,0x21,0x03,0x1d,0x08,0xca,0x21,0x41,0x06,0x93,0xe8,0xa1,0x85,0x31,0xe9,0x24,0x48,0x20,0x30,0x1b,0x10,0x18,0x77,0x8f,0xa1,0x80,0xcc,0x40,0xc3,0x56,0x0b,0x8c,0x0a,0x22,0xba,0x12,0x88,0x81,0x84,}; const uint8_t *_I_DolphinFirstStart5_54x49[] = {_I_DolphinFirstStart5_54x49_0}; -const uint8_t _I_DolphinFirstStart0_70x53_0[] = {0x01,0x00,0x5a,0x01,0x80,0x60,0x3f,0xf7,0xf0,0x42,0xf8,0x01,0x43,0x07,0x04,0x24,0x72,0x01,0xc0,0x9d,0x82,0x13,0xff,0xff,0xbd,0x70,0x20,0x20,0x72,0xe0,0x40,0x2a,0x11,0xdb,0x00,0x6c,0xec,0x10,0x0d,0x44,0x3a,0x71,0x0e,0x04,0x14,0x42,0x01,0x54,0x86,0xd3,0x27,0x02,0x44,0xd4,0x41,0xb0,0xf2,0x10,0x42,0x55,0x38,0x71,0x1b,0x10,0x18,0xa0,0x41,0x11,0xb1,0xc8,0x28,0x98,0x09,0xfc,0x00,0x72,0x35,0x49,0x8d,0x0b,0xc1,0x70,0xf0,0x10,0x4b,0x51,0x11,0xc2,0x6c,0x0a,0xa3,0x03,0x80,0x7f,0xbf,0xf3,0x08,0x46,0x60,0x90,0x30,0x60,0x50,0xd8,0x2c,0x11,0x0c,0x71,0x5c,0x60,0xf8,0x0f,0xcf,0x3f,0x81,0x80,0xa1,0x9e,0x86,0x0f,0xc0,0x82,0x64,0x30,0x3e,0x09,0x84,0x03,0xf1,0x03,0xa0,0x40,0xa4,0x18,0x39,0xfc,0x20,0x52,0x30,0x19,0x07,0xc6,0x8e,0x4a,0x18,0x22,0x74,0x60,0x1a,0x0f,0xc6,0x3c,0x60,0x5c,0x05,0x28,0xe4,0x3f,0x99,0xf8,0x22,0x28,0x7e,0x05,0x91,0xa8,0x7f,0x23,0xf0,0x59,0x00,0xac,0x63,0xe0,0x81,0xcf,0x4f,0xe0,0xb1,0x81,0x58,0xc3,0xc1,0x08,0x24,0x1f,0xf9,0x68,0x6a,0x1f,0xe9,0xff,0x16,0x02,0x34,0x13,0x50,0x82,0x0a,0xea,0x60,0x1f,0xf9,0xf0,0x41,0x05,0x1d,0x30,0x09,0x18,0x60,0x15,0xa3,0xe8,0x83,0x47,0xe0,0xec,0x2c,0xaf,0xf2,0x0e,0x08,0x1f,0xc1,0x18,0x60,0x1a,0xaf,0xc2,0x6c,0x89,0x62,0x03,0x19,0xad,0xe5,0x70,0x44,0x62,0x80,0x5a,0xa1,0x4f,0x63,0x23,0x0c,0x7a,0xaa,0x4d,0x11,0xe9,0x00,0x06,0x73,0xaa,0x25,0x0a,0x78,0xaf,0x90,0x09,0x25,0x54,0x56,0x5f,0x04,0x30,0xc0,0x64,0x7a,0xa1,0x11,0x7e,0x20,0x18,0x0f,0x3c,0x82,0xaa,0x04,0x18,0x0d,0xf8,0x16,0x33,0xe8,0x84,0xa8,0x08,0x3c,0x33,0x00,0xf0,0x20,0x71,0x08,0xa9,0x38,0x86,0x62,0x62,0x18,0x40,0x44,0x80,0x09,0x04,0x08,0x90,0x01,0x20,0x41,0x17,0x22,0x90,0x01,0x3e,0x00,0x76,0x80,0x1d,0x48,0x00,0x8d,0x91,0x00,0x34,0xf8,0x20,0xe2,0xa7,0x9c,0x06,0x5c,0x11,0x02,0x28,0x5d,0x91,0x35,0x48,0xaf,0xf8,0x04,0x3f,0xf9,0x88,0x20,0x01,}; -const uint8_t *_I_DolphinFirstStart0_70x53[] = {_I_DolphinFirstStart0_70x53_0}; - const uint8_t _I_DolphinFirstStart6_58x54_0[] = {0x01,0x00,0x21,0x01,0x00,0x0f,0xf2,0x7e,0x06,0x4c,0x04,0x0f,0x81,0x03,0x03,0x9d,0x80,0x04,0x30,0xc0,0x39,0xc6,0x00,0x43,0x30,0x03,0x9c,0x10,0x04,0x34,0x00,0x39,0xc0,0x84,0x44,0x07,0x38,0x08,0x0d,0x41,0x68,0x13,0x70,0x39,0x08,0xd0,0x56,0xa1,0xd1,0x03,0x94,0x80,0x04,0x30,0x68,0x04,0x20,0x0e,0x84,0x91,0x03,0xa9,0x64,0x62,0x80,0x41,0x88,0x40,0x3f,0xc6,0xf1,0xfe,0x43,0xc0,0xe3,0x80,0xff,0xff,0xe0,0x3f,0xf8,0xf8,0x1c,0x78,0x18,0x1f,0xfe,0x0f,0x02,0x12,0x18,0x47,0x03,0x82,0x10,0x1e,0x08,0x1c,0xf5,0x60,0x71,0xd4,0x81,0xcf,0xab,0xff,0xd5,0xf5,0xc0,0xe3,0x04,0xe0,0x03,0x86,0xae,0x27,0x28,0x27,0x40,0x0e,0x21,0x91,0x03,0x96,0x80,0x0e,0x34,0x18,0x79,0x28,0x60,0x95,0x00,0x38,0xf8,0x20,0x27,0xd1,0x82,0x6a,0x03,0xc3,0x1c,0x39,0x94,0x0a,0xa1,0xc0,0xc5,0x2f,0xca,0x05,0x02,0x90,0x24,0x56,0x04,0x68,0x10,0x01,0x4f,0x80,0xea,0x5b,0x10,0x38,0x83,0x8d,0xa0,0x30,0x30,0x38,0xa3,0x09,0xc0,0x20,0xf2,0x03,0x90,0xc0,0x46,0xe2,0x91,0x2f,0x80,0xfc,0xe0,0x1e,0x08,0x02,0x54,0x47,0x62,0x27,0x2f,0xfb,0x14,0xdc,0xc6,0xb5,0x30,0x38,0x8b,0x05,0x6a,0x60,0x01,0x89,0x00,0xc8,0x16,0x50,0x29,0x10,0x1c,0x8d,0x25,0x05,0xa1,0x15,0xc9,0xfe,0x50,0xaa,0x08,0x10,0x67,0x01,0x22,0x8a,0xe0,0x60,0xe5,0xf2,0x07,0x8e,0xa8,0xb0,0x49,0xe1,0x00,0x0d,0xd4,0x68,0x5a,0x00,0x39,0x46,0x88,0x84,0x07,0x30,0xe8,0x81,0xc6,0x40,0x4d,0x11,0x91,0x17,0x06,0x40,0x65,0x11,0x51,0x01,0xc6,0x81,0x04,0x32,0x18,0x1e,0x92,0x64,0x00,0x11,0x68,0x81,0xd6,0xa0,0x07,0x16,0x22,0x6b,0x0a,0x82,0x07,0x3f,0x05,0x4d,0xdc,0x24,0x21,}; const uint8_t *_I_DolphinFirstStart6_58x54[] = {_I_DolphinFirstStart6_58x54_0}; -const uint8_t _I_DolphinFirstStart1_59x53_0[] = {0x01,0x00,0x1e,0x01,0x00,0x0e,0x03,0xfe,0x07,0x5b,0x84,0x02,0x06,0x07,0x48,0x64,0x02,0x08,0x07,0x48,0x14,0x02,0x10,0x07,0x48,0x0c,0x03,0x21,0x3f,0x13,0x18,0x84,0xa8,0x00,0x75,0x8c,0x00,0xca,0x00,0x0b,0x28,0x20,0x1d,0xa0,0x59,0xe0,0x39,0x48,0x07,0x03,0x81,0xd5,0x81,0xd6,0x81,0x55,0x8c,0x01,0xc6,0x21,0x00,0x87,0x68,0x25,0x52,0x40,0x39,0x7c,0x21,0xf5,0x08,0xa8,0x1d,0x20,0xfa,0x88,0x70,0x1c,0xfd,0x10,0x3a,0xa4,0x1f,0x88,0x54,0x18,0x85,0x52,0x09,0xbe,0x81,0xc1,0x0c,0x83,0x10,0x94,0x40,0x39,0xf0,0x19,0x21,0xc8,0x62,0x12,0x0c,0x04,0x0e,0x0c,0x07,0x38,0x07,0x86,0x07,0x18,0x03,0x94,0xc2,0x01,0x9e,0x81,0xca,0x38,0x89,0x21,0x0f,0x0c,0x03,0xf9,0x27,0x13,0x94,0xd0,0xb6,0x70,0x20,0x38,0xda,0x80,0xe5,0x10,0x03,0x95,0x59,0x54,0x70,0x10,0x38,0xda,0xc0,0xc3,0xfe,0xc1,0xab,0x0b,0xaa,0x2a,0x1c,0x05,0x81,0x58,0x38,0x09,0xd0,0x5c,0xa3,0xe0,0x72,0x86,0xae,0x8d,0x40,0x34,0x06,0xa1,0xc0,0xc0,0xe3,0xc0,0x65,0x1c,0x19,0x58,0x29,0xe1,0x00,0x14,0x28,0x0a,0x26,0x61,0x00,0x15,0x58,0x0a,0x2e,0x34,0xd6,0x42,0x9e,0x6b,0x54,0x82,0x92,0x08,0x1e,0x63,0x41,0x1d,0x0a,0x88,0x60,0x1d,0x42,0x11,0x5c,0x01,0xe5,0x3c,0x03,0x97,0x30,0x0e,0x42,0x42,0x80,0xd0,0x82,0xe4,0x07,0x28,0x17,0x10,0x1e,0xb0,0x4a,0x20,0x3d,0x61,0x1a,0x80,0x79,0x0f,0x0a,0x21,0x70,0x07,0x90,0x1c,0xa4,0x1a,0x00,0x7a,0xd0,0x0e,0x42,0x34,0x20,0x10,0xe0,0x00,0xed,0x00,0xa1,0x82,0xc8,0xc6,0x74,0x40,0xd9,0x01,0xce,0x84,0x07,0x69,0x10,0xcc,0x80,0xe7,0x5c,0x03,0xb4,0xa8,0x96,0x40,0x73,0x8a,0x96,0xc8,0x0c,0x40,}; -const uint8_t *_I_DolphinFirstStart1_59x53[] = {_I_DolphinFirstStart1_59x53_0}; - -const uint8_t _I_DolphinFirstStart8_56x51_0[] = {0x01,0x00,0xfd,0x00,0x00,0x17,0x83,0xff,0x01,0x03,0x1c,0x72,0x01,0x06,0x03,0x1c,0x0e,0x01,0x18,0x02,0x96,0x00,0x04,0x36,0x00,0x31,0x50,0x01,0x24,0x1c,0x29,0x00,0x28,0xa0,0x40,0x21,0x88,0x01,0x8a,0x08,0x02,0x18,0x40,0x18,0x80,0x64,0x09,0x20,0x89,0x81,0x98,0x3c,0x42,0x63,0x03,0x30,0xcc,0x70,0x10,0x71,0xd9,0x01,0x86,0xc1,0x1c,0x03,0x24,0x42,0x7e,0x50,0x12,0x91,0x62,0x2f,0xf8,0x0e,0x00,0x18,0xb9,0x17,0x1c,0x04,0x83,0x02,0x06,0x1e,0x27,0xc4,0x54,0x20,0x62,0xf2,0x7c,0xe0,0x52,0x0c,0x10,0x88,0x7c,0x9f,0xf8,0x28,0x18,0x41,0xa5,0xff,0x85,0x48,0x30,0x80,0xd1,0xe4,0x5f,0xc1,0xa3,0x84,0x26,0x0f,0x23,0xfe,0x1b,0x18,0x44,0x16,0x01,0x90,0x81,0xc1,0x62,0x10,0x84,0xc0,0xf8,0x20,0x30,0x28,0x84,0x40,0x1a,0x25,0x11,0x82,0x42,0x22,0x11,0xf4,0xd9,0xc1,0x02,0x22,0xb2,0x38,0x14,0xc1,0x8e,0x90,0x14,0xc1,0xa2,0x86,0x02,0xc6,0x30,0x31,0x06,0x8c,0x0c,0x26,0x02,0x56,0x9d,0x04,0x0c,0x6a,0xa1,0x03,0x21,0x20,0x68,0x5f,0xe7,0xa9,0x00,0x86,0x85,0x01,0x8f,0xe0,0x08,0xe3,0x00,0xe1,0x02,0xc6,0xfe,0x16,0x23,0xe1,0x13,0x10,0xa4,0x82,0xb1,0x12,0x88,0x00,0xf0,0x91,0xe0,0x6a,0xfd,0x63,0xfc,0x08,0x78,0x18,0xb5,0x5e,0xad,0xfb,0x84,0xa0,0x95,0x48,0xad,0x54,0x4a,0x50,0x4d,0x44,0x6b,0x56,0x0d,0x28,0x45,0x42,0x6a,0x0d,0x38,0x46,0x02,0x55,0xaa,0x35,0x25,0x52,0xac,0x06,0x4b,0x04,0xa8,0x0c,0x94,0x03,0xa0,0x80,0x04,}; -const uint8_t *_I_DolphinFirstStart8_56x51[] = {_I_DolphinFirstStart8_56x51_0}; - -const uint8_t _I_DolphinFirstStart7_61x51_0[] = {0x01,0x00,0x13,0x01,0x00,0x17,0x03,0xff,0x01,0x03,0xa4,0xe2,0x01,0x0e,0x03,0xa4,0x1a,0x01,0x30,0x03,0x1e,0x00,0x2a,0x3c,0x00,0x39,0xd0,0x00,0x65,0x03,0x01,0x94,0x80,0x06,0x50,0x40,0x19,0x44,0x00,0x65,0x08,0x01,0xb0,0x2c,0xe2,0x81,0xb6,0x86,0x0a,0xd8,0x7c,0x20,0x75,0x85,0x10,0xcc,0x06,0x50,0x50,0x3b,0x10,0xce,0x00,0x69,0x20,0x79,0x7c,0x20,0x20,0x71,0xc0,0x07,0xca,0xf1,0x02,0x81,0x01,0xc6,0x3a,0x07,0x1f,0xe4,0x10,0x0e,0x53,0xe0,0x38,0xe7,0xa0,0xa0,0x72,0xbb,0x81,0xca,0x12,0x68,0x1c,0x05,0x5c,0x0e,0x3f,0xe8,0xc8,0x1c,0xab,0xe0,0x72,0x94,0x81,0xda,0xb2,0x07,0x5f,0xe0,0x3d,0xbf,0x95,0x44,0x20,0x81,0xce,0xf1,0x2f,0x03,0x94,0xb8,0xae,0x51,0x00,0x39,0x47,0x60,0xd0,0x84,0x70,0x81,0xcb,0x44,0x9d,0x10,0x3a,0x58,0xce,0xe6,0x07,0x29,0x10,0x18,0xa0,0x50,0x88,0x76,0x02,0x22,0x07,0x49,0x8e,0x02,0x24,0x07,0x4e,0x0e,0x02,0x12,0x96,0x38,0x44,0x07,0x02,0x8f,0x1c,0x07,0x1c,0x4e,0x30,0x1c,0x10,0x3c,0x6c,0x13,0x80,0x38,0xc0,0xb0,0x80,0xf1,0x6e,0x90,0x1c,0x71,0x10,0xd7,0x49,0x81,0xc7,0x20,0x0f,0x17,0xe9,0x42,0x20,0x91,0x09,0xeb,0x24,0xe2,0x10,0x49,0x07,0x6f,0xff,0x80,0x56,0x88,0x1c,0xa2,0xae,0xd1,0x66,0x89,0xe0,0x68,0x11,0xb8,0x06,0xc0,0x2e,0x40,0x71,0x9a,0xc0,0x2b,0x00,0x73,0xc0,0x7a,0xe0,0x09,0x12,0x03,0x95,0x57,0xff,0x17,0x03,0x9c,0x03,0x57,0xaa,0x78,0x94,0x40,0xa6,0x35,0x5a,0xac,0x14,0x0e,0x9a,0xad,0x50,0xf8,0x41,0x05,0x00,0x83,0x55,0x14,0x06,0x07,0x18,0x54,0xa0,0x0e,0xb0,0x60,0x31,0xc0,0x00,}; -const uint8_t *_I_DolphinFirstStart7_61x51[] = {_I_DolphinFirstStart7_61x51_0}; - const uint8_t _I_Flipper_young_80x60_0[] = {0x01,0x00,0xa3,0x01,0x00,0x1e,0x03,0xff,0xff,0x87,0x82,0x57,0xf1,0x83,0x90,0xde,0x01,0x2b,0x0e,0x83,0x70,0xfb,0x10,0x10,0x41,0xf8,0x27,0x70,0xcc,0x34,0xc6,0x0e,0x09,0x3e,0x04,0x86,0x21,0x0c,0x90,0xc3,0x03,0xa9,0xe7,0xb0,0x46,0x2c,0x51,0x40,0x4a,0x63,0x38,0x31,0x0a,0x34,0x90,0x12,0x91,0x8e,0x3c,0xff,0x89,0x4c,0x04,0xa4,0x43,0xfd,0xf3,0xc3,0xf2,0x01,0x29,0xe0,0x2b,0x8e,0x72,0xa0,0x46,0x4b,0xe0,0x30,0xba,0x10,0x22,0xca,0x1c,0x0b,0x26,0x09,0x3c,0x04,0x0c,0x08,0x59,0xc8,0x21,0x64,0xc4,0x47,0x98,0x82,0x81,0x0a,0xe0,0x21,0x39,0x04,0x34,0x88,0x60,0x93,0xa0,0x45,0x4b,0x06,0xa3,0x40,0x48,0xfc,0x20,0xf0,0x82,0xa2,0x4d,0x60,0x11,0xe9,0xc2,0x19,0x64,0xd0,0x08,0x1f,0x80,0x7e,0x60,0x01,0x92,0x60,0x20,0x38,0x05,0x21,0x7c,0x3f,0xf0,0x1a,0xe6,0x00,0xe6,0x21,0x32,0x1a,0x0c,0x0e,0x91,0x80,0x8f,0xc0,0x06,0x25,0xcc,0xbf,0xc1,0xaa,0x10,0x0b,0xfc,0x02,0x60,0x2e,0x2c,0x04,0x32,0xc1,0x00,0xff,0x40,0x68,0x00,0x91,0x89,0xc0,0x21,0x20,0x51,0xfe,0x41,0xf0,0x00,0x91,0xc4,0xcf,0xe2,0x40,0x51,0xfc,0x0c,0x86,0x07,0x80,0xe2,0xdf,0xda,0x25,0xf0,0x9f,0xc0,0x21,0x98,0x0f,0x27,0xfd,0xa2,0x5e,0x01,0x90,0xc4,0x30,0x1e,0x2f,0xfc,0xa1,0x3a,0x45,0x41,0xb0,0x60,0x3e,0x5e,0x79,0x4a,0x10,0xbf,0xe2,0x61,0xc0,0x82,0x52,0x01,0xff,0x36,0x8e,0x3b,0xe5,0xff,0x04,0x9f,0xf8,0x78,0x3b,0x8f,0x97,0xf8,0x12,0x7f,0xc3,0x78,0xf8,0x3e,0x5f,0xc0,0x49,0xfe,0x08,0xc2,0x17,0x1f,0xcd,0xa5,0xac,0x5f,0x02,0x30,0xc0,0x30,0x5f,0xfd,0x23,0xbc,0xbc,0x1f,0xf0,0xc1,0x5f,0xaa,0x8e,0x52,0x28,0x10,0x10,0x6f,0x1b,0x28,0x57,0x81,0x66,0x25,0x01,0x80,0x4e,0x28,0x15,0x98,0xad,0xc3,0xfd,0xff,0xff,0x91,0x87,0xc1,0x80,0xd4,0xc2,0xb2,0x03,0xb1,0x5b,0x13,0x34,0x6a,0xf1,0x58,0x84,0x0e,0x1d,0x00,0x23,0x14,0x0f,0x55,0x0a,0x88,0x67,0x0d,0x83,0x7c,0x04,0x8c,0x0a,0xa9,0x15,0x90,0x7c,0x07,0x23,0xf8,0x80,0xc1,0xa0,0xda,0x88,0x54,0x82,0x00,0x2f,0x1f,0xe4,0x3c,0x7a,0x35,0x08,0xab,0x20,0x7f,0x03,0xc1,0x2d,0x96,0x82,0x14,0xce,0x20,0x02,0x04,0xc6,0x00,0x60,0x20,0x01,0x84,0xc4,0x6a,0x21,0x36,0x3b,0x8c,0xf0,0x3c,0xc8,0x02,0x1b,0x88,0x01,0xe1,0x80,0x98,0x2d,0x10,0x01,0xb0,0x05,0xa1,0x00,0x3d,0xf8,0x13,0x17,0x81,0x47,0x80,0x0b,0xc0,0x28,0x8e,0x02,0xa4,0x81,0x2c,0xf0,0x20,0x01,0x00,}; const uint8_t *_I_Flipper_young_80x60[] = {_I_Flipper_young_80x60_0}; +const uint8_t _I_DolphinFirstStart8_56x51_0[] = {0x01,0x00,0xfd,0x00,0x00,0x17,0x83,0xff,0x01,0x03,0x1c,0x72,0x01,0x06,0x03,0x1c,0x0e,0x01,0x18,0x02,0x96,0x00,0x04,0x36,0x00,0x31,0x50,0x01,0x24,0x1c,0x29,0x00,0x28,0xa0,0x40,0x21,0x88,0x01,0x8a,0x08,0x02,0x18,0x40,0x18,0x80,0x64,0x09,0x20,0x89,0x81,0x98,0x3c,0x42,0x63,0x03,0x30,0xcc,0x70,0x10,0x71,0xd9,0x01,0x86,0xc1,0x1c,0x03,0x24,0x42,0x7e,0x50,0x12,0x91,0x62,0x2f,0xf8,0x0e,0x00,0x18,0xb9,0x17,0x1c,0x04,0x83,0x02,0x06,0x1e,0x27,0xc4,0x54,0x20,0x62,0xf2,0x7c,0xe0,0x52,0x0c,0x10,0x88,0x7c,0x9f,0xf8,0x28,0x18,0x41,0xa5,0xff,0x85,0x48,0x30,0x80,0xd1,0xe4,0x5f,0xc1,0xa3,0x84,0x26,0x0f,0x23,0xfe,0x1b,0x18,0x44,0x16,0x01,0x90,0x81,0xc1,0x62,0x10,0x84,0xc0,0xf8,0x20,0x30,0x28,0x84,0x40,0x1a,0x25,0x11,0x82,0x42,0x22,0x11,0xf4,0xd9,0xc1,0x02,0x22,0xb2,0x38,0x14,0xc1,0x8e,0x90,0x14,0xc1,0xa2,0x86,0x02,0xc6,0x30,0x31,0x06,0x8c,0x0c,0x26,0x02,0x56,0x9d,0x04,0x0c,0x6a,0xa1,0x03,0x21,0x20,0x68,0x5f,0xe7,0xa9,0x00,0x86,0x85,0x01,0x8f,0xe0,0x08,0xe3,0x00,0xe1,0x02,0xc6,0xfe,0x16,0x23,0xe1,0x13,0x10,0xa4,0x82,0xb1,0x12,0x88,0x00,0xf0,0x91,0xe0,0x6a,0xfd,0x63,0xfc,0x08,0x78,0x18,0xb5,0x5e,0xad,0xfb,0x84,0xa0,0x95,0x48,0xad,0x54,0x4a,0x50,0x4d,0x44,0x6b,0x56,0x0d,0x28,0x45,0x42,0x6a,0x0d,0x38,0x46,0x02,0x55,0xaa,0x35,0x25,0x52,0xac,0x06,0x4b,0x04,0xa8,0x0c,0x94,0x03,0xa0,0x80,0x04,}; +const uint8_t *_I_DolphinFirstStart8_56x51[] = {_I_DolphinFirstStart8_56x51_0}; + +const uint8_t _I_DolphinFirstStart1_59x53_0[] = {0x01,0x00,0x1e,0x01,0x00,0x0e,0x03,0xfe,0x07,0x5b,0x84,0x02,0x06,0x07,0x48,0x64,0x02,0x08,0x07,0x48,0x14,0x02,0x10,0x07,0x48,0x0c,0x03,0x21,0x3f,0x13,0x18,0x84,0xa8,0x00,0x75,0x8c,0x00,0xca,0x00,0x0b,0x28,0x20,0x1d,0xa0,0x59,0xe0,0x39,0x48,0x07,0x03,0x81,0xd5,0x81,0xd6,0x81,0x55,0x8c,0x01,0xc6,0x21,0x00,0x87,0x68,0x25,0x52,0x40,0x39,0x7c,0x21,0xf5,0x08,0xa8,0x1d,0x20,0xfa,0x88,0x70,0x1c,0xfd,0x10,0x3a,0xa4,0x1f,0x88,0x54,0x18,0x85,0x52,0x09,0xbe,0x81,0xc1,0x0c,0x83,0x10,0x94,0x40,0x39,0xf0,0x19,0x21,0xc8,0x62,0x12,0x0c,0x04,0x0e,0x0c,0x07,0x38,0x07,0x86,0x07,0x18,0x03,0x94,0xc2,0x01,0x9e,0x81,0xca,0x38,0x89,0x21,0x0f,0x0c,0x03,0xf9,0x27,0x13,0x94,0xd0,0xb6,0x70,0x20,0x38,0xda,0x80,0xe5,0x10,0x03,0x95,0x59,0x54,0x70,0x10,0x38,0xda,0xc0,0xc3,0xfe,0xc1,0xab,0x0b,0xaa,0x2a,0x1c,0x05,0x81,0x58,0x38,0x09,0xd0,0x5c,0xa3,0xe0,0x72,0x86,0xae,0x8d,0x40,0x34,0x06,0xa1,0xc0,0xc0,0xe3,0xc0,0x65,0x1c,0x19,0x58,0x29,0xe1,0x00,0x14,0x28,0x0a,0x26,0x61,0x00,0x15,0x58,0x0a,0x2e,0x34,0xd6,0x42,0x9e,0x6b,0x54,0x82,0x92,0x08,0x1e,0x63,0x41,0x1d,0x0a,0x88,0x60,0x1d,0x42,0x11,0x5c,0x01,0xe5,0x3c,0x03,0x97,0x30,0x0e,0x42,0x42,0x80,0xd0,0x82,0xe4,0x07,0x28,0x17,0x10,0x1e,0xb0,0x4a,0x20,0x3d,0x61,0x1a,0x80,0x79,0x0f,0x0a,0x21,0x70,0x07,0x90,0x1c,0xa4,0x1a,0x00,0x7a,0xd0,0x0e,0x42,0x34,0x20,0x10,0xe0,0x00,0xed,0x00,0xa1,0x82,0xc8,0xc6,0x74,0x40,0xd9,0x01,0xce,0x84,0x07,0x69,0x10,0xcc,0x80,0xe7,0x5c,0x03,0xb4,0xa8,0x96,0x40,0x73,0x8a,0x96,0xc8,0x0c,0x40,}; +const uint8_t *_I_DolphinFirstStart1_59x53[] = {_I_DolphinFirstStart1_59x53_0}; + +const uint8_t _I_DolphinOkay_41x43_0[] = {0x01,0x00,0xa0,0x00,0x00,0x0f,0x82,0x3e,0x05,0x38,0xf7,0x80,0x08,0x58,0x08,0x0c,0x02,0x0e,0x05,0x1b,0x00,0x08,0x63,0x00,0x21,0x88,0x00,0x86,0x40,0x02,0x18,0x40,0x08,0x68,0x00,0x21,0x82,0x06,0x88,0x0a,0xf0,0x21,0x39,0x09,0x84,0x02,0x20,0x57,0x09,0x98,0x15,0x67,0xc0,0x54,0xbe,0x81,0x4f,0x01,0xfe,0x02,0x9d,0x03,0xc4,0x20,0x10,0x29,0x7c,0x80,0xa9,0xfe,0x02,0xac,0x14,0x0a,0x77,0xc8,0x58,0x8c,0xf0,0x11,0x51,0x79,0xff,0x61,0x44,0x93,0x81,0x02,0xc4,0x9e,0x60,0xb2,0xf0,0xa0,0x46,0x0c,0x17,0x14,0x99,0x1a,0x07,0x80,0x59,0x49,0x82,0x21,0xc0,0xa4,0x82,0x24,0xb9,0x20,0x88,0x1c,0x47,0xc2,0x07,0x11,0x54,0xa0,0x60,0x53,0xb8,0x0a,0x4b,0xf3,0x03,0x87,0x81,0x4a,0x0d,0xfc,0x1a,0x98,0x68,0xb8,0x01,0x51,0x13,0x15,0xe0,0x82,0x7f,0x8d,0x78,0x38,0xbf,0xff,0xfa,0xb8,0x60,0xbf,0x1b,0xf9,0x50,0x14,0xea,0xe7,0x02,0x02,0x8e,0xac,0x94,0x40,}; +const uint8_t *_I_DolphinOkay_41x43[] = {_I_DolphinOkay_41x43_0}; + const uint8_t _I_DolphinFirstStart3_57x48_0[] = {0x01,0x00,0x12,0x01,0x00,0x16,0x03,0xff,0x07,0x03,0xa5,0x82,0x01,0x38,0x03,0xa4,0x62,0x01,0xc0,0x03,0xa4,0x10,0x04,0x30,0x10,0x39,0xc0,0x80,0x48,0x0c,0x40,0x91,0x7e,0x20,0x60,0x72,0x84,0x02,0x8b,0x78,0x12,0x28,0x80,0x68,0x85,0x87,0x20,0x11,0x18,0x5c,0x80,0xe8,0x01,0x19,0xc5,0x00,0x0e,0x62,0xc1,0x9f,0x01,0xcb,0xe9,0x03,0x84,0x60,0x20,0xf8,0x00,0x38,0xd7,0x21,0xb1,0x0f,0x04,0x04,0x0e,0x5a,0x89,0xd4,0x83,0xc0,0x4b,0x3a,0xc5,0x54,0xcc,0x20,0x51,0x00,0x8e,0xc3,0x54,0x80,0x13,0xf8,0x81,0xc6,0xc1,0x55,0x01,0x8c,0x78,0x0e,0x30,0xee,0x06,0xaa,0x05,0xe0,0xae,0x01,0xc6,0x23,0x80,0xaa,0xc1,0x60,0x1a,0x90,0x38,0xc8,0x60,0x1a,0xb8,0x54,0x02,0xad,0x07,0x80,0xd0,0x40,0x83,0x15,0x80,0x7b,0x21,0x10,0x1c,0x0c,0x03,0x7f,0x2a,0x80,0x4d,0x00,0xe3,0x01,0xf8,0xf0,0x2a,0xf0,0x08,0x60,0x1c,0x60,0x41,0xd1,0xdf,0x1a,0x44,0x0e,0x50,0x68,0x05,0xe3,0x07,0x02,0x82,0x01,0xc6,0x19,0x00,0xf8,0x5f,0xe0,0x20,0x72,0xfa,0x40,0x7f,0xc2,0xb1,0x03,0x88,0x68,0x7f,0xf6,0xb4,0x28,0xc0,0x80,0xe3,0x88,0xaa,0xc7,0x40,0xe9,0x50,0xd5,0x41,0x94,0xa2,0x07,0x29,0x87,0x52,0x02,0x07,0x12,0x30,0xc1,0x22,0x16,0x86,0x29,0x01,0xca,0x30,0xf6,0x10,0x39,0xc2,0x23,0x10,0x6c,0x00,0x1d,0x3d,0x10,0x1b,0x02,0xe0,0x41,0x03,0x08,0x75,0x0c,0x60,0x0e,0x4f,0x11,0x0a,0x0c,0x18,0x0e,0x96,0x06,0x28,0x81,0xd3,0x01,0x1f,0x01,0x90,0x1c,0xdc,0xc2,0x01,0x15,0xd0,0x81,0xdc,0x4c,0x30,0x30,0x3f,0x00,0xc4,0x0e,0x30,0x20,0x3c,0x8c,0xc8,0x0f,0x2b,0x41,}; const uint8_t *_I_DolphinFirstStart3_57x48[] = {_I_DolphinFirstStart3_57x48_0}; +const uint8_t _I_DolphinFirstStart7_61x51_0[] = {0x01,0x00,0x13,0x01,0x00,0x17,0x03,0xff,0x01,0x03,0xa4,0xe2,0x01,0x0e,0x03,0xa4,0x1a,0x01,0x30,0x03,0x1e,0x00,0x2a,0x3c,0x00,0x39,0xd0,0x00,0x65,0x03,0x01,0x94,0x80,0x06,0x50,0x40,0x19,0x44,0x00,0x65,0x08,0x01,0xb0,0x2c,0xe2,0x81,0xb6,0x86,0x0a,0xd8,0x7c,0x20,0x75,0x85,0x10,0xcc,0x06,0x50,0x50,0x3b,0x10,0xce,0x00,0x69,0x20,0x79,0x7c,0x20,0x20,0x71,0xc0,0x07,0xca,0xf1,0x02,0x81,0x01,0xc6,0x3a,0x07,0x1f,0xe4,0x10,0x0e,0x53,0xe0,0x38,0xe7,0xa0,0xa0,0x72,0xbb,0x81,0xca,0x12,0x68,0x1c,0x05,0x5c,0x0e,0x3f,0xe8,0xc8,0x1c,0xab,0xe0,0x72,0x94,0x81,0xda,0xb2,0x07,0x5f,0xe0,0x3d,0xbf,0x95,0x44,0x20,0x81,0xce,0xf1,0x2f,0x03,0x94,0xb8,0xae,0x51,0x00,0x39,0x47,0x60,0xd0,0x84,0x70,0x81,0xcb,0x44,0x9d,0x10,0x3a,0x58,0xce,0xe6,0x07,0x29,0x10,0x18,0xa0,0x50,0x88,0x76,0x02,0x22,0x07,0x49,0x8e,0x02,0x24,0x07,0x4e,0x0e,0x02,0x12,0x96,0x38,0x44,0x07,0x02,0x8f,0x1c,0x07,0x1c,0x4e,0x30,0x1c,0x10,0x3c,0x6c,0x13,0x80,0x38,0xc0,0xb0,0x80,0xf1,0x6e,0x90,0x1c,0x71,0x10,0xd7,0x49,0x81,0xc7,0x20,0x0f,0x17,0xe9,0x42,0x20,0x91,0x09,0xeb,0x24,0xe2,0x10,0x49,0x07,0x6f,0xff,0x80,0x56,0x88,0x1c,0xa2,0xae,0xd1,0x66,0x89,0xe0,0x68,0x11,0xb8,0x06,0xc0,0x2e,0x40,0x71,0x9a,0xc0,0x2b,0x00,0x73,0xc0,0x7a,0xe0,0x09,0x12,0x03,0x95,0x57,0xff,0x17,0x03,0x9c,0x03,0x57,0xaa,0x78,0x94,0x40,0xa6,0x35,0x5a,0xac,0x14,0x0e,0x9a,0xad,0x50,0xf8,0x41,0x05,0x00,0x83,0x55,0x14,0x06,0x07,0x18,0x54,0xa0,0x0e,0xb0,0x60,0x31,0xc0,0x00,}; +const uint8_t *_I_DolphinFirstStart7_61x51[] = {_I_DolphinFirstStart7_61x51_0}; + +const uint8_t _I_DolphinFirstStart0_70x53_0[] = {0x01,0x00,0x5a,0x01,0x80,0x60,0x3f,0xf7,0xf0,0x42,0xf8,0x01,0x43,0x07,0x04,0x24,0x72,0x01,0xc0,0x9d,0x82,0x13,0xff,0xff,0xbd,0x70,0x20,0x20,0x72,0xe0,0x40,0x2a,0x11,0xdb,0x00,0x6c,0xec,0x10,0x0d,0x44,0x3a,0x71,0x0e,0x04,0x14,0x42,0x01,0x54,0x86,0xd3,0x27,0x02,0x44,0xd4,0x41,0xb0,0xf2,0x10,0x42,0x55,0x38,0x71,0x1b,0x10,0x18,0xa0,0x41,0x11,0xb1,0xc8,0x28,0x98,0x09,0xfc,0x00,0x72,0x35,0x49,0x8d,0x0b,0xc1,0x70,0xf0,0x10,0x4b,0x51,0x11,0xc2,0x6c,0x0a,0xa3,0x03,0x80,0x7f,0xbf,0xf3,0x08,0x46,0x60,0x90,0x30,0x60,0x50,0xd8,0x2c,0x11,0x0c,0x71,0x5c,0x60,0xf8,0x0f,0xcf,0x3f,0x81,0x80,0xa1,0x9e,0x86,0x0f,0xc0,0x82,0x64,0x30,0x3e,0x09,0x84,0x03,0xf1,0x03,0xa0,0x40,0xa4,0x18,0x39,0xfc,0x20,0x52,0x30,0x19,0x07,0xc6,0x8e,0x4a,0x18,0x22,0x74,0x60,0x1a,0x0f,0xc6,0x3c,0x60,0x5c,0x05,0x28,0xe4,0x3f,0x99,0xf8,0x22,0x28,0x7e,0x05,0x91,0xa8,0x7f,0x23,0xf0,0x59,0x00,0xac,0x63,0xe0,0x81,0xcf,0x4f,0xe0,0xb1,0x81,0x58,0xc3,0xc1,0x08,0x24,0x1f,0xf9,0x68,0x6a,0x1f,0xe9,0xff,0x16,0x02,0x34,0x13,0x50,0x82,0x0a,0xea,0x60,0x1f,0xf9,0xf0,0x41,0x05,0x1d,0x30,0x09,0x18,0x60,0x15,0xa3,0xe8,0x83,0x47,0xe0,0xec,0x2c,0xaf,0xf2,0x0e,0x08,0x1f,0xc1,0x18,0x60,0x1a,0xaf,0xc2,0x6c,0x89,0x62,0x03,0x19,0xad,0xe5,0x70,0x44,0x62,0x80,0x5a,0xa1,0x4f,0x63,0x23,0x0c,0x7a,0xaa,0x4d,0x11,0xe9,0x00,0x06,0x73,0xaa,0x25,0x0a,0x78,0xaf,0x90,0x09,0x25,0x54,0x56,0x5f,0x04,0x30,0xc0,0x64,0x7a,0xa1,0x11,0x7e,0x20,0x18,0x0f,0x3c,0x82,0xaa,0x04,0x18,0x0d,0xf8,0x16,0x33,0xe8,0x84,0xa8,0x08,0x3c,0x33,0x00,0xf0,0x20,0x71,0x08,0xa9,0x38,0x86,0x62,0x62,0x18,0x40,0x44,0x80,0x09,0x04,0x08,0x90,0x01,0x20,0x41,0x17,0x22,0x90,0x01,0x3e,0x00,0x76,0x80,0x1d,0x48,0x00,0x8d,0x91,0x00,0x34,0xf8,0x20,0xe2,0xa7,0x9c,0x06,0x5c,0x11,0x02,0x28,0x5d,0x91,0x35,0x48,0xaf,0xf8,0x04,0x3f,0xf9,0x88,0x20,0x01,}; +const uint8_t *_I_DolphinFirstStart0_70x53[] = {_I_DolphinFirstStart0_70x53_0}; + +const uint8_t _I_DolphinFirstStart4_67x53_0[] = {0x01,0x00,0x1f,0x01,0x00,0x17,0xc3,0xfe,0x08,0x68,0x74,0x02,0x0e,0x07,0x4c,0x04,0x06,0x01,0x18,0x04,0x25,0x00,0x04,0x36,0x00,0x42,0x48,0x02,0x88,0x00,0x28,0x80,0x0c,0xa0,0x40,0x83,0x84,0x00,0xca,0x08,0x08,0x30,0x21,0x83,0x0c,0x2c,0x81,0xe3,0x04,0x20,0xc0,0x80,0x02,0x31,0x32,0x11,0x02,0x27,0x00,0x5d,0x40,0x45,0x87,0x90,0x3e,0x7c,0x00,0x43,0x84,0x4e,0x60,0x43,0x30,0x89,0x82,0x12,0x80,0x15,0x20,0x40,0x99,0xc8,0x22,0x7b,0x88,0x10,0x20,0x82,0x27,0x7c,0x82,0x9d,0x48,0x22,0x5f,0x0d,0xfc,0x08,0x10,0x41,0x12,0xf8,0x57,0xc2,0x28,0x30,0x1e,0x07,0x9e,0x06,0x87,0x25,0x79,0xc4,0x20,0x40,0x83,0x21,0x14,0x22,0x08,0x08,0x38,0x2a,0xb8,0xd9,0x47,0x0a,0x14,0x09,0xf0,0x54,0x47,0x1f,0x81,0x82,0x1a,0xde,0x8e,0x33,0xd1,0xc7,0x81,0x0f,0x0e,0x45,0x18,0x20,0xa1,0xe6,0xf2,0x10,0x89,0xa0,0x70,0x11,0x00,0x41,0x46,0x03,0x86,0x55,0x10,0x40,0xc1,0x82,0x25,0x20,0x04,0x11,0x94,0x80,0x43,0x10,0x84,0x01,0x46,0xc0,0xbd,0x38,0x40,0x20,0x8f,0x49,0x08,0xc4,0x1c,0xc8,0x22,0x50,0x38,0x20,0x20,0x86,0xe4,0x83,0x10,0x41,0x8b,0x87,0xf9,0x03,0x81,0xc0,0x81,0x05,0x81,0xc0,0x40,0xf3,0x90,0x60,0x41,0x70,0x2c,0x17,0x01,0xc0,0xc1,0x41,0x05,0x30,0x98,0x43,0x04,0x65,0x01,0x04,0x0c,0x32,0x38,0x91,0x18,0x04,0x14,0x10,0x38,0x18,0x1e,0xac,0x7c,0x41,0x11,0x88,0x5f,0xfc,0x17,0x55,0xa9,0x82,0x06,0x05,0xbc,0x85,0x02,0x08,0xc6,0x32,0x0f,0xe5,0x5e,0x1a,0x08,0x5c,0x06,0xaa,0x34,0x08,0x4a,0x06,0x02,0xab,0x75,0xf0,0x4f,0xc1,0x05,0x80,0x08,0x8e,0xab,0x7f,0xea,0x04,0x11,0x80,0x6a,0xa0,0x02,0x03,0x08,}; +const uint8_t *_I_DolphinFirstStart4_67x53[] = {_I_DolphinFirstStart4_67x53_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 *_I_PassportBottom_128x17[] = {_I_PassportBottom_128x17_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 *_I_DoorLocked_10x56[] = {_I_DoorLocked_10x56_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 *_I_DoorLeft_70x55[] = {_I_DoorLeft_70x55_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 *_I_PassportLeft_6x47[] = {_I_PassportLeft_6x47_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 *_I_DoorRight_70x55[] = {_I_DoorRight_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 *_I_DoorLocked_10x56[] = {_I_DoorLocked_10x56_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 *_I_PassportLeft_6x47[] = {_I_PassportLeft_6x47_0}; + const uint8_t _I_LockPopup_100x49_0[] = {0x01,0x00,0x37,0x01,0xfc,0x7f,0xc0,0x13,0x01,0xfe,0x03,0x2a,0x07,0x06,0x12,0xd4,0x1a,0x06,0x0c,0xa8,0x60,0x33,0xe0,0x12,0x08,0x40,0x32,0x3f,0xd0,0x70,0x64,0xe0,0x20,0x31,0x8a,0x00,0x32,0x2c,0x10,0x0b,0x00,0x32,0x62,0x10,0x0c,0x06,0x00,0x19,0x00,0x82,0xc0,0x83,0x22,0x08,0x04,0x18,0x11,0x6a,0x01,0x25,0x02,0x84,0x83,0x1e,0x02,0x04,0x10,0xe1,0x03,0x1e,0x3c,0x0c,0x9c,0x1c,0x02,0x43,0x00,0x84,0x4f,0xc1,0x8f,0x80,0xaf,0x40,0x39,0x14,0x00,0x63,0xd0,0x36,0xf0,0x09,0xc6,0x00,0x18,0xd4,0x3a,0x06,0x9c,0x08,0x20,0xc9,0xdf,0xc0,0x20,0x7f,0x00,0x65,0x40,0x3f,0x80,0xc7,0xd0,0x10,0x06,0x01,0x7f,0x06,0x34,0x8e,0xa1,0x3d,0x80,0x70,0x0b,0x4f,0x23,0xd0,0x50,0xa0,0x1f,0x08,0x78,0x66,0x11,0xe3,0xfc,0x83,0x83,0x1e,0x40,0x0c,0x1f,0xfb,0xec,0x41,0x8c,0x03,0x1e,0x07,0x00,0x4d,0x10,0x0a,0x04,0xc0,0x9b,0x30,0x0c,0x1f,0xff,0xff,0x9f,0x06,0x3e,0x01,0x80,0x48,0xe7,0x99,0x83,0x0d,0x6a,0xe0,0xc4,0x90,0x03,0x1a,0x76,0x0c,0x38,0xe0,0x34,0x45,0x25,0x02,0x06,0x0d,0xe0,0x18,0x3c,0x08,0x19,0x40,0x78,0x00,0xc1,0x81,0xc3,0x27,0xf8,0x48,0x26,0x82,0x7d,0x00,0xfc,0x40,0xfc,0x10,0xfc,0x04,0xfc,0x18,0x30,0x28,0x7d,0x02,0x3f,0x00,0x98,0x41,0x38,0x31,0x08,0x25,0x0e,0x19,0x1f,0x81,0x42,0x70,0x11,0xa2,0x08,0xe2,0x30,0x72,0x08,0x76,0x0a,0x19,0x0f,0x85,0x42,0x60,0x11,0x51,0x78,0xc2,0x20,0x32,0x08,0x26,0x00,0x18,0x91,0x00,0x60,0x91,0x44,0x08,0x34,0x08,0x64,0x1f,0xe4,0x07,0x3f,0x84,0x0d,0x58,0x44,0x01,0x83,0xdc,0x60,0x43,0xe1,0x39,0xa9,0xd0,0x60,0x70,0x16,0x78,0xca,0x01,0x8f,0x83,0x3d,0x10,0x33,0x29,0x00,0xc7,0xa1,0x83,0x3f,0x10,0x0c,0x79,0x30,0x32,0xa0,0xdf,0xc7,0xa0,0x80,0x22,0x07,0xf8,0x06,0x54,0x04,}; const uint8_t *_I_LockPopup_100x49[] = {_I_LockPopup_100x49_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 *_I_Mute_25x27[] = {_I_Mute_25x27_0}; - -const uint8_t _I_IrdaArrowUp_4x8_0[] = {0x00,0x18,0x3C,0x7E,0xFF,}; -const uint8_t *_I_IrdaArrowUp_4x8[] = {_I_IrdaArrowUp_4x8_0}; - -const uint8_t _I_Up_hvr_25x27_0[] = {0x01,0x00,0x39,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3c,0xf7,0x80,0xcb,0x8e,0x03,0x2c,0x18,0x0c,0x80,0x26,0x25,0x18,0x08,0xa4,0x7f,0x90,0x11,0x88,0xfe,0x20,0x31,0xf8,0x07,0xc2,0x03,0x0f,0x80,0x78,0x00,0x68,0x37,0xf0,0x1d,0x95,0xcc,0xbe,0x66,0x73,}; -const uint8_t *_I_Up_hvr_25x27[] = {_I_Up_hvr_25x27_0}; - -const uint8_t _I_Mute_hvr_25x27_0[] = {0x01,0x00,0x4a,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x21,0xfe,0x40,0x7b,0xf7,0xff,0x5c,0x07,0x7f,0xbf,0xf9,0xc0,0x6f,0xfd,0xff,0xd8,0x3c,0x7c,0x1f,0x90,0x38,0xff,0x7f,0x40,0x31,0xbd,0x82,0xc6,0xff,0xb7,0x01,0x97,0x3c,0x06,0xc0,0xb3,0x09,0x98,0x6c,0x84,0x68,0x2b,0x21,0x99,0x8e,0xcc,0x86,0x64,0xb5,0x01,0x89,0x5c,0xcb,0xe6,0x67,0x30,}; -const uint8_t *_I_Mute_hvr_25x27[] = {_I_Mute_hvr_25x27_0}; - -const uint8_t _I_Vol_down_25x27_0[] = {0x01,0x00,0x2c,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x3f,0x01,0xff,0x07,0xff,0x07,0x01,0xa0,0x5f,0xc0,0x7e,0x03,0x38,0x19,0x4c,0x60,0x30,0x68,0x07,0x02,0x01,0xfc,0xff,0xdf,0xcc,0xe8,}; -const uint8_t *_I_Vol_down_25x27[] = {_I_Vol_down_25x27_0}; - -const uint8_t _I_Down_25x27_0[] = {0x01,0x00,0x46,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x3f,0x01,0x9f,0xc7,0xff,0x1f,0x01,0xa7,0x87,0xff,0x0f,0x80,0xf0,0x7f,0xf0,0x78,0x0e,0x07,0xff,0x03,0x0b,0x8f,0xfc,0x04,0x30,0x1f,0xf0,0x7c,0xaf,0x80,0x32,0x9c,0x00,0xca,0x20,0x37,0xf0,0x18,0xc0,0xca,0x63,0x01,0x83,0x40,0x38,0x10,0x0f,0xe7,0xfe,0xfe,0x67,0x40,}; -const uint8_t *_I_Down_25x27[] = {_I_Down_25x27_0}; - -const uint8_t _I_Power_hvr_25x27_0[] = {0x01,0x00,0x4b,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x3f,0xff,0x78,0x0c,0xb8,0xe0,0x35,0xbf,0xf1,0xbf,0x90,0x19,0xff,0x1b,0xf1,0x01,0x8f,0xf1,0xfe,0x30,0x1c,0xff,0x1f,0xe6,0x03,0x5f,0x78,0x0c,0xbf,0xe0,0x39,0x8f,0xff,0xc3,0x63,0x3f,0xff,0x08,0xc6,0xff,0x7c,0x15,0x89,0x04,0x7f,0xc0,0x31,0xc1,0x8e,0xc8,0x8e,0x60,0x36,0x2b,0x99,0x7c,0xcc,0xe6,}; -const uint8_t *_I_Power_hvr_25x27[] = {_I_Power_hvr_25x27_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 *_I_IrdaLearnShort_128x31[] = {_I_IrdaLearnShort_128x31_0}; - -const uint8_t _I_IrdaArrowDown_4x8_0[] = {0x00,0xFF,0x7E,0x3C,0x18,}; -const uint8_t *_I_IrdaArrowDown_4x8[] = {_I_IrdaArrowDown_4x8_0}; - -const uint8_t _I_Vol_down_hvr_25x27_0[] = {0x01,0x00,0x23,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3f,0x01,0xf8,0xb4,0x7f,0x00,0x34,0x0b,0xf8,0x0f,0xc0,0x6e,0x57,0x32,0xf9,0x99,0xcc,}; -const uint8_t *_I_Vol_down_hvr_25x27[] = {_I_Vol_down_hvr_25x27_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 *_I_IrdaLearn_128x64[] = {_I_IrdaLearn_128x64_0}; - const uint8_t _I_Down_hvr_25x27_0[] = {0x01,0x00,0x3a,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3f,0x01,0x9c,0x3e,0x01,0xe0,0x01,0xa4,0x7e,0x01,0xf0,0x80,0x8b,0x47,0xf1,0x01,0x16,0x8f,0xf0,0x2e,0x23,0x11,0x01,0x88,0x04,0xf0,0x60,0x32,0xe3,0x80,0xcb,0xde,0x37,0xf0,0x1a,0x95,0xcc,0xbe,0x66,0x73,}; const uint8_t *_I_Down_hvr_25x27[] = {_I_Down_hvr_25x27_0}; +const uint8_t _I_Vol_down_hvr_25x27_0[] = {0x01,0x00,0x23,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3f,0x01,0xf8,0xb4,0x7f,0x00,0x34,0x0b,0xf8,0x0f,0xc0,0x6e,0x57,0x32,0xf9,0x99,0xcc,}; +const uint8_t *_I_Vol_down_hvr_25x27[] = {_I_Vol_down_hvr_25x27_0}; + +const uint8_t _I_Down_25x27_0[] = {0x01,0x00,0x46,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x3f,0x01,0x9f,0xc7,0xff,0x1f,0x01,0xa7,0x87,0xff,0x0f,0x80,0xf0,0x7f,0xf0,0x78,0x0e,0x07,0xff,0x03,0x0b,0x8f,0xfc,0x04,0x30,0x1f,0xf0,0x7c,0xaf,0x80,0x32,0x9c,0x00,0xca,0x20,0x37,0xf0,0x18,0xc0,0xca,0x63,0x01,0x83,0x40,0x38,0x10,0x0f,0xe7,0xfe,0xfe,0x67,0x40,}; +const uint8_t *_I_Down_25x27[] = {_I_Down_25x27_0}; + const uint8_t _I_Fill_marker_7x7_0[] = {0x00,0x1C,0x32,0x6F,0x5F,0x7F,0x3E,0x1C,}; const uint8_t *_I_Fill_marker_7x7[] = {_I_Fill_marker_7x7_0}; -const uint8_t _I_Power_25x27_0[] = {0x01,0x00,0x54,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x30,0x18,0x80,0x0c,0xa7,0x00,0x35,0xc0,0xce,0x60,0x70,0x1e,0x0c,0xe6,0x0f,0x01,0xf0,0xce,0x21,0xd0,0x1b,0x0c,0xe2,0x18,0x03,0x58,0x80,0x0c,0xa0,0x00,0x39,0xf0,0xc0,0x03,0x63,0xc1,0x80,0x88,0xc7,0x03,0x83,0x15,0x8c,0x07,0xfe,0x02,0x18,0x0d,0xf0,0x76,0x44,0x73,0x01,0x94,0x0c,0xa6,0x30,0x18,0x34,0x03,0x81,0x00,0xfe,0x7f,0xef,0xe6,0x74,}; -const uint8_t *_I_Power_25x27[] = {_I_Power_25x27_0}; +const uint8_t _I_Vol_down_25x27_0[] = {0x01,0x00,0x2c,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x3f,0x01,0xff,0x07,0xff,0x07,0x01,0xa0,0x5f,0xc0,0x7e,0x03,0x38,0x19,0x4c,0x60,0x30,0x68,0x07,0x02,0x01,0xfc,0xff,0xdf,0xcc,0xe8,}; +const uint8_t *_I_Vol_down_25x27[] = {_I_Vol_down_25x27_0}; const uint8_t _I_Vol_up_25x27_0[] = {0x01,0x00,0x2f,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x38,0x88,0x00,0xfc,0x06,0xbc,0x1f,0xfc,0x1c,0x06,0x81,0x7f,0x01,0xc1,0x0e,0xa0,0x65,0x31,0x80,0xc1,0xa0,0x1c,0x08,0x07,0xf3,0xff,0x7f,0x33,0xa0,}; const uint8_t *_I_Vol_up_25x27[] = {_I_Vol_up_25x27_0}; -const uint8_t _I_Up_25x27_0[] = {0x01,0x00,0x44,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x3c,0x88,0x00,0xca,0x70,0x03,0x2b,0xe0,0x0c,0xbf,0xc0,0x32,0xff,0x80,0x87,0x03,0xff,0x81,0xc0,0x78,0x3f,0xf8,0x3c,0x07,0xc3,0xff,0x87,0xc0,0x7e,0x3f,0xf8,0xf8,0x0d,0x06,0xfe,0x03,0x78,0x19,0x4c,0x60,0x30,0x68,0x07,0x02,0x01,0xfc,0xff,0xdf,0xcc,0xe8,}; -const uint8_t *_I_Up_25x27[] = {_I_Up_25x27_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 *_I_Back_15x10[] = {_I_Back_15x10_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 *_I_IrdaSend_128x64[] = {_I_IrdaSend_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 *_I_IrdaSendShort_128x34[] = {_I_IrdaSendShort_128x34_0}; +const uint8_t _I_Up_hvr_25x27_0[] = {0x01,0x00,0x39,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3c,0xf7,0x80,0xcb,0x8e,0x03,0x2c,0x18,0x0c,0x80,0x26,0x25,0x18,0x08,0xa4,0x7f,0x90,0x11,0x88,0xfe,0x20,0x31,0xf8,0x07,0xc2,0x03,0x0f,0x80,0x78,0x00,0x68,0x37,0xf0,0x1d,0x95,0xcc,0xbe,0x66,0x73,}; +const uint8_t *_I_Up_hvr_25x27[] = {_I_Up_hvr_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 *_I_Vol_up_hvr_25x27[] = {_I_Vol_up_hvr_25x27_0}; -const uint8_t _I_KeySave_24x11_0[] = {0x01,0x00,0x1e,0x00,0xff,0x7f,0xff,0xf0,0x18,0x06,0x00,0x04,0x53,0x1c,0xbe,0x33,0x13,0x94,0xc9,0x64,0x72,0x99,0xed,0x0e,0x53,0x05,0x19,0xb3,0xe3,0x02,0x8a,0x1d,0x1b,0xf8,}; -const uint8_t *_I_KeySave_24x11[] = {_I_KeySave_24x11_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 *_I_IrdaLearnShort_128x31[] = {_I_IrdaLearnShort_128x31_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 *_I_KeyBackspaceSelected_16x9[] = {_I_KeyBackspaceSelected_16x9_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 *_I_IrdaSend_128x64[] = {_I_IrdaSend_128x64_0}; + +const uint8_t _I_DolphinReadingSuccess_59x63_0[] = {0x01,0x00,0x19,0x01,0x00,0x1d,0x00,0x0f,0xd2,0x00,0x21,0xe0,0x3f,0xf0,0xf9,0x00,0x40,0xee,0x00,0x11,0x88,0x04,0x0e,0x18,0x11,0x18,0x8c,0x40,0x0e,0x50,0x30,0x10,0xc0,0xa1,0x01,0xe2,0x05,0x14,0x12,0x08,0x33,0x58,0x44,0x08,0x66,0xa1,0xe3,0x01,0x9c,0x83,0x00,0x24,0x11,0x11,0x06,0xc4,0x76,0x20,0x75,0x15,0x99,0x48,0xc0,0xe9,0x0f,0x03,0x95,0xfc,0x86,0x3c,0x09,0x80,0x1c,0x7c,0x00,0x91,0x81,0x48,0x2f,0xc1,0x41,0x8c,0xc0,0x20,0x30,0x1c,0x87,0xfc,0x0e,0x30,0x70,0x70,0x81,0xc7,0xe6,0x07,0x18,0x08,0x1c,0xb9,0x1e,0x38,0x0f,0x02,0x01,0xf0,0x03,0xa0,0xa4,0x7f,0x90,0x30,0x38,0xff,0xe0,0x28,0x21,0xff,0x06,0x44,0x0e,0x46,0xe1,0x01,0x8c,0x03,0x34,0x2f,0x25,0x18,0x80,0xc7,0x2a,0x03,0x2e,0x01,0x3c,0x70,0x12,0xa2,0x39,0x78,0x27,0xe0,0x31,0xea,0x82,0xc4,0x6c,0x31,0xf0,0x78,0xea,0xb0,0x22,0x31,0xfc,0x1a,0xc6,0x01,0x55,0x25,0x88,0xf8,0x4b,0x02,0x1f,0x13,0xe1,0x7f,0x97,0x85,0x15,0x03,0x90,0xf8,0xa0,0x10,0xa1,0xb1,0x0e,0x88,0x00,0x7f,0x0f,0xc0,0x7c,0x57,0x27,0x3c,0xb0,0x7f,0x5f,0xa9,0x1f,0xc0,0x6a,0xc5,0x05,0xc0,0xf0,0x11,0x46,0xac,0x18,0x3f,0xf9,0x54,0x75,0x00,0x73,0x1f,0x0f,0xfe,0xfe,0xc6,0x30,0x01,0xbc,0x48,0x00,0x84,0x82,0x00,0x1b,0x64,0xc0,0x07,0x60,0x03,0xb4,0x70,0x0c,0xbf,0x82,0x31,0x01,0x8d,0x0c,0x40,0x02,0x37,0x08,0x1d,0x74,0x00,0x76,0xa0,0x01,0xdb,0x01,0xfe,0x85,0x8b,0x96,0xaa,0x9b,0x30,0x01,0x6a,0xa3,0x40,0x75,0xaa,0x03,0xdb,0x50,0xbb,0x30,0x01,0x54,0x24,0x25,0xe6,0x51,0x08,0x1f,0x68,0x00,0x7f,0x03,0xf2,0x79,0xc0,0xf4,}; +const uint8_t *_I_DolphinReadingSuccess_59x63[] = {_I_DolphinReadingSuccess_59x63_0}; + +const uint8_t _I_Mute_hvr_25x27_0[] = {0x01,0x00,0x4a,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x21,0xfe,0x40,0x7b,0xf7,0xff,0x5c,0x07,0x7f,0xbf,0xf9,0xc0,0x6f,0xfd,0xff,0xd8,0x3c,0x7c,0x1f,0x90,0x38,0xff,0x7f,0x40,0x31,0xbd,0x82,0xc6,0xff,0xb7,0x01,0x97,0x3c,0x06,0xc0,0xb3,0x09,0x98,0x6c,0x84,0x68,0x2b,0x21,0x99,0x8e,0xcc,0x86,0x64,0xb5,0x01,0x89,0x5c,0xcb,0xe6,0x67,0x30,}; +const uint8_t *_I_Mute_hvr_25x27[] = {_I_Mute_hvr_25x27_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 *_I_Back_15x10[] = {_I_Back_15x10_0}; + +const uint8_t _I_Up_25x27_0[] = {0x01,0x00,0x44,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x20,0x3c,0x88,0x00,0xca,0x70,0x03,0x2b,0xe0,0x0c,0xbf,0xc0,0x32,0xff,0x80,0x87,0x03,0xff,0x81,0xc0,0x78,0x3f,0xf8,0x3c,0x07,0xc3,0xff,0x87,0xc0,0x7e,0x3f,0xf8,0xf8,0x0d,0x06,0xfe,0x03,0x78,0x19,0x4c,0x60,0x30,0x68,0x07,0x02,0x01,0xfc,0xff,0xdf,0xcc,0xe8,}; +const uint8_t *_I_Up_25x27[] = {_I_Up_25x27_0}; + +const uint8_t _I_IrdaArrowUp_4x8_0[] = {0x00,0x18,0x3C,0x7E,0xFF,}; +const uint8_t *_I_IrdaArrowUp_4x8[] = {_I_IrdaArrowUp_4x8_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 *_I_Mute_25x27[] = {_I_Mute_25x27_0}; + +const uint8_t _I_Power_25x27_0[] = {0x01,0x00,0x54,0x00,0xfc,0x7f,0xe7,0xf0,0x08,0x24,0x02,0x81,0x00,0x81,0x40,0x30,0x10,0x08,0x08,0x38,0x60,0x30,0x18,0x80,0x0c,0xa7,0x00,0x35,0xc0,0xce,0x60,0x70,0x1e,0x0c,0xe6,0x0f,0x01,0xf0,0xce,0x21,0xd0,0x1b,0x0c,0xe2,0x18,0x03,0x58,0x80,0x0c,0xa0,0x00,0x39,0xf0,0xc0,0x03,0x63,0xc1,0x80,0x88,0xc7,0x03,0x83,0x15,0x8c,0x07,0xfe,0x02,0x18,0x0d,0xf0,0x76,0x44,0x73,0x01,0x94,0x0c,0xa6,0x30,0x18,0x34,0x03,0x81,0x00,0xfe,0x7f,0xef,0xe6,0x74,}; +const uint8_t *_I_Power_25x27[] = {_I_Power_25x27_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 *_I_IrdaSendShort_128x34[] = {_I_IrdaSendShort_128x34_0}; + +const uint8_t _I_IrdaArrowDown_4x8_0[] = {0x00,0xFF,0x7E,0x3C,0x18,}; +const uint8_t *_I_IrdaArrowDown_4x8[] = {_I_IrdaArrowDown_4x8_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 *_I_IrdaLearn_128x64[] = {_I_IrdaLearn_128x64_0}; + +const uint8_t _I_Power_hvr_25x27_0[] = {0x01,0x00,0x4b,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x3f,0xff,0x78,0x0c,0xb8,0xe0,0x35,0xbf,0xf1,0xbf,0x90,0x19,0xff,0x1b,0xf1,0x01,0x8f,0xf1,0xfe,0x30,0x1c,0xff,0x1f,0xe6,0x03,0x5f,0x78,0x0c,0xbf,0xe0,0x39,0x8f,0xff,0xc3,0x63,0x3f,0xff,0x08,0xc6,0xff,0x7c,0x15,0x89,0x04,0x7f,0xc0,0x31,0xc1,0x8e,0xc8,0x8e,0x60,0x36,0x2b,0x99,0x7c,0xcc,0xe6,}; +const uint8_t *_I_Power_hvr_25x27[] = {_I_Power_hvr_25x27_0}; const uint8_t _I_KeySaveSelected_24x11_0[] = {0x01,0x00,0x1a,0x00,0xff,0x7f,0xc0,0x0d,0xcf,0xb4,0x7c,0xee,0xf6,0xbf,0x6d,0xbe,0xd7,0xe1,0xaf,0xda,0xff,0xbe,0x7c,0xc7,0xcc,0x28,0xa1,0xd1,0xbf,0x80,}; const uint8_t *_I_KeySaveSelected_24x11[] = {_I_KeySaveSelected_24x11_0}; @@ -202,6 +199,12 @@ const uint8_t *_I_KeySaveSelected_24x11[] = {_I_KeySaveSelected_24x11_0}; const uint8_t _I_KeyBackspace_16x9_0[] = {0x00,0xFE,0x7F,0x01,0x80,0x11,0x80,0x19,0x80,0xFD,0xBF,0x19,0x80,0x11,0x80,0x01,0x80,0xFE,0x7F,}; const uint8_t *_I_KeyBackspace_16x9[] = {_I_KeyBackspace_16x9_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 *_I_KeyBackspaceSelected_16x9[] = {_I_KeyBackspaceSelected_16x9_0}; + +const uint8_t _I_KeySave_24x11_0[] = {0x01,0x00,0x1e,0x00,0xff,0x7f,0xff,0xf0,0x18,0x06,0x00,0x04,0x53,0x1c,0xbe,0x33,0x13,0x94,0xc9,0x64,0x72,0x99,0xed,0x0e,0x53,0x05,0x19,0xb3,0xe3,0x02,0x8a,0x1d,0x1b,0xf8,}; +const uint8_t *_I_KeySave_24x11[] = {_I_KeySave_24x11_0}; + const uint8_t _A_125khz_14_0[] = {0x00,0x80,0x07,0x00,0x08,0x00,0x13,0x00,0x24,0x0E,0x28,0x71,0x28,0x85,0x21,0x01,0x02,0x62,0x02,0x92,0x02,0x92,0x02,0x64,0x02,0x04,0x01,0xF8,0x00,}; const uint8_t _A_125khz_14_1[] = {0x00,0x80,0x07,0x00,0x08,0x00,0x10,0x00,0x20,0x0E,0x20,0x71,0x20,0x85,0x21,0x01,0x02,0x62,0x02,0x92,0x02,0x92,0x02,0x64,0x02,0x04,0x01,0xF8,0x00,}; const uint8_t _A_125khz_14_2[] = {0x01,0x00,0x17,0x00,0x00,0x3c,0x3a,0x01,0x71,0x80,0x61,0x60,0x30,0x18,0x15,0x8a,0x05,0x92,0x00,0x95,0x92,0x05,0x04,0x80,0xfe,0x20,0x00,}; @@ -344,45 +347,45 @@ const uint8_t *_I_Detailed_chip_17x13[] = {_I_Detailed_chip_17x13_0}; const uint8_t _I_Medium_chip_22x21_0[] = {0x01,0x00,0x35,0x00,0xfe,0x7f,0xe1,0xf0,0x28,0x04,0x43,0xf3,0xff,0x93,0xe1,0x6a,0x52,0x8e,0x2f,0xfe,0x51,0x25,0x80,0x4a,0x72,0xb6,0x79,0x55,0x76,0xc1,0x2e,0xaa,0xc0,0x25,0x51,0xdc,0x00,0x14,0x70,0x00,0x56,0xae,0x81,0x47,0x2b,0x7d,0x95,0x07,0x48,0x46,0x42,0x92,0x17,0x90,0xd4,0x87,0x64,}; const uint8_t *_I_Medium_chip_22x21[] = {_I_Medium_chip_22x21_0}; -const uint8_t _I_Health_16x16_0[] = {0x01,0x00,0x12,0x00,0x00,0x2f,0x02,0x03,0x40,0x00,0x95,0xe2,0x1f,0x08,0x84,0x00,0xc4,0x12,0x60,0xf1,0x0c,0xb8,}; -const uint8_t *_I_Health_16x16[] = {_I_Health_16x16_0}; +const uint8_t _I_BatteryBody_52x28_0[] = {0x01,0x00,0x45,0x00,0xe0,0x7f,0x3f,0xe0,0x02,0x87,0xf0,0x21,0xe0,0xc3,0x84,0x50,0x39,0xbf,0xff,0x27,0xfe,0xf3,0x09,0xe0,0x42,0x81,0xab,0x0d,0x03,0x1c,0x2b,0xfc,0x0d,0x48,0x55,0xdc,0x1a,0x90,0x8f,0x18,0x6d,0x41,0xaa,0x1b,0x71,0x4b,0x0d,0xd4,0x1b,0xe0,0xdf,0x1b,0xd5,0xfc,0x1a,0xa5,0x36,0x06,0xac,0x20,0xa7,0xe0,0xdc,0xa5,0x7c,0x7c,0xb7,0xff,0xb4,0x21,0x5c,0xcb,0xc6,}; +const uint8_t *_I_BatteryBody_52x28[] = {_I_BatteryBody_52x28_0}; const uint8_t _I_FaceCharging_29x14_0[] = {0x01,0x00,0x28,0x00,0xa0,0x00,0x86,0x05,0x60,0x01,0x8c,0x0e,0x61,0x00,0xc0,0x40,0x63,0x10,0x0e,0x04,0x03,0xf9,0x00,0xf0,0x41,0xc0,0x66,0x13,0xb8,0x40,0x94,0xc0,0x07,0x04,0x82,0x00,0xc6,0x11,0x02,0x01,0x8f,0xc2,0x03,0x00,}; const uint8_t *_I_FaceCharging_29x14[] = {_I_FaceCharging_29x14_0}; -const uint8_t _I_BatteryBody_52x28_0[] = {0x01,0x00,0x45,0x00,0xe0,0x7f,0x3f,0xe0,0x02,0x87,0xf0,0x21,0xe0,0xc3,0x84,0x50,0x39,0xbf,0xff,0x27,0xfe,0xf3,0x09,0xe0,0x42,0x81,0xab,0x0d,0x03,0x1c,0x2b,0xfc,0x0d,0x48,0x55,0xdc,0x1a,0x90,0x8f,0x18,0x6d,0x41,0xaa,0x1b,0x71,0x4b,0x0d,0xd4,0x1b,0xe0,0xdf,0x1b,0xd5,0xfc,0x1a,0xa5,0x36,0x06,0xac,0x20,0xa7,0xe0,0xdc,0xa5,0x7c,0x7c,0xb7,0xff,0xb4,0x21,0x5c,0xcb,0xc6,}; -const uint8_t *_I_BatteryBody_52x28[] = {_I_BatteryBody_52x28_0}; - -const uint8_t _I_Voltage_16x16_0[] = {0x01,0x00,0x1a,0x00,0x00,0x24,0x0a,0x01,0x03,0xc0,0x40,0x78,0x10,0x1f,0x04,0x03,0xe1,0x07,0xc0,0x40,0xc0,0xe3,0xc0,0x80,0x58,0x20,0x12,0x00,0xd3,0x00,}; -const uint8_t *_I_Voltage_16x16[] = {_I_Voltage_16x16_0}; +const uint8_t _I_Health_16x16_0[] = {0x01,0x00,0x12,0x00,0x00,0x2f,0x02,0x03,0x40,0x00,0x95,0xe2,0x1f,0x08,0x84,0x00,0xc4,0x12,0x60,0xf1,0x0c,0xb8,}; +const uint8_t *_I_Health_16x16[] = {_I_Health_16x16_0}; const uint8_t _I_Temperature_16x16_0[] = {0x01,0x00,0x12,0x00,0x00,0x1e,0x02,0x01,0x40,0x80,0x80,0x66,0x41,0x02,0xf0,0x40,0xc0,0x23,0xc0,0x80,0x86,0xd4,}; const uint8_t *_I_Temperature_16x16[] = {_I_Temperature_16x16_0}; -const uint8_t _I_FaceNopower_29x14_0[] = {0x01,0x00,0x24,0x00,0x00,0x1f,0x02,0x01,0x60,0x01,0xa7,0x80,0x02,0x57,0xe0,0x48,0xc3,0xe7,0xd0,0x0c,0x04,0x3c,0x39,0x1f,0x88,0x18,0x0c,0x61,0x90,0x60,0x18,0xff,0x82,0x44,0x03,0x38,0x74,0x38,0x2c,0x80,}; -const uint8_t *_I_FaceNopower_29x14[] = {_I_FaceNopower_29x14_0}; - -const uint8_t _I_FaceNormal_29x14_0[] = {0x01,0x00,0x1e,0x00,0x00,0x1c,0xf2,0x01,0x80,0x83,0xd7,0xa0,0x1c,0x08,0x5d,0xf8,0x06,0x30,0xf0,0x1b,0x84,0xcc,0x41,0x10,0x88,0x10,0x0e,0x62,0x10,0x10,0x18,0xf8,0x00,0x42,}; -const uint8_t *_I_FaceNormal_29x14[] = {_I_FaceNormal_29x14_0}; - const uint8_t _I_Battery_16x16_0[] = {0x01,0x00,0x12,0x00,0x00,0x1e,0x02,0x03,0xc0,0x81,0xc8,0x20,0x80,0x11,0xd0,0x41,0x40,0x72,0x11,0x10,0xda,0x80,}; const uint8_t *_I_Battery_16x16[] = {_I_Battery_16x16_0}; const uint8_t _I_FaceConfused_29x14_0[] = {0x01,0x00,0x30,0x00,0xc0,0x00,0x46,0x1f,0x38,0x80,0xd0,0x22,0x14,0x48,0x0c,0x82,0x0f,0x52,0x80,0xe8,0x21,0x14,0xa0,0x18,0xc2,0xa6,0x59,0x19,0x24,0x27,0x09,0x48,0xa1,0x41,0x2f,0x12,0x4c,0x0c,0x0c,0x51,0x1f,0xc8,0x78,0x0c,0x7f,0xd1,0xf0,0x18,0xc3,0xa3,0x00,0x74,}; const uint8_t *_I_FaceConfused_29x14[] = {_I_FaceConfused_29x14_0}; -const uint8_t _I_RFIDDolphinSuccess_108x57_0[] = {0x01,0x00,0xe7,0x01,0x00,0x0f,0x03,0xff,0x1f,0x06,0xd4,0xe2,0x01,0xe0,0x06,0xd4,0x18,0x04,0x30,0x30,0x64,0x60,0x20,0x20,0x31,0x86,0x03,0x62,0x80,0x03,0x28,0x80,0x36,0x24,0x00,0x36,0x00,0x28,0x5c,0xc3,0xe6,0x00,0x58,0x40,0xec,0xc1,0xb1,0x04,0x02,0x19,0x24,0x80,0x0b,0x02,0x02,0x40,0x37,0xc4,0x8c,0x2e,0x40,0x6f,0x93,0x8b,0x81,0x07,0x06,0xdc,0xc2,0x38,0x66,0x50,0x6a,0xe2,0x27,0xe0,0xd2,0xfc,0x08,0x09,0x0c,0x9c,0x4b,0x98,0x34,0xa0,0xe1,0xd5,0x06,0x8f,0x92,0xc2,0x05,0x1e,0x42,0xe1,0x81,0xa3,0xe2,0xf0,0xbc,0x4c,0x1a,0xff,0x2f,0x9b,0x80,0xd8,0xca,0x05,0x1f,0x97,0xfd,0xf8,0x60,0xd2,0x01,0x1e,0x00,0x1a,0x5c,0x00,0x08,0xc9,0xc1,0xab,0x40,0xf9,0x83,0x46,0x61,0x00,0xd8,0x4a,0x81,0xab,0xa0,0xf3,0x5f,0xc6,0x05,0x58,0x8a,0xa4,0x09,0x76,0x21,0xb1,0xf2,0x83,0x4f,0x5d,0x1a,0x01,0x8c,0x90,0x1a,0x31,0x0d,0x07,0xa9,0x16,0x50,0x0a,0xac,0x34,0xba,0x42,0xa1,0x88,0x50,0x23,0xaa,0x72,0xe0,0x6a,0xa1,0x4a,0x32,0x39,0x88,0x6c,0x60,0xc7,0x82,0xb0,0x55,0x60,0xa2,0x92,0x80,0xc0,0x43,0x63,0x03,0x25,0x96,0xe3,0x54,0x33,0x18,0xc4,0x90,0x22,0x21,0x81,0x81,0x03,0x4a,0xa9,0x55,0x7a,0x17,0xf3,0x82,0x9f,0x6d,0x5e,0xa9,0xb6,0x50,0x38,0x70,0x35,0x70,0x15,0x5a,0xa9,0xb8,0xa3,0x46,0x12,0x06,0x9f,0x83,0x54,0x8a,0x28,0x80,0x34,0xfc,0x08,0x93,0xaa,0xc7,0x40,0x83,0x83,0x81,0xd3,0xa1,0xd1,0x08,0x84,0x0c,0x24,0x3f,0xed,0x54,0x18,0x26,0x50,0x20,0xd9,0x42,0x21,0x90,0x4c,0x07,0xff,0xae,0x52,0x20,0x6a,0xc4,0x23,0x1f,0x88,0x3f,0xf0,0x1a,0x45,0x31,0xe7,0x03,0x4a,0x41,0xe0,0x69,0x0f,0xc2,0x1e,0x0d,0x19,0x80,0x48,0xa2,0x10,0xc5,0x68,0xdf,0x0a,0x82,0xb9,0x28,0x22,0x2c,0xe3,0x0a,0xd1,0x2b,0x0f,0x00,0x3c,0x22,0x91,0x53,0x9c,0x50,0x1a,0x30,0x08,0x39,0x1c,0x60,0x6d,0x12,0x3d,0x8c,0xc2,0x51,0x00,0x17,0x0c,0xe2,0x01,0xff,0x83,0x84,0xc6,0x40,0xb0,0x19,0x84,0xd0,0x1a,0x5c,0x08,0x1f,0xf8,0x8c,0x50,0x43,0x08,0xce,0x2d,0x06,0x71,0x5f,0x17,0xfe,0x12,0xdf,0x20,0x69,0x55,0x01,0xa6,0x00,0x18,0x40,0xa4,0x80,0x63,0x3c,0xb5,0x03,0x56,0x08,0x8b,0x20,0x10,0xcf,0x03,0x62,0x08,0x20,0x00,0x94,0xc6,0x01,0x70,0x01,0x0c,0xe8,0x36,0x20,0xd3,0xe0,0x00,0xcb,0x10,0x02,0x19,0xf3,0x9c,0x41,0xa3,0x15,0x31,0x90,0x00,0x70,0xc0,0x21,0xdd,0x86,0xc4,0x78,0x3e,0xa3,0x71,0xe0,0x30,0x20,0x31,0xbe,0x86,0xc4,0x1a,0x35,0x40,0x20,0x8d,0x89,0x28,0x5b,0xa0,0xd9,0xea,0x3d,0x44,0x42,0x87,0x83,0x48,0x36,0x49,0xe1,0xa0,0x75,0x67,0x8d,0x41,0x54,0x14,0x03,0xf5,0x2a,0x06,0x96,0x03,0x54,0xc4,0x14,0xd0,0x83,0x4a,0xfb,0x35,0x06,0x90,0x38,0x4e,0x46,0xb4,0x10,0xd9,0x81,0x49,0x72,0x40,0x01,0x0a,0x95,0xd4,0x36,0x20,0xd7,0x55,0x10,}; -const uint8_t *_I_RFIDDolphinSuccess_108x57[] = {_I_RFIDDolphinSuccess_108x57_0}; +const uint8_t _I_FaceNormal_29x14_0[] = {0x01,0x00,0x1e,0x00,0x00,0x1c,0xf2,0x01,0x80,0x83,0xd7,0xa0,0x1c,0x08,0x5d,0xf8,0x06,0x30,0xf0,0x1b,0x84,0xcc,0x41,0x10,0x88,0x10,0x0e,0x62,0x10,0x10,0x18,0xf8,0x00,0x42,}; +const uint8_t *_I_FaceNormal_29x14[] = {_I_FaceNormal_29x14_0}; -const uint8_t _I_RFIDBigChip_37x36_0[] = {0x01,0x00,0x6e,0x00,0x83,0x01,0x0f,0xcd,0xff,0x00,0x0c,0x1e,0x24,0x08,0x28,0x47,0x24,0x12,0x51,0x39,0x28,0x24,0xa2,0x91,0x5e,0x07,0xab,0xfe,0x04,0x1c,0x04,0xaa,0x01,0x15,0x02,0x28,0x4c,0x81,0x2c,0x04,0x4e,0x05,0xfc,0x08,0x35,0x59,0x06,0x02,0x81,0x15,0xca,0xe4,0x26,0xf2,0x10,0x70,0xd7,0x66,0x11,0x70,0x70,0xd4,0x20,0x14,0x10,0x70,0xc7,0x68,0x13,0x70,0x70,0xd4,0x28,0x10,0x10,0x4a,0x84,0xc6,0x80,0x13,0x10,0xe8,0xd0,0x03,0xa2,0x27,0x19,0xf0,0x9c,0x46,0x28,0x3b,0x42,0xcf,0x96,0x6a,0xd4,0x13,0x6f,0x2a,0x2c,0xa2,0x90,0x54,0x59,0xfe,0x52,0xa7,0x02,0x4f,0x9f,0xf1,0x52,0x60,}; -const uint8_t *_I_RFIDBigChip_37x36[] = {_I_RFIDBigChip_37x36_0}; +const uint8_t _I_Voltage_16x16_0[] = {0x01,0x00,0x1a,0x00,0x00,0x24,0x0a,0x01,0x03,0xc0,0x40,0x78,0x10,0x1f,0x04,0x03,0xe1,0x07,0xc0,0x40,0xc0,0xe3,0xc0,0x80,0x58,0x20,0x12,0x00,0xd3,0x00,}; +const uint8_t *_I_Voltage_16x16[] = {_I_Voltage_16x16_0}; + +const uint8_t _I_FaceNopower_29x14_0[] = {0x01,0x00,0x24,0x00,0x00,0x1f,0x02,0x01,0x60,0x01,0xa7,0x80,0x02,0x57,0xe0,0x48,0xc3,0xe7,0xd0,0x0c,0x04,0x3c,0x39,0x1f,0x88,0x18,0x0c,0x61,0x90,0x60,0x18,0xff,0x82,0x44,0x03,0x38,0x74,0x38,0x2c,0x80,}; +const uint8_t *_I_FaceNopower_29x14[] = {_I_FaceNopower_29x14_0}; const uint8_t _I_RFIDDolphinSend_97x61_0[] = {0x01,0x00,0x8d,0x01,0x00,0x0f,0xfa,0x3e,0x04,0x2a,0x00,0x2d,0x78,0x10,0x1f,0x04,0x04,0x0a,0x38,0x00,0x62,0xcc,0x00,0x43,0x06,0x06,0x44,0x30,0x04,0x31,0x80,0x31,0x07,0x48,0x00,0x50,0x20,0x10,0xc8,0x01,0x64,0x0c,0x1d,0x04,0x28,0x24,0x83,0xd2,0x81,0x04,0xc4,0x18,0x42,0xc3,0x01,0x90,0x30,0xbe,0x05,0x51,0x29,0xa0,0x74,0x60,0x80,0xc1,0x84,0x0b,0x44,0x5e,0x43,0x73,0x82,0x41,0x20,0x1e,0x4a,0x68,0x31,0x27,0x90,0x48,0x84,0x20,0x18,0x31,0x7e,0x64,0x06,0x20,0x0c,0x2a,0x14,0x12,0x40,0x0c,0x28,0xa0,0xc4,0x41,0x87,0x81,0x17,0x08,0x30,0xa0,0xfd,0x08,0x0c,0x20,0xfc,0x38,0x08,0xc4,0x24,0x32,0x95,0x02,0x18,0xc2,0x61,0x18,0x09,0x20,0x31,0x03,0x25,0x84,0x1d,0x88,0x30,0x62,0x21,0x96,0xe2,0x44,0x22,0x00,0xc2,0x26,0xa0,0x64,0x68,0x80,0xc4,0x33,0x9e,0x92,0x9f,0x00,0xa3,0x48,0x24,0x00,0xc4,0x40,0xa4,0xa8,0x18,0xa9,0xb5,0x9b,0x48,0x28,0x05,0xa1,0x06,0x22,0xd4,0xa3,0x7e,0x05,0x98,0xe0,0x4f,0x22,0xcf,0x58,0x6f,0x80,0x10,0x34,0x24,0x31,0x3a,0x52,0x0f,0xe0,0x03,0x0c,0xf1,0xee,0x2d,0x63,0x00,0x0c,0x0f,0xe0,0x13,0x28,0xa0,0x31,0xa0,0x3f,0x08,0x18,0x10,0x45,0xa2,0xe3,0x40,0x00,0xf4,0x3f,0xe1,0xa1,0x84,0x02,0x94,0x18,0xb0,0xc0,0x63,0xc6,0x3f,0xe0,0x31,0x87,0x03,0x1e,0x11,0x3c,0x80,0x47,0xc1,0x90,0x56,0x1b,0x06,0x01,0xc0,0x20,0x06,0x17,0x88,0xf8,0x60,0xa0,0xc7,0x31,0x8a,0x58,0x60,0xe1,0x99,0x00,0x08,0x9a,0x01,0x06,0xd9,0x10,0x03,0x1f,0x44,0x19,0x43,0xc3,0x40,0xc4,0x2c,0x19,0x58,0x08,0x29,0xa0,0x60,0x0c,0xf2,0x00,0x27,0x02,0x05,0x20,0x06,0x4d,0x02,0x0b,0xc0,0x02,0x08,0x3c,0x80,0x09,0xa0,0x39,0x0a,0xd4,0x41,0x8f,0x50,0x05,0x09,0xa4,0x5b,0x4d,0x00,0xd8,0x23,0xc4,0x96,0x20,0xc7,0xac,0x40,0x2d,0x53,0x00,0x64,0x6b,0x20,0x1d,0x4a,0x08,0x32,0x2a,0x90,0x0d,0x46,0x0e,0x02,0x0c,0x79,0x51,0x08,0x61,0xf0,0x20,0x63,0xc5,0x4b,0x83,0x1e,0xfe,0x57,0xd3,0x51,0x40,0xbe,0xc0,0x08,0x42,0x00,0x53,0x30,0xe8,0x3f,0x50,0x14,0x73,0x80,0x0b,0xeb,0x07,0x61,0x40,0x00,0x7d,0x5f,0xf8,0x38,0x32,0x7a,0x03,0xf7,0x55,0xa6,0x78,0x19,0x54,0x0c,0xa8,0x32,0xa0,0x19,0xa0,0x65,0xc4,0x0b,0xe2,0x00,0x98,0x40,0x33,0xc1,0x92,0xfa,0x10,0x67,0x80,0x08,}; const uint8_t *_I_RFIDDolphinSend_97x61[] = {_I_RFIDDolphinSend_97x61_0}; +const uint8_t _I_RFIDDolphinSuccess_108x57_0[] = {0x01,0x00,0xe7,0x01,0x00,0x0f,0x03,0xff,0x1f,0x06,0xd4,0xe2,0x01,0xe0,0x06,0xd4,0x18,0x04,0x30,0x30,0x64,0x60,0x20,0x20,0x31,0x86,0x03,0x62,0x80,0x03,0x28,0x80,0x36,0x24,0x00,0x36,0x00,0x28,0x5c,0xc3,0xe6,0x00,0x58,0x40,0xec,0xc1,0xb1,0x04,0x02,0x19,0x24,0x80,0x0b,0x02,0x02,0x40,0x37,0xc4,0x8c,0x2e,0x40,0x6f,0x93,0x8b,0x81,0x07,0x06,0xdc,0xc2,0x38,0x66,0x50,0x6a,0xe2,0x27,0xe0,0xd2,0xfc,0x08,0x09,0x0c,0x9c,0x4b,0x98,0x34,0xa0,0xe1,0xd5,0x06,0x8f,0x92,0xc2,0x05,0x1e,0x42,0xe1,0x81,0xa3,0xe2,0xf0,0xbc,0x4c,0x1a,0xff,0x2f,0x9b,0x80,0xd8,0xca,0x05,0x1f,0x97,0xfd,0xf8,0x60,0xd2,0x01,0x1e,0x00,0x1a,0x5c,0x00,0x08,0xc9,0xc1,0xab,0x40,0xf9,0x83,0x46,0x61,0x00,0xd8,0x4a,0x81,0xab,0xa0,0xf3,0x5f,0xc6,0x05,0x58,0x8a,0xa4,0x09,0x76,0x21,0xb1,0xf2,0x83,0x4f,0x5d,0x1a,0x01,0x8c,0x90,0x1a,0x31,0x0d,0x07,0xa9,0x16,0x50,0x0a,0xac,0x34,0xba,0x42,0xa1,0x88,0x50,0x23,0xaa,0x72,0xe0,0x6a,0xa1,0x4a,0x32,0x39,0x88,0x6c,0x60,0xc7,0x82,0xb0,0x55,0x60,0xa2,0x92,0x80,0xc0,0x43,0x63,0x03,0x25,0x96,0xe3,0x54,0x33,0x18,0xc4,0x90,0x22,0x21,0x81,0x81,0x03,0x4a,0xa9,0x55,0x7a,0x17,0xf3,0x82,0x9f,0x6d,0x5e,0xa9,0xb6,0x50,0x38,0x70,0x35,0x70,0x15,0x5a,0xa9,0xb8,0xa3,0x46,0x12,0x06,0x9f,0x83,0x54,0x8a,0x28,0x80,0x34,0xfc,0x08,0x93,0xaa,0xc7,0x40,0x83,0x83,0x81,0xd3,0xa1,0xd1,0x08,0x84,0x0c,0x24,0x3f,0xed,0x54,0x18,0x26,0x50,0x20,0xd9,0x42,0x21,0x90,0x4c,0x07,0xff,0xae,0x52,0x20,0x6a,0xc4,0x23,0x1f,0x88,0x3f,0xf0,0x1a,0x45,0x31,0xe7,0x03,0x4a,0x41,0xe0,0x69,0x0f,0xc2,0x1e,0x0d,0x19,0x80,0x48,0xa2,0x10,0xc5,0x68,0xdf,0x0a,0x82,0xb9,0x28,0x22,0x2c,0xe3,0x0a,0xd1,0x2b,0x0f,0x00,0x3c,0x22,0x91,0x53,0x9c,0x50,0x1a,0x30,0x08,0x39,0x1c,0x60,0x6d,0x12,0x3d,0x8c,0xc2,0x51,0x00,0x17,0x0c,0xe2,0x01,0xff,0x83,0x84,0xc6,0x40,0xb0,0x19,0x84,0xd0,0x1a,0x5c,0x08,0x1f,0xf8,0x8c,0x50,0x43,0x08,0xce,0x2d,0x06,0x71,0x5f,0x17,0xfe,0x12,0xdf,0x20,0x69,0x55,0x01,0xa6,0x00,0x18,0x40,0xa4,0x80,0x63,0x3c,0xb5,0x03,0x56,0x08,0x8b,0x20,0x10,0xcf,0x03,0x62,0x08,0x20,0x00,0x94,0xc6,0x01,0x70,0x01,0x0c,0xe8,0x36,0x20,0xd3,0xe0,0x00,0xcb,0x10,0x02,0x19,0xf3,0x9c,0x41,0xa3,0x15,0x31,0x90,0x00,0x70,0xc0,0x21,0xdd,0x86,0xc4,0x78,0x3e,0xa3,0x71,0xe0,0x30,0x20,0x31,0xbe,0x86,0xc4,0x1a,0x35,0x40,0x20,0x8d,0x89,0x28,0x5b,0xa0,0xd9,0xea,0x3d,0x44,0x42,0x87,0x83,0x48,0x36,0x49,0xe1,0xa0,0x75,0x67,0x8d,0x41,0x54,0x14,0x03,0xf5,0x2a,0x06,0x96,0x03,0x54,0xc4,0x14,0xd0,0x83,0x4a,0xfb,0x35,0x06,0x90,0x38,0x4e,0x46,0xb4,0x10,0xd9,0x81,0x49,0x72,0x40,0x01,0x0a,0x95,0xd4,0x36,0x20,0xd7,0x55,0x10,}; +const uint8_t *_I_RFIDDolphinSuccess_108x57[] = {_I_RFIDDolphinSuccess_108x57_0}; + const uint8_t _I_RFIDDolphinReceive_97x61_0[] = {0x01,0x00,0x87,0x01,0x00,0x0f,0xfa,0x3e,0x04,0x28,0x08,0x2d,0x78,0x10,0x1f,0x00,0x24,0x70,0x01,0x86,0x98,0x00,0x86,0x0c,0x0c,0x88,0x60,0x08,0x63,0x10,0x0a,0x00,0x31,0xa0,0x40,0x21,0x90,0x03,0x04,0x1a,0x5a,0x08,0x50,0xe9,0x01,0x23,0x20,0x07,0x88,0x30,0xc5,0xa6,0x03,0x10,0x61,0xfc,0x0a,0xa2,0x2d,0x48,0x0c,0x82,0x20,0x04,0x18,0x40,0x40,0x42,0x44,0x37,0x28,0x80,0x30,0xbc,0x94,0xd0,0x62,0x4f,0x20,0x91,0x08,0x44,0x12,0x01,0x17,0xe6,0x40,0x42,0x45,0x00,0xa1,0x03,0x08,0xa8,0x31,0x41,0x88,0x83,0x0f,0x03,0x08,0x06,0x1c,0x1f,0xa1,0x01,0x84,0x1f,0x8a,0x31,0x09,0x0c,0xa5,0x40,0x86,0x30,0x98,0x46,0x02,0x48,0x0c,0x40,0xc9,0x61,0x00,0xe2,0x0c,0x18,0x88,0x65,0xb8,0x85,0x51,0x06,0x21,0x34,0x83,0x23,0x44,0x06,0x29,0x1c,0xb4,0x94,0xf8,0x05,0x19,0x12,0x20,0xc2,0x40,0xb4,0xa8,0x18,0xa9,0xb5,0x9b,0x48,0x28,0x05,0xa1,0x06,0x22,0xd4,0xa3,0x7e,0x05,0x98,0xe0,0x62,0x0c,0xf6,0x86,0xf8,0x16,0x63,0x42,0x06,0x0b,0xa1,0x60,0xfe,0x06,0xe8,0xcf,0x23,0x0d,0x53,0x00,0x14,0x0f,0xe0,0xea,0x28,0xa0,0x31,0xa0,0x3f,0x08,0x18,0x10,0x45,0xa2,0x11,0x20,0x01,0xf4,0x3f,0xe0,0x81,0x84,0x02,0x94,0x18,0xb0,0xc0,0x63,0xc6,0x3f,0xe0,0x31,0x87,0x03,0x1e,0x11,0x3c,0x80,0x47,0xc1,0x91,0x18,0x80,0x58,0x30,0x0e,0x01,0x00,0x30,0xbc,0x47,0xc3,0x05,0x06,0x3c,0x52,0x00,0xe4,0x20,0xcc,0x80,0x04,0x4d,0x00,0x83,0x73,0x08,0x01,0x8f,0xa2,0x0c,0xa1,0xe1,0xa0,0x62,0x16,0x0c,0xac,0x04,0x14,0xd0,0x30,0x08,0x80,0x31,0xb8,0x10,0x27,0x89,0x03,0x1e,0x81,0x05,0xe0,0x01,0x04,0x1e,0x40,0x04,0xd0,0x1c,0x85,0x6a,0x20,0xc7,0xa8,0x02,0x84,0xd2,0x34,0x00,0x63,0x6c,0x11,0xe2,0x4b,0x10,0x63,0xd6,0x20,0x16,0xa9,0x80,0x32,0x35,0x90,0x0e,0xa5,0x04,0x19,0x15,0x48,0x06,0xa3,0x07,0x01,0x06,0x3c,0xa8,0x84,0x30,0xf8,0x10,0x31,0xe2,0xa5,0xc1,0x8f,0x7f,0x2b,0xe9,0xa8,0xa0,0x5f,0x60,0x04,0x21,0x00,0x29,0x98,0x74,0x1f,0xa8,0x0a,0x39,0xc0,0x05,0xf5,0x83,0xb0,0xa0,0x00,0x3e,0xaf,0xfc,0x1c,0x19,0x3d,0x01,0xfb,0xaa,0xd3,0x3c,0x0c,0xaa,0x06,0x54,0x19,0x50,0x0c,0xd0,0x32,0xe2,0x05,0xf1,0x00,0x4c,0x20,0x19,0xe0,0xc9,0x7d,0x08,0x33,0xc0,0x04,}; const uint8_t *_I_RFIDDolphinReceive_97x61[] = {_I_RFIDDolphinReceive_97x61_0}; +const uint8_t _I_RFIDBigChip_37x36_0[] = {0x01,0x00,0x6e,0x00,0x83,0x01,0x0f,0xcd,0xff,0x00,0x0c,0x1e,0x24,0x08,0x28,0x47,0x24,0x12,0x51,0x39,0x28,0x24,0xa2,0x91,0x5e,0x07,0xab,0xfe,0x04,0x1c,0x04,0xaa,0x01,0x15,0x02,0x28,0x4c,0x81,0x2c,0x04,0x4e,0x05,0xfc,0x08,0x35,0x59,0x06,0x02,0x81,0x15,0xca,0xe4,0x26,0xf2,0x10,0x70,0xd7,0x66,0x11,0x70,0x70,0xd4,0x20,0x14,0x10,0x70,0xc7,0x68,0x13,0x70,0x70,0xd4,0x28,0x10,0x10,0x4a,0x84,0xc6,0x80,0x13,0x10,0xe8,0xd0,0x03,0xa2,0x27,0x19,0xf0,0x9c,0x46,0x28,0x3b,0x42,0xcf,0x96,0x6a,0xd4,0x13,0x6f,0x2a,0x2c,0xa2,0x90,0x54,0x59,0xfe,0x52,0xa7,0x02,0x4f,0x9f,0xf1,0x52,0x60,}; +const uint8_t *_I_RFIDBigChip_37x36[] = {_I_RFIDBigChip_37x36_0}; + const uint8_t _I_SDQuestion_35x43_0[] = {0x01,0x00,0x67,0x00,0xf8,0x7f,0xc0,0x03,0x03,0xfc,0x01,0x0a,0x0f,0x38,0xa4,0xe4,0xa4,0x80,0x4f,0x0c,0x20,0x13,0xc0,0x9f,0x80,0x02,0x15,0xfe,0x00,0x04,0x29,0xfc,0x03,0xfd,0x07,0xfa,0x47,0xe7,0xdf,0xc8,0x3f,0xea,0x1f,0x7f,0xfc,0x41,0xff,0xb8,0xff,0xf8,0x10,0x7f,0xe0,0x4e,0xef,0x86,0x08,0x68,0x33,0xf1,0x10,0xff,0x3f,0xf1,0xf1,0x60,0x81,0x06,0x1e,0x36,0x10,0x20,0xe1,0xc0,0x87,0xc7,0x02,0x0f,0xd3,0xff,0xe3,0x02,0x0f,0xe8,0x08,0x7f,0xd0,0x21,0x89,0xc4,0x08,0x9f,0x70,0x21,0x9a,0x08,0x08,0xc1,0x89,0x02,0x20,0x62,0x40,0x8f,0xfe,0x68,0x98,}; const uint8_t *_I_SDQuestion_35x43[] = {_I_SDQuestion_35x43_0}; @@ -392,14 +395,8 @@ const uint8_t *_I_SDError_43x35[] = {_I_SDError_43x35_0}; const uint8_t _I_Cry_dolph_55x52_0[] = {0x01,0x00,0xe8,0x00,0x00,0x0f,0xe3,0xff,0x01,0x03,0x1f,0xfb,0xff,0x0f,0x02,0x96,0x02,0x0f,0x00,0x9f,0x01,0x8b,0xc0,0x12,0x1f,0x80,0x18,0xae,0x00,0x21,0xe0,0x07,0x0a,0x30,0x0a,0x28,0x18,0x08,0x61,0x80,0x62,0x83,0x00,0x90,0x14,0x61,0x02,0x0c,0x16,0x00,0x76,0x60,0x66,0x98,0x0b,0x04,0x90,0x60,0x66,0xb0,0x00,0x48,0x0d,0x21,0x21,0x03,0x30,0x74,0x40,0xd3,0x80,0x03,0x34,0x04,0xc0,0x52,0x00,0x32,0xc7,0xa0,0x18,0x80,0x31,0x80,0x07,0xe1,0x01,0x37,0x18,0x50,0x80,0xc2,0x92,0x10,0x31,0xe8,0x23,0xe9,0x63,0x86,0x54,0x3f,0xe0,0xe1,0x0d,0x96,0x83,0xfc,0x06,0x40,0x69,0x6c,0x3c,0x60,0xd2,0xfc,0xc0,0x60,0x58,0x48,0x0c,0x1b,0x81,0x08,0x14,0x9c,0x1a,0x81,0x04,0x03,0x46,0x80,0x0c,0x50,0x26,0x21,0xc1,0x94,0x26,0x14,0x27,0x8a,0x40,0xc0,0xc2,0xe7,0x26,0x40,0x81,0x86,0xc0,0x6b,0x28,0x64,0x0f,0x01,0x10,0x4e,0x14,0x60,0x0c,0x29,0x02,0x48,0x8b,0x5c,0x45,0x22,0x01,0x10,0x31,0x3a,0x4c,0x0c,0x34,0x06,0xf1,0xd8,0x00,0xc5,0x1a,0x64,0x94,0x0c,0xc0,0x37,0x52,0x20,0x81,0x84,0x26,0x3e,0x88,0x0c,0x38,0x28,0x54,0x0e,0xac,0x1f,0xe1,0x3f,0x06,0x96,0x82,0x7e,0x29,0x4a,0xaf,0xfd,0x76,0x30,0x3a,0x41,0x14,0x7f,0xd0,0xf8,0x78,0x18,0xaa,0x9f,0xd4,0xe0,0x83,0x4f,0xf5,0xf7,0x38,0x0b,0x9c,0x6a,0x1f,0x5b,0x5c,0x00,}; const uint8_t *_I_Cry_dolph_55x52[] = {_I_Cry_dolph_55x52_0}; -const uint8_t _I_BadUsb_9x8_0[] = {0x00,0x01,0x01,0xBB,0x01,0xFE,0x00,0xFE,0x00,0xD6,0x00,0xD6,0x00,0x7C,0x00,0x38,0x00,}; -const uint8_t *_I_BadUsb_9x8[] = {_I_BadUsb_9x8_0}; - -const uint8_t _I_PlaceholderR_30x13_0[] = {0x01,0x00,0x19,0x00,0xfe,0x7f,0xff,0xf0,0xf8,0x10,0x18,0x62,0x10,0x10,0x18,0xc8,0x00,0x7e,0x03,0xb8,0x18,0x0c,0x66,0x1f,0xe1,0x58,0xc7,0xc5,0xe6,}; -const uint8_t *_I_PlaceholderR_30x13[] = {_I_PlaceholderR_30x13_0}; - -const uint8_t _I_Background_128x8_0[] = {0x01,0x00,0x43,0x00,0xff,0x7f,0xc0,0x19,0x7f,0x80,0x87,0xb7,0x01,0x3d,0xfd,0xff,0x74,0xff,0xdf,0x7f,0x87,0x87,0xfd,0xfb,0xd3,0xe7,0xf7,0x9d,0xbf,0xff,0x35,0x41,0x09,0x8c,0x20,0x04,0x31,0xc8,0xe0,0x0c,0x62,0x18,0x08,0x10,0x10,0x70,0x99,0xde,0xfe,0xde,0xe7,0xf7,0xff,0x70,0xfc,0x3f,0x6d,0x7f,0x9e,0x6f,0xd9,0xfd,0xd9,0xf3,0x43,0xff,0x2f,0x68,0x00,0x4d,0xfe,}; -const uint8_t *_I_Background_128x8[] = {_I_Background_128x8_0}; +const uint8_t _I_Background_128x11_0[] = {0x01,0x00,0x70,0x00,0xff,0x40,0x40,0xc9,0xe0,0xff,0x80,0x06,0x1e,0x08,0x38,0x0c,0x0c,0x1e,0x93,0x00,0x19,0x46,0x01,0x07,0x7d,0x83,0x03,0xd2,0x31,0xff,0xdb,0xd5,0x66,0x20,0x83,0xc0,0xff,0x05,0x24,0x00,0x1c,0x78,0x28,0xbc,0x40,0x72,0xbf,0xcf,0x47,0xeb,0x40,0xdb,0x7a,0xbf,0xf0,0x40,0x39,0x60,0x28,0x3f,0xe0,0xa0,0xea,0x80,0x63,0x3f,0x0b,0x17,0xe4,0x3e,0x5a,0xbc,0xf9,0x99,0x70,0x1f,0x81,0x50,0xc0,0x80,0xe7,0x3e,0x1e,0x9d,0x57,0xfb,0x7f,0x23,0x15,0xb0,0x12,0x5b,0x5b,0x02,0x1d,0x8c,0xc3,0x80,0x24,0x9e,0x03,0x80,0x5e,0x40,0x00,0xa1,0x88,0x0e,0x98,0x00,0x7b,0x07,0x08,0xb2,0x44,0x41,}; +const uint8_t *_I_Background_128x11[] = {_I_Background_128x11_0}; const uint8_t _I_Lock_8x8_0[] = {0x00,0x3C,0x42,0x42,0xFF,0xFF,0xE7,0xFF,0xFF,}; const uint8_t *_I_Lock_8x8[] = {_I_Lock_8x8_0}; @@ -407,129 +404,136 @@ const uint8_t *_I_Lock_8x8[] = {_I_Lock_8x8_0}; const uint8_t _I_Battery_26x8_0[] = {0x01,0x00,0x13,0x00,0xff,0x7f,0xef,0xf0,0x08,0x0c,0x03,0x00,0x03,0x38,0x18,0x0c,0xa0,0x40,0x36,0x05,0x98,0x6d,0x00,}; const uint8_t *_I_Battery_26x8[] = {_I_Battery_26x8_0}; -const uint8_t _I_PlaceholderL_11x13_0[] = {0x01,0x00,0x10,0x00,0xfe,0x40,0x60,0x50,0x28,0x0c,0x10,0x03,0xb0,0x38,0x37,0xfe,0x07,0xfe,0x80,0x80,}; -const uint8_t *_I_PlaceholderL_11x13[] = {_I_PlaceholderL_11x13_0}; - const uint8_t _I_Battery_19x8_0[] = {0x01,0x00,0x0f,0x00,0xff,0x7f,0xe0,0x30,0x18,0x04,0x08,0x04,0x90,0x60,0x12,0x02,0xcc,0x28,0x40,}; const uint8_t *_I_Battery_19x8[] = {_I_Battery_19x8_0}; -const uint8_t _I_SDcardMounted_11x8_0[] = {0x01,0x00,0x09,0x00,0xff,0xc1,0xff,0xf0,0x40,0x1c,0xd9,0xe0,0x00,}; -const uint8_t *_I_SDcardMounted_11x8[] = {_I_SDcardMounted_11x8_0}; - -const uint8_t _I_SDcardFail_11x8_0[] = {0x00,0xFF,0x07,0xB7,0x07,0xFF,0x07,0x87,0x07,0x7B,0x07,0xFF,0x07,0xFF,0x07,0x67,0x00,}; -const uint8_t *_I_SDcardFail_11x8[] = {_I_SDcardFail_11x8_0}; - const uint8_t _I_USBConnected_15x8_0[] = {0x00,0xF0,0x07,0x08,0x7C,0x04,0x44,0x07,0x54,0x07,0x54,0x04,0x44,0x08,0x7C,0xF0,0x07,}; const uint8_t *_I_USBConnected_15x8[] = {_I_USBConnected_15x8_0}; -const uint8_t _I_Bluetooth_5x8_0[] = {0x00,0x04,0x0D,0x16,0x0C,0x0C,0x16,0x0D,0x04,}; -const uint8_t *_I_Bluetooth_5x8[] = {_I_Bluetooth_5x8_0}; +const uint8_t _I_Background_128x8_0[] = {0x01,0x00,0x43,0x00,0xff,0x7f,0xc0,0x19,0x7f,0x80,0x87,0xb7,0x01,0x3d,0xfd,0xff,0x74,0xff,0xdf,0x7f,0x87,0x87,0xfd,0xfb,0xd3,0xe7,0xf7,0x9d,0xbf,0xff,0x35,0x41,0x09,0x8c,0x20,0x04,0x31,0xc8,0xe0,0x0c,0x62,0x18,0x08,0x10,0x10,0x70,0x99,0xde,0xfe,0xde,0xe7,0xf7,0xff,0x70,0xfc,0x3f,0x6d,0x7f,0x9e,0x6f,0xd9,0xfd,0xd9,0xf3,0x43,0xff,0x2f,0x68,0x00,0x4d,0xfe,}; +const uint8_t *_I_Background_128x8[] = {_I_Background_128x8_0}; + +const uint8_t _I_BadUsb_9x8_0[] = {0x00,0x01,0x01,0xBB,0x01,0xFE,0x00,0xFE,0x00,0xD6,0x00,0xD6,0x00,0x7C,0x00,0x38,0x00,}; +const uint8_t *_I_BadUsb_9x8[] = {_I_BadUsb_9x8_0}; const uint8_t _I_BT_Pair_9x8_0[] = {0x00,0x11,0x01,0x35,0x00,0x58,0x01,0x31,0x00,0x30,0x01,0x59,0x00,0x34,0x01,0x11,0x01,}; const uint8_t *_I_BT_Pair_9x8[] = {_I_BT_Pair_9x8_0}; -const uint8_t _I_Background_128x11_0[] = {0x01,0x00,0x70,0x00,0xff,0x40,0x40,0xc9,0xe0,0xff,0x80,0x06,0x1e,0x08,0x38,0x0c,0x0c,0x1e,0x93,0x00,0x19,0x46,0x01,0x07,0x7d,0x83,0x03,0xd2,0x31,0xff,0xdb,0xd5,0x66,0x20,0x83,0xc0,0xff,0x05,0x24,0x00,0x1c,0x78,0x28,0xbc,0x40,0x72,0xbf,0xcf,0x47,0xeb,0x40,0xdb,0x7a,0xbf,0xf0,0x40,0x39,0x60,0x28,0x3f,0xe0,0xa0,0xea,0x80,0x63,0x3f,0x0b,0x17,0xe4,0x3e,0x5a,0xbc,0xf9,0x99,0x70,0x1f,0x81,0x50,0xc0,0x80,0xe7,0x3e,0x1e,0x9d,0x57,0xfb,0x7f,0x23,0x15,0xb0,0x12,0x5b,0x5b,0x02,0x1d,0x8c,0xc3,0x80,0x24,0x9e,0x03,0x80,0x5e,0x40,0x00,0xa1,0x88,0x0e,0x98,0x00,0x7b,0x07,0x08,0xb2,0x44,0x41,}; -const uint8_t *_I_Background_128x11[] = {_I_Background_128x11_0}; +const uint8_t _I_PlaceholderL_11x13_0[] = {0x01,0x00,0x10,0x00,0xfe,0x40,0x60,0x50,0x28,0x0c,0x10,0x03,0xb0,0x38,0x37,0xfe,0x07,0xfe,0x80,0x80,}; +const uint8_t *_I_PlaceholderL_11x13[] = {_I_PlaceholderL_11x13_0}; -const uint8_t _I_Scanning_123x52_0[] = {0x01,0x00,0xd3,0x01,0x00,0x78,0x03,0xc0,0x1f,0x00,0xe0,0x7f,0xc1,0xfb,0xf0,0x80,0x41,0xc0,0xc7,0x03,0x07,0xbe,0xb2,0x07,0x18,0x07,0xc4,0x40,0x06,0x55,0x68,0x2d,0x80,0x0a,0x58,0x08,0x10,0x3c,0xe1,0x00,0x32,0xc0,0xc2,0xb0,0x00,0xf8,0x82,0x02,0x0a,0x01,0x15,0x80,0x40,0x40,0xc3,0x40,0x07,0xa0,0x10,0xa8,0x10,0x09,0xc0,0x19,0x01,0xe9,0x82,0x01,0x0c,0x82,0x01,0x74,0x13,0x1d,0x03,0x04,0x24,0x28,0x05,0x04,0x1e,0x76,0x80,0x79,0xc8,0x30,0x50,0x28,0x30,0x14,0x64,0x26,0x23,0xe8,0x78,0x21,0xe0,0xf4,0x85,0x43,0x30,0x12,0x03,0x00,0x83,0xc7,0x41,0x1c,0x3b,0x10,0x3c,0xe2,0x98,0x08,0x80,0xa4,0x61,0x1e,0x0e,0x9c,0x0c,0x1e,0x51,0x00,0x7a,0x95,0x46,0x11,0x90,0xd3,0xd0,0x24,0x80,0xfb,0xe4,0x5f,0xf0,0x92,0x80,0x79,0x61,0x01,0xe3,0xff,0x07,0x9e,0x22,0xcf,0x3e,0xc4,0x03,0xd3,0xf5,0xff,0x07,0xa5,0x12,0xc9,0x2e,0x07,0xa7,0xf3,0x5f,0xff,0x8a,0x93,0xce,0x89,0xe4,0x97,0xe2,0x25,0x40,0xf1,0x8c,0x75,0x3b,0xf1,0xf1,0xf8,0x9b,0xc8,0x1e,0x55,0x0f,0xfc,0x03,0xfd,0x1f,0xf6,0x4f,0xc9,0xe2,0x8f,0x3a,0x27,0x12,0x5f,0xea,0x68,0x0c,0x06,0x35,0xfc,0x2f,0x92,0xbc,0xf0,0x98,0x89,0x7c,0x75,0x8e,0x37,0xd8,0xf1,0x7c,0xa3,0x0c,0xf3,0xc3,0x47,0xf8,0xcb,0x81,0xc2,0x5f,0x62,0xc0,0xf2,0x77,0xa5,0x1b,0xeb,0xc3,0x6c,0x8d,0x12,0x03,0x22,0x07,0x8c,0x30,0x18,0x2d,0x82,0xc3,0xc2,0xaf,0x84,0x42,0x81,0xc8,0xb1,0x01,0xb2,0x4e,0x08,0x08,0x68,0xb0,0x50,0x20,0xdf,0xb4,0x90,0x3a,0x10,0x3d,0x19,0x05,0x86,0x1e,0x8f,0x03,0x03,0xa5,0x83,0xd0,0xa1,0x10,0x30,0x79,0x00,0x0a,0x0a,0x02,0x19,0x84,0x03,0xa5,0xff,0xc0,0x8a,0x88,0x00,0x81,0xe1,0x80,0x12,0x07,0xa5,0x1f,0xc0,0x03,0xde,0x0b,0x80,0x80,0x0a,0x47,0xa3,0x1f,0x80,0x42,0x43,0xf1,0xe1,0x80,0x60,0x3d,0x30,0xf8,0x04,0x48,0x3e,0xf0,0x08,0xf1,0x40,0x7d,0x00,0xf1,0x56,0x08,0xfe,0x20,0x17,0x0f,0x70,0x3c,0x55,0x82,0x00,0x58,0x38,0x0c,0xa7,0x9f,0x90,0x78,0x80,0x1c,0xec,0x5a,0xac,0xff,0xc0,0x1f,0x30,0x1a,0x05,0x57,0xfb,0x5f,0xf8,0x45,0xc3,0xf3,0x80,0xf5,0x7f,0xe7,0xfe,0x00,0x7c,0x87,0xc7,0xab,0xff,0x8f,0x83,0xea,0x05,0x80,0xd5,0x7f,0xe1,0xfe,0x08,0x98,0x7e,0x60,0x15,0x5a,0xac,0x0f,0xe1,0x15,0x0f,0xc9,0x78,0x75,0x50,0x0d,0x84,0x28,0x3f,0x55,0x4b,0xac,0x02,0xb1,0x0d,0x0f,0xd6,0xa0,0xf8,0x3a,0x85,0x29,0xaf,0xde,0xf8,0x04,0x1a,0xe2,0x54,0x83,0xf0,0x00,0x2d,0x70,0xd4,0x43,0xf2,0x00,0x2e,0xb8,0x3a,0x20,0x05,0x93,0xc0,0x5e,0xc1,0xf2,0x79,0x3e,0x04,0x7c,0x1f,0x32,0xa0,0x19,0x7c,0x1e,0x86,0x00,0x6a,0xa8,0x0c,0xbf,0x84,0xe9,0x4e,0x88,0x0c,0x85,0xd5,0x00,}; -const uint8_t *_I_Scanning_123x52[] = {_I_Scanning_123x52_0}; +const uint8_t _I_SDcardFail_11x8_0[] = {0x00,0xFF,0x07,0xB7,0x07,0xFF,0x07,0x87,0x07,0x7B,0x07,0xFF,0x07,0xFF,0x07,0x67,0x00,}; +const uint8_t *_I_SDcardFail_11x8[] = {_I_SDcardFail_11x8_0}; + +const uint8_t _I_Bluetooth_5x8_0[] = {0x00,0x04,0x0D,0x16,0x0C,0x0C,0x16,0x0D,0x04,}; +const uint8_t *_I_Bluetooth_5x8[] = {_I_Bluetooth_5x8_0}; + +const uint8_t _I_PlaceholderR_30x13_0[] = {0x01,0x00,0x19,0x00,0xfe,0x7f,0xff,0xf0,0xf8,0x10,0x18,0x62,0x10,0x10,0x18,0xc8,0x00,0x7e,0x03,0xb8,0x18,0x0c,0x66,0x1f,0xe1,0x58,0xc7,0xc5,0xe6,}; +const uint8_t *_I_PlaceholderR_30x13[] = {_I_PlaceholderR_30x13_0}; + +const uint8_t _I_SDcardMounted_11x8_0[] = {0x01,0x00,0x09,0x00,0xff,0xc1,0xff,0xf0,0x40,0x1c,0xd9,0xe0,0x00,}; +const uint8_t *_I_SDcardMounted_11x8[] = {_I_SDcardMounted_11x8_0}; const uint8_t _I_Quest_7x8_0[] = {0x00,0x1E,0x33,0x33,0x30,0x18,0x0C,0x00,0x0C,}; const uint8_t *_I_Quest_7x8[] = {_I_Quest_7x8_0}; -const uint8_t _I_Unlock_7x8_0[] = {0x00,0x1C,0x22,0x02,0x4F,0x67,0x73,0x79,0x3C,}; -const uint8_t *_I_Unlock_7x8[] = {_I_Unlock_7x8_0}; +const uint8_t _I_Lock_7x8_0[] = {0x00,0x1C,0x22,0x22,0x7F,0x7F,0x77,0x7F,0x3E,}; +const uint8_t *_I_Lock_7x8[] = {_I_Lock_7x8_0}; + +const uint8_t _I_Scanning_123x52_0[] = {0x01,0x00,0xd3,0x01,0x00,0x78,0x03,0xc0,0x1f,0x00,0xe0,0x7f,0xc1,0xfb,0xf0,0x80,0x41,0xc0,0xc7,0x03,0x07,0xbe,0xb2,0x07,0x18,0x07,0xc4,0x40,0x06,0x55,0x68,0x2d,0x80,0x0a,0x58,0x08,0x10,0x3c,0xe1,0x00,0x32,0xc0,0xc2,0xb0,0x00,0xf8,0x82,0x02,0x0a,0x01,0x15,0x80,0x40,0x40,0xc3,0x40,0x07,0xa0,0x10,0xa8,0x10,0x09,0xc0,0x19,0x01,0xe9,0x82,0x01,0x0c,0x82,0x01,0x74,0x13,0x1d,0x03,0x04,0x24,0x28,0x05,0x04,0x1e,0x76,0x80,0x79,0xc8,0x30,0x50,0x28,0x30,0x14,0x64,0x26,0x23,0xe8,0x78,0x21,0xe0,0xf4,0x85,0x43,0x30,0x12,0x03,0x00,0x83,0xc7,0x41,0x1c,0x3b,0x10,0x3c,0xe2,0x98,0x08,0x80,0xa4,0x61,0x1e,0x0e,0x9c,0x0c,0x1e,0x51,0x00,0x7a,0x95,0x46,0x11,0x90,0xd3,0xd0,0x24,0x80,0xfb,0xe4,0x5f,0xf0,0x92,0x80,0x79,0x61,0x01,0xe3,0xff,0x07,0x9e,0x22,0xcf,0x3e,0xc4,0x03,0xd3,0xf5,0xff,0x07,0xa5,0x12,0xc9,0x2e,0x07,0xa7,0xf3,0x5f,0xff,0x8a,0x93,0xce,0x89,0xe4,0x97,0xe2,0x25,0x40,0xf1,0x8c,0x75,0x3b,0xf1,0xf1,0xf8,0x9b,0xc8,0x1e,0x55,0x0f,0xfc,0x03,0xfd,0x1f,0xf6,0x4f,0xc9,0xe2,0x8f,0x3a,0x27,0x12,0x5f,0xea,0x68,0x0c,0x06,0x35,0xfc,0x2f,0x92,0xbc,0xf0,0x98,0x89,0x7c,0x75,0x8e,0x37,0xd8,0xf1,0x7c,0xa3,0x0c,0xf3,0xc3,0x47,0xf8,0xcb,0x81,0xc2,0x5f,0x62,0xc0,0xf2,0x77,0xa5,0x1b,0xeb,0xc3,0x6c,0x8d,0x12,0x03,0x22,0x07,0x8c,0x30,0x18,0x2d,0x82,0xc3,0xc2,0xaf,0x84,0x42,0x81,0xc8,0xb1,0x01,0xb2,0x4e,0x08,0x08,0x68,0xb0,0x50,0x20,0xdf,0xb4,0x90,0x3a,0x10,0x3d,0x19,0x05,0x86,0x1e,0x8f,0x03,0x03,0xa5,0x83,0xd0,0xa1,0x10,0x30,0x79,0x00,0x0a,0x0a,0x02,0x19,0x84,0x03,0xa5,0xff,0xc0,0x8a,0x88,0x00,0x81,0xe1,0x80,0x12,0x07,0xa5,0x1f,0xc0,0x03,0xde,0x0b,0x80,0x80,0x0a,0x47,0xa3,0x1f,0x80,0x42,0x43,0xf1,0xe1,0x80,0x60,0x3d,0x30,0xf8,0x04,0x48,0x3e,0xf0,0x08,0xf1,0x40,0x7d,0x00,0xf1,0x56,0x08,0xfe,0x20,0x17,0x0f,0x70,0x3c,0x55,0x82,0x00,0x58,0x38,0x0c,0xa7,0x9f,0x90,0x78,0x80,0x1c,0xec,0x5a,0xac,0xff,0xc0,0x1f,0x30,0x1a,0x05,0x57,0xfb,0x5f,0xf8,0x45,0xc3,0xf3,0x80,0xf5,0x7f,0xe7,0xfe,0x00,0x7c,0x87,0xc7,0xab,0xff,0x8f,0x83,0xea,0x05,0x80,0xd5,0x7f,0xe1,0xfe,0x08,0x98,0x7e,0x60,0x15,0x5a,0xac,0x0f,0xe1,0x15,0x0f,0xc9,0x78,0x75,0x50,0x0d,0x84,0x28,0x3f,0x55,0x4b,0xac,0x02,0xb1,0x0d,0x0f,0xd6,0xa0,0xf8,0x3a,0x85,0x29,0xaf,0xde,0xf8,0x04,0x1a,0xe2,0x54,0x83,0xf0,0x00,0x2d,0x70,0xd4,0x43,0xf2,0x00,0x2e,0xb8,0x3a,0x20,0x05,0x93,0xc0,0x5e,0xc1,0xf2,0x79,0x3e,0x04,0x7c,0x1f,0x32,0xa0,0x19,0x7c,0x1e,0x86,0x00,0x6a,0xa8,0x0c,0xbf,0x84,0xe9,0x4e,0x88,0x0c,0x85,0xd5,0x00,}; +const uint8_t *_I_Scanning_123x52[] = {_I_Scanning_123x52_0}; const uint8_t _I_MHz_25x11_0[] = {0x01,0x00,0x21,0x00,0xe1,0xe1,0xa0,0x30,0x0f,0x38,0x0c,0xbf,0xe0,0x34,0xfe,0xc0,0x7b,0x7f,0xe0,0x19,0xf0,0x60,0x1d,0xbc,0x35,0x84,0x36,0x53,0x10,0x19,0x46,0x40,0x64,0x13,0x10,0x19,0x80,}; const uint8_t *_I_MHz_25x11[] = {_I_MHz_25x11_0}; -const uint8_t _I_Lock_7x8_0[] = {0x00,0x1C,0x22,0x22,0x7F,0x7F,0x77,0x7F,0x3E,}; -const uint8_t *_I_Lock_7x8[] = {_I_Lock_7x8_0}; - -const uint8_t _I_DolphinMafia_115x62_0[] = {0x01,0x00,0x21,0x02,0x00,0x1e,0x02,0x06,0x0e,0xcb,0x04,0x10,0x1d,0x91,0x88,0x40,0x3b,0x20,0xc0,0xec,0xc0,0x40,0x62,0x03,0xac,0x80,0x03,0xb2,0x31,0x00,0x90,0x03,0xae,0x5e,0x0e,0xcf,0xc4,0x56,0x01,0x40,0x07,0x56,0xbe,0x14,0x0e,0x2f,0xf1,0x5e,0x2a,0xa1,0xd1,0xc0,0x7c,0x3f,0xf0,0x70,0x73,0x70,0x35,0x41,0xd1,0xc0,0x7f,0xff,0xf0,0xf0,0x73,0x50,0x03,0xa4,0x0d,0x10,0x74,0x07,0x46,0x55,0xe0,0x07,0x10,0xb1,0xc3,0xa3,0x55,0xfe,0x03,0x88,0x94,0xe1,0xd1,0xd5,0x03,0x4a,0x3e,0x59,0x9e,0xaf,0xfe,0xff,0x05,0x60,0x4e,0xab,0xf5,0xff,0x95,0xb4,0xa4,0x3a,0x3f,0xd0,0xe0,0xfa,0x20,0x20,0xf8,0xd5,0xff,0xb5,0xf0,0x0f,0x88,0x3a,0x6a,0xbf,0xf8,0xaf,0x82,0x6f,0x03,0x07,0x47,0xaf,0xff,0x0a,0xfe,0x5f,0xc1,0xd3,0xf6,0xbf,0xe0,0x7f,0xfe,0xf0,0x73,0x41,0x00,0x43,0xfa,0xd7,0xf8,0x27,0xfe,0xe0,0x73,0x40,0x80,0x43,0xfe,0xab,0xfe,0x21,0xfc,0xe5,0x9b,0x05,0x48,0xea,0x3f,0xc8,0xfa,0xc4,0x66,0x07,0x44,0x0e,0x8f,0x00,0xb0,0x2b,0x31,0x07,0x0f,0x00,0x1c,0x72,0x00,0x70,0xf8,0x37,0xe5,0x81,0xff,0x89,0x08,0xf2,0x71,0x80,0x20,0xfe,0x2b,0xf0,0x5f,0xc0,0x38,0xc8,0xa5,0x60,0xc3,0x00,0xc7,0xf9,0xaf,0x81,0x2d,0x04,0x34,0x40,0xe1,0x98,0x47,0x68,0x04,0x92,0xab,0xc0,0x7e,0xb7,0xf7,0x39,0x03,0x85,0x8e,0x24,0xf1,0xc0,0x7f,0xf5,0x78,0x0f,0x53,0xb4,0xbc,0x1f,0xb8,0x1a,0x0c,0x61,0xc5,0x82,0xab,0xc0,0x3e,0xa3,0xa2,0xfc,0x07,0x46,0x09,0x60,0x19,0x8f,0x80,0xec,0x38,0x08,0x52,0x6c,0xb8,0xdc,0x28,0x7c,0x10,0x2a,0x5f,0x0f,0xfc,0x5a,0x01,0x05,0x1a,0x8e,0x02,0x02,0x1d,0x1f,0x81,0xa8,0xbe,0x13,0xf8,0x52,0x2c,0x8c,0x62,0x77,0x42,0x11,0x40,0xe0,0xca,0x93,0x8e,0x03,0x8a,0x30,0x10,0x48,0x54,0x03,0x04,0xbb,0x2c,0x00,0x0c,0x64,0x80,0xe4,0x0e,0x88,0x38,0x7c,0x10,0x04,0x09,0x48,0x83,0xac,0x1b,0x18,0xf3,0x44,0xc1,0xca,0x1d,0x15,0x40,0x8e,0x05,0x02,0x20,0xe6,0x24,0x12,0x8c,0x8b,0x05,0x21,0x07,0x24,0x14,0x08,0x73,0x80,0x19,0x78,0x43,0xb2,0xff,0x15,0x30,0xc4,0x01,0x26,0x8f,0x14,0x61,0xa9,0x8a,0x09,0x10,0x02,0x12,0x1c,0x80,0x84,0xaf,0x10,0x71,0xaa,0xc4,0x00,0x3b,0x04,0xea,0x24,0x48,0x1c,0xbd,0x8f,0xf8,0x00,0x67,0xf0,0x09,0x40,0x20,0x61,0x00,0xe4,0xf6,0x07,0x4b,0xc1,0x1f,0x07,0x14,0x40,0x1c,0x9d,0x66,0x79,0x24,0xc6,0xa0,0x0e,0x32,0x51,0xfa,0xce,0xe7,0x50,0x07,0x1c,0x80,0x30,0x58,0x0e,0xa2,0xcc,0xa0,0x19,0x00,0x71,0x42,0x13,0x27,0x40,0xf5,0x45,0x41,0xc5,0x08,0xb0,0x80,0xc6,0x18,0xf2,0x28,0x04,0x83,0xe8,0x58,0x10,0x30,0xc2,0x2c,0x40,0x91,0x89,0x3c,0x88,0x62,0x21,0xd2,0xff,0x03,0x87,0xc8,0x12,0x19,0x08,0x39,0x3e,0x83,0xb2,0x4a,0x0e,0xa2,0x0d,0xc0,0xe0,0x50,0x06,0xa7,0xe8,0x2c,0x94,0xc2,0x09,0x50,0x8c,0xce,0x20,0x34,0x70,0x71,0x41,0x3e,0x85,0xe2,0xe0,0x41,0x38,0x1e,0x28,0x3c,0x19,0xc8,0x70,0x4f,0xc1,0xdc,0xe0,0x74,0x01,0xd8,0xc6,0x24,0x00,0x82,0x81,0x7c,0x12,0xa6,0x7e,0x10,0x28,0xd8,0x22,0x00,0xe3,0xfc,0x34,0x53,0x00,0x23,0x1c,0x04,0x44,0x0e,0x50,0x10,0xeb,0x17,0xca,0x1c,0x07,0x20,}; -const uint8_t *_I_DolphinMafia_115x62[] = {_I_DolphinMafia_115x62_0}; - -const uint8_t _I_DolphinExcited_64x63_0[] = {0x01,0x00,0x36,0x01,0x00,0x25,0x00,0x0f,0xd2,0x00,0x3b,0xe0,0x00,0xeb,0x10,0x0c,0x34,0x40,0x30,0xd0,0x88,0x80,0x1d,0xa1,0x00,0x42,0xfc,0x7f,0xc0,0x63,0x04,0x01,0x0e,0x02,0x0f,0x00,0x00,0x8c,0x08,0x0e,0x37,0x00,0x10,0xc6,0x20,0x10,0x10,0xd9,0x11,0x92,0x1c,0x1a,0x3e,0x00,0x04,0x42,0x02,0x1a,0x20,0xb0,0xce,0x00,0x64,0x07,0x20,0x59,0x16,0x50,0x36,0x45,0x94,0x84,0x78,0x20,0x60,0x75,0x8e,0x43,0x06,0x63,0x3c,0x33,0x94,0x0c,0xd2,0x5c,0x30,0x38,0xe4,0x08,0x43,0x10,0xc0,0x5e,0x06,0x22,0x53,0x1a,0x02,0x08,0x7f,0xd0,0x32,0xc1,0x50,0x21,0x14,0x0e,0x70,0x1c,0x46,0xe2,0x07,0x19,0x06,0x3c,0xdc,0x20,0x91,0xae,0x01,0xcc,0xbe,0x30,0x09,0xfc,0x12,0x41,0xff,0x83,0xcc,0x0a,0xa3,0x1f,0x03,0x99,0xe8,0x7c,0x10,0xf8,0x25,0xa0,0x5e,0x50,0x0f,0x84,0x1e,0x09,0x54,0x03,0x9f,0xf2,0x07,0x02,0xd5,0x11,0xca,0x01,0xfe,0x80,0xc0,0xaa,0x9f,0xf0,0x39,0x5f,0xd0,0x43,0xaa,0x83,0x41,0x92,0xc3,0x1f,0x03,0x8d,0x52,0x02,0x2e,0x25,0xc9,0x6a,0x99,0x46,0xa6,0x2a,0xa0,0x1c,0xaf,0xca,0x62,0x94,0x28,0xcb,0x7e,0x0f,0x15,0x71,0xf8,0x3c,0x22,0x71,0x03,0x8a,0x84,0x67,0x18,0x0f,0xac,0x1c,0x0e,0x38,0x08,0x0c,0x3e,0x01,0xae,0xbd,0x13,0x0c,0x0e,0x35,0x8e,0xa8,0x1c,0xb0,0x1f,0xf8,0x06,0x83,0xf4,0x27,0x38,0x07,0xff,0xff,0x8f,0x03,0xa0,0x4c,0x80,0xed,0x60,0x03,0xb4,0x60,0x0e,0xd0,0x60,0x3a,0x87,0x84,0x0e,0xb7,0xc2,0xfa,0x18,0x05,0x44,0x20,0x73,0xff,0xf7,0xce,0xe4,0x07,0x2d,0x52,0x2c,0x80,0xe7,0x54,0xea,0x81,0xd7,0x50,0x0f,0x7a,0xaa,0x3d,0x41,0xe2,0x07,0x5a,0x80,0x3c,0xa0,0x40,0x72,0xd0,0x6a,0x80,0xa2,0x07,0x3a,0x05,0x54,0x8e,0x20,0x73,0xc0,0x03,0xd8,0x60,0x30,0x40,0x3a,0xc0,0x00,0xee,0xea,0x10,0x3b,0x80,}; -const uint8_t *_I_DolphinExcited_64x63[] = {_I_DolphinExcited_64x63_0}; - -const uint8_t _I_iButtonDolphinSuccess_109x60_0[] = {0x01,0x00,0xac,0x01,0x00,0x17,0xfe,0x1e,0x0c,0xaf,0x04,0x02,0xe0,0x0d,0xa8,0xf4,0x03,0x01,0x03,0x06,0x46,0x02,0x02,0x03,0x18,0xe0,0x36,0x2c,0x00,0x36,0x00,0x2c,0x40,0x3e,0x60,0xd8,0x84,0x01,0x0c,0x5a,0x40,0x05,0x82,0x01,0x0e,0x04,0x0d,0x70,0x42,0x04,0x90,0x49,0x02,0xe4,0x20,0x41,0x28,0xc0,0x07,0x40,0x06,0xf8,0x00,0xa4,0x00,0xd6,0x03,0xa8,0x37,0x44,0x2a,0x31,0x74,0xd3,0x83,0x57,0x80,0x0d,0xc7,0x18,0xa9,0xa8,0x36,0x2a,0x86,0x06,0x8d,0xfc,0x36,0x60,0xd7,0xc0,0x3b,0x8c,0x36,0xf0,0x4a,0x05,0xf9,0x6e,0x5e,0x06,0x23,0x41,0x24,0x1f,0xf6,0x01,0x74,0x01,0xb1,0xe3,0x82,0x81,0x47,0x40,0x0d,0x7c,0x87,0x8e,0x12,0x05,0x1a,0x84,0x0d,0xb6,0xa0,0xd2,0x85,0x86,0xc8,0x1a,0x50,0x40,0x69,0x40,0xb2,0x1f,0xf0,0x69,0x50,0x01,0xa5,0x08,0xfc,0x03,0x5f,0x60,0x0d,0x28,0x84,0x1a,0x07,0x18,0x06,0xaf,0x00,0x1a,0x3c,0x03,0xb8,0xc3,0x20,0xd0,0x28,0x87,0xfc,0x8a,0x50,0x08,0x78,0x08,0x70,0x77,0x0c,0x44,0x06,0x05,0x30,0xff,0x18,0x4a,0x01,0x30,0x01,0x0d,0x33,0x19,0x11,0x1b,0x8c,0xa2,0xf8,0x7d,0x27,0x71,0xd0,0x20,0x51,0x20,0x68,0xd5,0x00,0x42,0x0d,0x2c,0x00,0x08,0x64,0x10,0x19,0x20,0x28,0x75,0x07,0x53,0x3d,0x18,0x35,0x2a,0x9f,0xf4,0x9a,0x41,0x90,0x23,0x00,0x94,0x43,0xe0,0x5e,0xae,0x03,0x9d,0xb4,0xe0,0xd1,0x0d,0x8c,0xd0,0x52,0xb1,0x00,0xd9,0x83,0x46,0x34,0x45,0x41,0xa8,0x9f,0x86,0x01,0x14,0x05,0x08,0x08,0x81,0xa6,0x62,0x10,0x68,0xe5,0x20,0x70,0x41,0x80,0x80,0x10,0xc4,0x34,0x48,0x04,0x2a,0x38,0x0d,0x99,0x16,0x02,0x1a,0xd5,0x10,0x6c,0x5e,0x2e,0x0b,0xa1,0x4b,0x0a,0x60,0xc1,0xa7,0x84,0xfc,0x58,0x01,0xb5,0x02,0x82,0xb4,0xc4,0x16,0x22,0xa5,0x06,0x96,0x19,0x20,0x20,0xd7,0x30,0x8c,0x0f,0x08,0x05,0x10,0x68,0xa1,0x44,0x1a,0x98,0x08,0x14,0x11,0x28,0x21,0x91,0x1d,0x8f,0x83,0xfe,0x07,0x1b,0x00,0x34,0x61,0x00,0xd3,0x1d,0x8c,0x7a,0x01,0x7e,0x80,0x56,0x30,0x06,0xb1,0x4a,0x08,0xd4,0xbf,0xc1,0x31,0xc0,0x7f,0xe8,0xf0,0x08,0x3c,0x40,0x1a,0x80,0x04,0x5a,0x8c,0x10,0x80,0x40,0xd7,0x05,0x08,0x36,0xc0,0xe2,0x0d,0xb8,0x30,0x34,0x45,0x82,0x0d,0x72,0x49,0x03,0x5a,0x41,0x55,0xf8,0x7f,0xff,0xe8,0x72,0x06,0xae,0x03,0xf4,0x0c,0x1d,0xf8,0x18,0x60,0x40,0xd2,0x4b,0x9f,0xd0,0x1a,0x35,0x71,0x48,0xc0,0x95,0x42,0x0d,0x4d,0x50,0x70,0x75,0x40,0xd1,0x80,0x83,0x5a,0xa1,0x55,0x00,0x0c,0x05,0xa4,0x20,0xd2,}; -const uint8_t *_I_iButtonDolphinSuccess_109x60[] = {_I_iButtonDolphinSuccess_109x60_0}; - -const uint8_t _I_iButtonDolphinVerySuccess_108x52_0[] = {0x01,0x00,0xc2,0x01,0x00,0x0f,0xe2,0xfe,0x0d,0xb8,0x3e,0x02,0x06,0x0c,0x9f,0x00,0x08,0x61,0x80,0xd9,0x8c,0x00,0x86,0x60,0x0d,0x98,0x30,0x08,0x6a,0x00,0xd9,0x80,0x80,0x87,0x40,0x0c,0x8c,0x00,0x0c,0xa8,0x01,0x12,0x00,0x2d,0x00,0x22,0x70,0x20,0x6b,0xc8,0x02,0x26,0x62,0x88,0x80,0x6c,0xc9,0x24,0x0d,0x9a,0x07,0x17,0xfe,0x1d,0x68,0x40,0x6c,0xe7,0x48,0x04,0x28,0x10,0x34,0xe8,0x10,0xd1,0x11,0xc4,0x01,0xa5,0x04,0x06,0x96,0xa0,0xa6,0x24,0xc2,0x88,0x17,0x88,0x1a,0x7d,0x43,0x78,0x82,0x4a,0x40,0x03,0x20,0xb0,0xff,0x20,0x16,0xa3,0xb2,0x48,0x03,0xe4,0x0d,0x1f,0xfc,0x06,0x3a,0x0d,0x4a,0x00,0x34,0xf8,0x00,0xd1,0x37,0x0f,0x82,0x9e,0x95,0x58,0x17,0x83,0xff,0x81,0x1b,0x0f,0xf1,0xfe,0x71,0xe0,0x69,0x7c,0x3f,0xe0,0x82,0xff,0xcf,0xc0,0x85,0x61,0x80,0x43,0xb0,0x5f,0xa8,0x79,0xdc,0x81,0xa5,0x70,0xc0,0x68,0x3c,0x10,0x1a,0x17,0xd5,0x28,0x42,0xd1,0x8f,0x84,0x46,0x83,0xb0,0x8e,0x40,0x34,0x5f,0xa8,0x38,0x34,0x45,0xa2,0x0d,0x18,0x04,0x9b,0x50,0x03,0x1a,0x14,0x35,0x36,0x5f,0x8f,0xf8,0xb8,0xa4,0x19,0x40,0x18,0xe8,0xa0,0xca,0x22,0xfe,0x7f,0xc4,0x05,0x20,0xa5,0x80,0xc6,0x82,0xcb,0x3f,0xf3,0x44,0xfc,0x12,0x40,0x18,0xe8,0x51,0x82,0x52,0x28,0xfc,0x38,0x0a,0x3e,0x48,0x98,0x6c,0x8f,0x43,0x00,0xe0,0x63,0xe0,0x62,0xe2,0x91,0x90,0x0a,0x02,0x0d,0x2f,0x82,0x50,0x41,0xa3,0x80,0x90,0x41,0x04,0xc3,0x01,0xc0,0x83,0x46,0x71,0x30,0x06,0x95,0x82,0x21,0x02,0x6e,0x88,0x6c,0x43,0x83,0x1f,0x2f,0x88,0x34,0x62,0x00,0xd1,0x15,0x08,0x2c,0x60,0xcc,0x51,0x0f,0x08,0xcc,0x81,0xa2,0x12,0x10,0x68,0xc6,0x3f,0x06,0xc2,0x06,0x8e,0x02,0x16,0x41,0x20,0x10,0xf8,0x01,0x85,0x00,0x19,0x0d,0x82,0x18,0x07,0x20,0x81,0x00,0x0c,0x9c,0x31,0x08,0x42,0x74,0x81,0xab,0x80,0x03,0x0c,0x32,0x11,0x0b,0x06,0xb9,0xc0,0x43,0xa3,0x10,0x8b,0x83,0x5c,0xe0,0x20,0x81,0xc8,0x26,0x49,0x4c,0x40,0x02,0x86,0x0a,0xc5,0x22,0x32,0x50,0x6b,0x93,0x86,0xc0,0x0d,0x19,0x18,0x35,0x8c,0x84,0x79,0x1a,0x84,0x84,0x1a,0xdf,0xc2,0xe0,0x8a,0xc7,0x51,0x22,0x06,0xb5,0x5e,0x3f,0x00,0x77,0x0d,0x60,0x36,0xfa,0xa9,0xd7,0x00,0x08,0x3a,0xc9,0x02,0x48,0xc0,0x05,0x54,0xba,0x98,0x8a,0xa8,0xf1,0x20,0x6a,0x6a,0x3d,0x43,0x61,0x80,0x4a,0x81,0xaf,0x40,0xea,0x8d,0x86,0x01,0x56,0x06,0x93,0x60,0x80,0x05,0xea,0x01,0x94,0xac,0x1b,0x11,0x80,0x19,0x45,0x41,0x44,0x0d,0x58,0x33,0x18,0xa1,0x4f,0xf3,0x06,0x1f,0x01,0x76,0x58,0x00,0xd9,0x83,0x52,0x7c,0x11,0x38,0x51,0x40,0x80,}; -const uint8_t *_I_iButtonDolphinVerySuccess_108x52[] = {_I_iButtonDolphinVerySuccess_108x52_0}; +const uint8_t _I_Unlock_7x8_0[] = {0x00,0x1C,0x22,0x02,0x4F,0x67,0x73,0x79,0x3C,}; +const uint8_t *_I_Unlock_7x8[] = {_I_Unlock_7x8_0}; const uint8_t _I_iButtonKey_49x44_0[] = {0x01,0x00,0xb4,0x00,0x00,0x24,0xfc,0x0a,0x9c,0x0e,0x00,0x19,0x26,0x18,0x00,0x32,0x43,0x20,0x10,0x10,0x31,0xc0,0x80,0xc9,0x80,0x02,0x08,0x18,0xec,0x00,0x21,0x03,0x1c,0x40,0x1e,0x22,0x15,0xa0,0x08,0x56,0x40,0x06,0x30,0xc0,0x85,0x84,0x86,0x40,0x21,0x84,0x10,0xcc,0x04,0x30,0x40,0x31,0x02,0x88,0x3a,0x20,0x01,0x83,0x0d,0x94,0x06,0x26,0x03,0xf8,0x43,0xc5,0xe9,0x0c,0x11,0x08,0xbc,0xe0,0x64,0x21,0x23,0x09,0x38,0x80,0x22,0x28,0x20,0x58,0x99,0xc4,0x50,0x41,0xe1,0xc0,0x60,0xcc,0xab,0x47,0x21,0xa6,0x02,0x9e,0x06,0x22,0x70,0xf0,0x00,0xcb,0x40,0x03,0x18,0xb0,0x78,0x14,0xe0,0x32,0x58,0x28,0xa5,0x84,0xd0,0x51,0x80,0xc9,0x30,0x06,0xae,0x62,0x84,0x06,0x48,0x64,0x88,0x0c,0x90,0x29,0x08,0x19,0x30,0x31,0x13,0x71,0xb8,0xc4,0xea,0x70,0x6b,0xc5,0x01,0x4a,0x7f,0xc8,0x7c,0x81,0x4a,0x77,0x8a,0xac,0x45,0x4a,0x7f,0x08,0x54,0x39,0x4a,0x7e,0x0e,0xa9,0xf0,0xcb,0xe3,0x7f,0x6e,0x22,0x5c,0x59,0x44,0x00,0x28,0x7a,0xd4,0x40,0x07,0xf0,0x02,0xa0,}; const uint8_t *_I_iButtonKey_49x44[] = {_I_iButtonKey_49x44_0}; -const uint8_t _I_DolphinNice_96x59_0[] = {0x01,0x00,0x8a,0x01,0x00,0x37,0xfa,0x3e,0x0a,0x8f,0x04,0x04,0x02,0x20,0xb7,0x8c,0x00,0x86,0x1c,0x0b,0x78,0x20,0x08,0x66,0x00,0xb7,0x81,0x00,0x86,0x80,0x0b,0x71,0x61,0x60,0x01,0x4c,0x07,0x41,0xe3,0x07,0xd0,0x4e,0x40,0xb8,0x1f,0x90,0x00,0xe4,0x00,0xba,0x88,0x01,0x0e,0x10,0x0a,0x48,0xf9,0x6c,0xbe,0x10,0x70,0x82,0x78,0x3c,0x15,0x82,0x18,0xc2,0x21,0x00,0xb4,0x02,0x0e,0xbc,0x86,0x30,0x48,0x80,0xd1,0x05,0x03,0x78,0x82,0xc0,0x3e,0x52,0x32,0x63,0x70,0x20,0x70,0x09,0xd4,0x98,0xb0,0xf0,0x60,0x58,0xc9,0xce,0x12,0x0b,0xbf,0xd4,0x9d,0x28,0x9e,0x24,0xa9,0x82,0xda,0x24,0x2d,0x10,0x00,0xfd,0x2a,0x60,0xb4,0x85,0x4e,0x00,0x85,0xf8,0xd4,0x82,0xd2,0x09,0xc0,0x12,0x14,0x12,0xad,0x81,0x29,0xa8,0x90,0xf5,0x01,0x75,0x80,0x46,0x00,0xa5,0x50,0x0b,0x90,0x1c,0x41,0x63,0x60,0x05,0x96,0xc0,0x2e,0x52,0x44,0x79,0x60,0x06,0x05,0x50,0x05,0x94,0x89,0x88,0x63,0x02,0x98,0x02,0xc7,0xc1,0x21,0x6a,0x98,0xa0,0x62,0x11,0x00,0x58,0xc6,0x02,0xe2,0xb8,0x21,0x80,0xc3,0x05,0x02,0x38,0x11,0x78,0xa5,0x0b,0x01,0x81,0x5a,0x88,0x2c,0x60,0x40,0xb1,0xc0,0x27,0x0a,0xfc,0x0f,0x28,0x04,0x06,0x50,0x05,0x18,0xa9,0x94,0xc1,0x67,0x48,0x02,0x8c,0xb8,0x16,0xf8,0x80,0x28,0xd6,0x16,0x86,0x0b,0x38,0x40,0xd4,0x76,0x0c,0xd4,0x05,0x94,0x10,0x9a,0x34,0x01,0x82,0x1f,0x06,0x05,0x02,0x98,0x01,0x47,0x54,0x18,0x35,0xc8,0xff,0x20,0x3c,0x00,0x58,0xd5,0x6a,0xa0,0xb3,0x81,0xa3,0x0a,0x0f,0x80,0xd5,0xea,0x81,0x67,0x07,0x46,0x14,0xe3,0xe1,0x55,0x18,0x18,0x2c,0x51,0x85,0xc0,0xef,0x85,0x8c,0x0c,0x30,0xf4,0x61,0x40,0x2d,0x46,0xb4,0x05,0x8b,0x04,0xb0,0x15,0x40,0x5a,0x50,0x23,0xe6,0x01,0x02,0x8c,0xa8,0x2e,0xb1,0xe5,0x40,0x81,0x46,0x6a,0x17,0x59,0xeb,0xe4,0xa8,0x11,0xa0,0x5a,0x68,0x27,0x4e,0xd3,0x59,0xad,0x82,0xfa,0xed,0x2a,0x04,0x28,0x2e,0xb7,0xa7,0x69,0xc3,0x42,0xeb,0xf5,0x1f,0x09,0x4c,0x42,0xed,0xea,0x01,0x8c,0x06,0x41,0x05,0x0b,0xbc,0x02,0x0d,0x80,0x83,0x05,0xe2,0x11,0x40,0x0b,0xb7,0x14,0x06,0x33,0x0c,0x83,0x89,0x02,0xe3,0xca,0x3d,0x95,0x01,0xe2,0x21,0x74,0xc2,0x81,0x0b,0x0e,0x17,0x6c,0x10,0x10,0xaf,0x09,0xe2,0x0b,0xbb,0xd0,0x42,0xeb,0x02,}; -const uint8_t *_I_DolphinNice_96x59[] = {_I_DolphinNice_96x59_0}; +const uint8_t _I_DolphinExcited_64x63_0[] = {0x01,0x00,0x36,0x01,0x00,0x25,0x00,0x0f,0xd2,0x00,0x3b,0xe0,0x00,0xeb,0x10,0x0c,0x34,0x40,0x30,0xd0,0x88,0x80,0x1d,0xa1,0x00,0x42,0xfc,0x7f,0xc0,0x63,0x04,0x01,0x0e,0x02,0x0f,0x00,0x00,0x8c,0x08,0x0e,0x37,0x00,0x10,0xc6,0x20,0x10,0x10,0xd9,0x11,0x92,0x1c,0x1a,0x3e,0x00,0x04,0x42,0x02,0x1a,0x20,0xb0,0xce,0x00,0x64,0x07,0x20,0x59,0x16,0x50,0x36,0x45,0x94,0x84,0x78,0x20,0x60,0x75,0x8e,0x43,0x06,0x63,0x3c,0x33,0x94,0x0c,0xd2,0x5c,0x30,0x38,0xe4,0x08,0x43,0x10,0xc0,0x5e,0x06,0x22,0x53,0x1a,0x02,0x08,0x7f,0xd0,0x32,0xc1,0x50,0x21,0x14,0x0e,0x70,0x1c,0x46,0xe2,0x07,0x19,0x06,0x3c,0xdc,0x20,0x91,0xae,0x01,0xcc,0xbe,0x30,0x09,0xfc,0x12,0x41,0xff,0x83,0xcc,0x0a,0xa3,0x1f,0x03,0x99,0xe8,0x7c,0x10,0xf8,0x25,0xa0,0x5e,0x50,0x0f,0x84,0x1e,0x09,0x54,0x03,0x9f,0xf2,0x07,0x02,0xd5,0x11,0xca,0x01,0xfe,0x80,0xc0,0xaa,0x9f,0xf0,0x39,0x5f,0xd0,0x43,0xaa,0x83,0x41,0x92,0xc3,0x1f,0x03,0x8d,0x52,0x02,0x2e,0x25,0xc9,0x6a,0x99,0x46,0xa6,0x2a,0xa0,0x1c,0xaf,0xca,0x62,0x94,0x28,0xcb,0x7e,0x0f,0x15,0x71,0xf8,0x3c,0x22,0x71,0x03,0x8a,0x84,0x67,0x18,0x0f,0xac,0x1c,0x0e,0x38,0x08,0x0c,0x3e,0x01,0xae,0xbd,0x13,0x0c,0x0e,0x35,0x8e,0xa8,0x1c,0xb0,0x1f,0xf8,0x06,0x83,0xf4,0x27,0x38,0x07,0xff,0xff,0x8f,0x03,0xa0,0x4c,0x80,0xed,0x60,0x03,0xb4,0x60,0x0e,0xd0,0x60,0x3a,0x87,0x84,0x0e,0xb7,0xc2,0xfa,0x18,0x05,0x44,0x20,0x73,0xff,0xf7,0xce,0xe4,0x07,0x2d,0x52,0x2c,0x80,0xe7,0x54,0xea,0x81,0xd7,0x50,0x0f,0x7a,0xaa,0x3d,0x41,0xe2,0x07,0x5a,0x80,0x3c,0xa0,0x40,0x72,0xd0,0x6a,0x80,0xa2,0x07,0x3a,0x05,0x54,0x8e,0x20,0x73,0xc0,0x03,0xd8,0x60,0x30,0x40,0x3a,0xc0,0x00,0xee,0xea,0x10,0x3b,0x80,}; +const uint8_t *_I_DolphinExcited_64x63[] = {_I_DolphinExcited_64x63_0}; const uint8_t _I_DolphinWait_61x59_0[] = {0x01,0x00,0x56,0x01,0x00,0x17,0xfa,0x1e,0x06,0x4f,0x84,0x06,0xe0,0x07,0x48,0x64,0x03,0x01,0x01,0x03,0x9c,0x0c,0x04,0x30,0x60,0x31,0x70,0x00,0x65,0x08,0x01,0x94,0xc0,0x06,0x51,0x00,0x5b,0x48,0x00,0x65,0x04,0x01,0x95,0x00,0x82,0xd8,0x00,0x19,0x40,0x7e,0x00,0x75,0x1f,0x88,0xe0,0x88,0x02,0x1a,0x1f,0x94,0x14,0x0e,0xbf,0x98,0x58,0x5c,0x42,0x45,0x00,0x9e,0x99,0x87,0x01,0x02,0x11,0x94,0xf2,0x2e,0x03,0x18,0x39,0x28,0x70,0x1f,0xc0,0x3e,0x42,0x00,0xe5,0x80,0xff,0xdf,0xc0,0xe5,0xf8,0x85,0xd8,0x10,0x27,0x40,0xf9,0xc2,0x63,0x88,0x12,0x82,0x6a,0x20,0x50,0x41,0xe9,0x42,0x20,0x95,0x48,0x6e,0x0c,0xfa,0x9a,0xaf,0xf9,0x90,0xe2,0x10,0x2e,0xac,0xe0,0x0e,0x98,0x29,0x52,0x11,0x13,0x23,0x15,0x3e,0x20,0x3c,0x61,0x40,0x52,0xfc,0x4f,0xe2,0x10,0x38,0x68,0x1c,0xa0,0xfc,0x08,0xbe,0x04,0x1e,0x5e,0x01,0xb9,0x03,0xc5,0x60,0x24,0xf2,0x84,0x60,0x63,0x40,0x71,0x27,0x9c,0x0e,0x2b,0x04,0x6c,0xa4,0x06,0x15,0x08,0x6c,0x99,0x8c,0xa6,0x0f,0x81,0x00,0x0c,0x08,0xf0,0x3c,0x05,0x61,0xc0,0x40,0x86,0xd0,0x30,0x78,0x80,0x0c,0xc6,0x2b,0x92,0x00,0x0d,0x51,0xf0,0x2d,0x42,0x0a,0x8e,0xaa,0x34,0x0f,0x4a,0x85,0x55,0x6e,0x20,0xf3,0xd5,0x6a,0x84,0xa2,0x66,0x2a,0x05,0xf7,0xaa,0x07,0x18,0xaf,0xfb,0x7f,0xea,0xc1,0xef,0xc0,0xe3,0xea,0x80,0xf8,0x27,0xf0,0x0a,0xc0,0x1c,0x67,0xa2,0xd1,0xb1,0xc0,0x34,0x00,0x71,0x14,0x8f,0x00,0x98,0x34,0x02,0x69,0xd0,0x37,0x90,0x16,0xf1,0x00,0x06,0xe1,0x84,0x31,0x89,0x14,0xe9,0xdc,0x40,0x38,0xa4,0xc4,0x4c,0x3c,0x1f,0x88,0x8c,0x5b,0xc3,0x01,0xbc,0x40,0x3f,0xf0,0xf6,0x71,0x0c,0x0b,0xe0,0x07,0x3c,0x0a,0xf8,0xa3,0xf0,0x03,0xb8,0xd8,0x80,0xe8,0x87,0x1b,0xa8,0x1c,0x78,0x1f,0xf8,0x0e,0x7e,0x01,0x6a,0x03,0x94,0x0f,0xfd,0xa0,0x80,0x7d,0x49,0x04,0x4d,0x12,0xc0,0xfa,0x83,0x83,0xbe,0x26,0x8d,0x02,0x05,0xd5,0xff,0xff,0xeb,0xe9,0x31,0x90,0x40,0x80,}; const uint8_t *_I_DolphinWait_61x59[] = {_I_DolphinWait_61x59_0}; -const Icon I_Certification1_103x23 = {.width=103,.height=23,.frame_count=1,.frame_rate=0,.frames=_I_Certification1_103x23}; +const uint8_t _I_iButtonDolphinVerySuccess_108x52_0[] = {0x01,0x00,0xc2,0x01,0x00,0x0f,0xe2,0xfe,0x0d,0xb8,0x3e,0x02,0x06,0x0c,0x9f,0x00,0x08,0x61,0x80,0xd9,0x8c,0x00,0x86,0x60,0x0d,0x98,0x30,0x08,0x6a,0x00,0xd9,0x80,0x80,0x87,0x40,0x0c,0x8c,0x00,0x0c,0xa8,0x01,0x12,0x00,0x2d,0x00,0x22,0x70,0x20,0x6b,0xc8,0x02,0x26,0x62,0x88,0x80,0x6c,0xc9,0x24,0x0d,0x9a,0x07,0x17,0xfe,0x1d,0x68,0x40,0x6c,0xe7,0x48,0x04,0x28,0x10,0x34,0xe8,0x10,0xd1,0x11,0xc4,0x01,0xa5,0x04,0x06,0x96,0xa0,0xa6,0x24,0xc2,0x88,0x17,0x88,0x1a,0x7d,0x43,0x78,0x82,0x4a,0x40,0x03,0x20,0xb0,0xff,0x20,0x16,0xa3,0xb2,0x48,0x03,0xe4,0x0d,0x1f,0xfc,0x06,0x3a,0x0d,0x4a,0x00,0x34,0xf8,0x00,0xd1,0x37,0x0f,0x82,0x9e,0x95,0x58,0x17,0x83,0xff,0x81,0x1b,0x0f,0xf1,0xfe,0x71,0xe0,0x69,0x7c,0x3f,0xe0,0x82,0xff,0xcf,0xc0,0x85,0x61,0x80,0x43,0xb0,0x5f,0xa8,0x79,0xdc,0x81,0xa5,0x70,0xc0,0x68,0x3c,0x10,0x1a,0x17,0xd5,0x28,0x42,0xd1,0x8f,0x84,0x46,0x83,0xb0,0x8e,0x40,0x34,0x5f,0xa8,0x38,0x34,0x45,0xa2,0x0d,0x18,0x04,0x9b,0x50,0x03,0x1a,0x14,0x35,0x36,0x5f,0x8f,0xf8,0xb8,0xa4,0x19,0x40,0x18,0xe8,0xa0,0xca,0x22,0xfe,0x7f,0xc4,0x05,0x20,0xa5,0x80,0xc6,0x82,0xcb,0x3f,0xf3,0x44,0xfc,0x12,0x40,0x18,0xe8,0x51,0x82,0x52,0x28,0xfc,0x38,0x0a,0x3e,0x48,0x98,0x6c,0x8f,0x43,0x00,0xe0,0x63,0xe0,0x62,0xe2,0x91,0x90,0x0a,0x02,0x0d,0x2f,0x82,0x50,0x41,0xa3,0x80,0x90,0x41,0x04,0xc3,0x01,0xc0,0x83,0x46,0x71,0x30,0x06,0x95,0x82,0x21,0x02,0x6e,0x88,0x6c,0x43,0x83,0x1f,0x2f,0x88,0x34,0x62,0x00,0xd1,0x15,0x08,0x2c,0x60,0xcc,0x51,0x0f,0x08,0xcc,0x81,0xa2,0x12,0x10,0x68,0xc6,0x3f,0x06,0xc2,0x06,0x8e,0x02,0x16,0x41,0x20,0x10,0xf8,0x01,0x85,0x00,0x19,0x0d,0x82,0x18,0x07,0x20,0x81,0x00,0x0c,0x9c,0x31,0x08,0x42,0x74,0x81,0xab,0x80,0x03,0x0c,0x32,0x11,0x0b,0x06,0xb9,0xc0,0x43,0xa3,0x10,0x8b,0x83,0x5c,0xe0,0x20,0x81,0xc8,0x26,0x49,0x4c,0x40,0x02,0x86,0x0a,0xc5,0x22,0x32,0x50,0x6b,0x93,0x86,0xc0,0x0d,0x19,0x18,0x35,0x8c,0x84,0x79,0x1a,0x84,0x84,0x1a,0xdf,0xc2,0xe0,0x8a,0xc7,0x51,0x22,0x06,0xb5,0x5e,0x3f,0x00,0x77,0x0d,0x60,0x36,0xfa,0xa9,0xd7,0x00,0x08,0x3a,0xc9,0x02,0x48,0xc0,0x05,0x54,0xba,0x98,0x8a,0xa8,0xf1,0x20,0x6a,0x6a,0x3d,0x43,0x61,0x80,0x4a,0x81,0xaf,0x40,0xea,0x8d,0x86,0x01,0x56,0x06,0x93,0x60,0x80,0x05,0xea,0x01,0x94,0xac,0x1b,0x11,0x80,0x19,0x45,0x41,0x44,0x0d,0x58,0x33,0x18,0xa1,0x4f,0xf3,0x06,0x1f,0x01,0x76,0x58,0x00,0xd9,0x83,0x52,0x7c,0x11,0x38,0x51,0x40,0x80,}; +const uint8_t *_I_iButtonDolphinVerySuccess_108x52[] = {_I_iButtonDolphinVerySuccess_108x52_0}; + +const uint8_t _I_DolphinMafia_115x62_0[] = {0x01,0x00,0x21,0x02,0x00,0x1e,0x02,0x06,0x0e,0xcb,0x04,0x10,0x1d,0x91,0x88,0x40,0x3b,0x20,0xc0,0xec,0xc0,0x40,0x62,0x03,0xac,0x80,0x03,0xb2,0x31,0x00,0x90,0x03,0xae,0x5e,0x0e,0xcf,0xc4,0x56,0x01,0x40,0x07,0x56,0xbe,0x14,0x0e,0x2f,0xf1,0x5e,0x2a,0xa1,0xd1,0xc0,0x7c,0x3f,0xf0,0x70,0x73,0x70,0x35,0x41,0xd1,0xc0,0x7f,0xff,0xf0,0xf0,0x73,0x50,0x03,0xa4,0x0d,0x10,0x74,0x07,0x46,0x55,0xe0,0x07,0x10,0xb1,0xc3,0xa3,0x55,0xfe,0x03,0x88,0x94,0xe1,0xd1,0xd5,0x03,0x4a,0x3e,0x59,0x9e,0xaf,0xfe,0xff,0x05,0x60,0x4e,0xab,0xf5,0xff,0x95,0xb4,0xa4,0x3a,0x3f,0xd0,0xe0,0xfa,0x20,0x20,0xf8,0xd5,0xff,0xb5,0xf0,0x0f,0x88,0x3a,0x6a,0xbf,0xf8,0xaf,0x82,0x6f,0x03,0x07,0x47,0xaf,0xff,0x0a,0xfe,0x5f,0xc1,0xd3,0xf6,0xbf,0xe0,0x7f,0xfe,0xf0,0x73,0x41,0x00,0x43,0xfa,0xd7,0xf8,0x27,0xfe,0xe0,0x73,0x40,0x80,0x43,0xfe,0xab,0xfe,0x21,0xfc,0xe5,0x9b,0x05,0x48,0xea,0x3f,0xc8,0xfa,0xc4,0x66,0x07,0x44,0x0e,0x8f,0x00,0xb0,0x2b,0x31,0x07,0x0f,0x00,0x1c,0x72,0x00,0x70,0xf8,0x37,0xe5,0x81,0xff,0x89,0x08,0xf2,0x71,0x80,0x20,0xfe,0x2b,0xf0,0x5f,0xc0,0x38,0xc8,0xa5,0x60,0xc3,0x00,0xc7,0xf9,0xaf,0x81,0x2d,0x04,0x34,0x40,0xe1,0x98,0x47,0x68,0x04,0x92,0xab,0xc0,0x7e,0xb7,0xf7,0x39,0x03,0x85,0x8e,0x24,0xf1,0xc0,0x7f,0xf5,0x78,0x0f,0x53,0xb4,0xbc,0x1f,0xb8,0x1a,0x0c,0x61,0xc5,0x82,0xab,0xc0,0x3e,0xa3,0xa2,0xfc,0x07,0x46,0x09,0x60,0x19,0x8f,0x80,0xec,0x38,0x08,0x52,0x6c,0xb8,0xdc,0x28,0x7c,0x10,0x2a,0x5f,0x0f,0xfc,0x5a,0x01,0x05,0x1a,0x8e,0x02,0x02,0x1d,0x1f,0x81,0xa8,0xbe,0x13,0xf8,0x52,0x2c,0x8c,0x62,0x77,0x42,0x11,0x40,0xe0,0xca,0x93,0x8e,0x03,0x8a,0x30,0x10,0x48,0x54,0x03,0x04,0xbb,0x2c,0x00,0x0c,0x64,0x80,0xe4,0x0e,0x88,0x38,0x7c,0x10,0x04,0x09,0x48,0x83,0xac,0x1b,0x18,0xf3,0x44,0xc1,0xca,0x1d,0x15,0x40,0x8e,0x05,0x02,0x20,0xe6,0x24,0x12,0x8c,0x8b,0x05,0x21,0x07,0x24,0x14,0x08,0x73,0x80,0x19,0x78,0x43,0xb2,0xff,0x15,0x30,0xc4,0x01,0x26,0x8f,0x14,0x61,0xa9,0x8a,0x09,0x10,0x02,0x12,0x1c,0x80,0x84,0xaf,0x10,0x71,0xaa,0xc4,0x00,0x3b,0x04,0xea,0x24,0x48,0x1c,0xbd,0x8f,0xf8,0x00,0x67,0xf0,0x09,0x40,0x20,0x61,0x00,0xe4,0xf6,0x07,0x4b,0xc1,0x1f,0x07,0x14,0x40,0x1c,0x9d,0x66,0x79,0x24,0xc6,0xa0,0x0e,0x32,0x51,0xfa,0xce,0xe7,0x50,0x07,0x1c,0x80,0x30,0x58,0x0e,0xa2,0xcc,0xa0,0x19,0x00,0x71,0x42,0x13,0x27,0x40,0xf5,0x45,0x41,0xc5,0x08,0xb0,0x80,0xc6,0x18,0xf2,0x28,0x04,0x83,0xe8,0x58,0x10,0x30,0xc2,0x2c,0x40,0x91,0x89,0x3c,0x88,0x62,0x21,0xd2,0xff,0x03,0x87,0xc8,0x12,0x19,0x08,0x39,0x3e,0x83,0xb2,0x4a,0x0e,0xa2,0x0d,0xc0,0xe0,0x50,0x06,0xa7,0xe8,0x2c,0x94,0xc2,0x09,0x50,0x8c,0xce,0x20,0x34,0x70,0x71,0x41,0x3e,0x85,0xe2,0xe0,0x41,0x38,0x1e,0x28,0x3c,0x19,0xc8,0x70,0x4f,0xc1,0xdc,0xe0,0x74,0x01,0xd8,0xc6,0x24,0x00,0x82,0x81,0x7c,0x12,0xa6,0x7e,0x10,0x28,0xd8,0x22,0x00,0xe3,0xfc,0x34,0x53,0x00,0x23,0x1c,0x04,0x44,0x0e,0x50,0x10,0xeb,0x17,0xca,0x1c,0x07,0x20,}; +const uint8_t *_I_DolphinMafia_115x62[] = {_I_DolphinMafia_115x62_0}; + +const uint8_t _I_DolphinNice_96x59_0[] = {0x01,0x00,0x8a,0x01,0x00,0x37,0xfa,0x3e,0x0a,0x8f,0x04,0x04,0x02,0x20,0xb7,0x8c,0x00,0x86,0x1c,0x0b,0x78,0x20,0x08,0x66,0x00,0xb7,0x81,0x00,0x86,0x80,0x0b,0x71,0x61,0x60,0x01,0x4c,0x07,0x41,0xe3,0x07,0xd0,0x4e,0x40,0xb8,0x1f,0x90,0x00,0xe4,0x00,0xba,0x88,0x01,0x0e,0x10,0x0a,0x48,0xf9,0x6c,0xbe,0x10,0x70,0x82,0x78,0x3c,0x15,0x82,0x18,0xc2,0x21,0x00,0xb4,0x02,0x0e,0xbc,0x86,0x30,0x48,0x80,0xd1,0x05,0x03,0x78,0x82,0xc0,0x3e,0x52,0x32,0x63,0x70,0x20,0x70,0x09,0xd4,0x98,0xb0,0xf0,0x60,0x58,0xc9,0xce,0x12,0x0b,0xbf,0xd4,0x9d,0x28,0x9e,0x24,0xa9,0x82,0xda,0x24,0x2d,0x10,0x00,0xfd,0x2a,0x60,0xb4,0x85,0x4e,0x00,0x85,0xf8,0xd4,0x82,0xd2,0x09,0xc0,0x12,0x14,0x12,0xad,0x81,0x29,0xa8,0x90,0xf5,0x01,0x75,0x80,0x46,0x00,0xa5,0x50,0x0b,0x90,0x1c,0x41,0x63,0x60,0x05,0x96,0xc0,0x2e,0x52,0x44,0x79,0x60,0x06,0x05,0x50,0x05,0x94,0x89,0x88,0x63,0x02,0x98,0x02,0xc7,0xc1,0x21,0x6a,0x98,0xa0,0x62,0x11,0x00,0x58,0xc6,0x02,0xe2,0xb8,0x21,0x80,0xc3,0x05,0x02,0x38,0x11,0x78,0xa5,0x0b,0x01,0x81,0x5a,0x88,0x2c,0x60,0x40,0xb1,0xc0,0x27,0x0a,0xfc,0x0f,0x28,0x04,0x06,0x50,0x05,0x18,0xa9,0x94,0xc1,0x67,0x48,0x02,0x8c,0xb8,0x16,0xf8,0x80,0x28,0xd6,0x16,0x86,0x0b,0x38,0x40,0xd4,0x76,0x0c,0xd4,0x05,0x94,0x10,0x9a,0x34,0x01,0x82,0x1f,0x06,0x05,0x02,0x98,0x01,0x47,0x54,0x18,0x35,0xc8,0xff,0x20,0x3c,0x00,0x58,0xd5,0x6a,0xa0,0xb3,0x81,0xa3,0x0a,0x0f,0x80,0xd5,0xea,0x81,0x67,0x07,0x46,0x14,0xe3,0xe1,0x55,0x18,0x18,0x2c,0x51,0x85,0xc0,0xef,0x85,0x8c,0x0c,0x30,0xf4,0x61,0x40,0x2d,0x46,0xb4,0x05,0x8b,0x04,0xb0,0x15,0x40,0x5a,0x50,0x23,0xe6,0x01,0x02,0x8c,0xa8,0x2e,0xb1,0xe5,0x40,0x81,0x46,0x6a,0x17,0x59,0xeb,0xe4,0xa8,0x11,0xa0,0x5a,0x68,0x27,0x4e,0xd3,0x59,0xad,0x82,0xfa,0xed,0x2a,0x04,0x28,0x2e,0xb7,0xa7,0x69,0xc3,0x42,0xeb,0xf5,0x1f,0x09,0x4c,0x42,0xed,0xea,0x01,0x8c,0x06,0x41,0x05,0x0b,0xbc,0x02,0x0d,0x80,0x83,0x05,0xe2,0x11,0x40,0x0b,0xb7,0x14,0x06,0x33,0x0c,0x83,0x89,0x02,0xe3,0xca,0x3d,0x95,0x01,0xe2,0x21,0x74,0xc2,0x81,0x0b,0x0e,0x17,0x6c,0x10,0x10,0xaf,0x09,0xe2,0x0b,0xbb,0xd0,0x42,0xeb,0x02,}; +const uint8_t *_I_DolphinNice_96x59[] = {_I_DolphinNice_96x59_0}; + +const uint8_t _I_iButtonDolphinSuccess_109x60_0[] = {0x01,0x00,0xac,0x01,0x00,0x17,0xfe,0x1e,0x0c,0xaf,0x04,0x02,0xe0,0x0d,0xa8,0xf4,0x03,0x01,0x03,0x06,0x46,0x02,0x02,0x03,0x18,0xe0,0x36,0x2c,0x00,0x36,0x00,0x2c,0x40,0x3e,0x60,0xd8,0x84,0x01,0x0c,0x5a,0x40,0x05,0x82,0x01,0x0e,0x04,0x0d,0x70,0x42,0x04,0x90,0x49,0x02,0xe4,0x20,0x41,0x28,0xc0,0x07,0x40,0x06,0xf8,0x00,0xa4,0x00,0xd6,0x03,0xa8,0x37,0x44,0x2a,0x31,0x74,0xd3,0x83,0x57,0x80,0x0d,0xc7,0x18,0xa9,0xa8,0x36,0x2a,0x86,0x06,0x8d,0xfc,0x36,0x60,0xd7,0xc0,0x3b,0x8c,0x36,0xf0,0x4a,0x05,0xf9,0x6e,0x5e,0x06,0x23,0x41,0x24,0x1f,0xf6,0x01,0x74,0x01,0xb1,0xe3,0x82,0x81,0x47,0x40,0x0d,0x7c,0x87,0x8e,0x12,0x05,0x1a,0x84,0x0d,0xb6,0xa0,0xd2,0x85,0x86,0xc8,0x1a,0x50,0x40,0x69,0x40,0xb2,0x1f,0xf0,0x69,0x50,0x01,0xa5,0x08,0xfc,0x03,0x5f,0x60,0x0d,0x28,0x84,0x1a,0x07,0x18,0x06,0xaf,0x00,0x1a,0x3c,0x03,0xb8,0xc3,0x20,0xd0,0x28,0x87,0xfc,0x8a,0x50,0x08,0x78,0x08,0x70,0x77,0x0c,0x44,0x06,0x05,0x30,0xff,0x18,0x4a,0x01,0x30,0x01,0x0d,0x33,0x19,0x11,0x1b,0x8c,0xa2,0xf8,0x7d,0x27,0x71,0xd0,0x20,0x51,0x20,0x68,0xd5,0x00,0x42,0x0d,0x2c,0x00,0x08,0x64,0x10,0x19,0x20,0x28,0x75,0x07,0x53,0x3d,0x18,0x35,0x2a,0x9f,0xf4,0x9a,0x41,0x90,0x23,0x00,0x94,0x43,0xe0,0x5e,0xae,0x03,0x9d,0xb4,0xe0,0xd1,0x0d,0x8c,0xd0,0x52,0xb1,0x00,0xd9,0x83,0x46,0x34,0x45,0x41,0xa8,0x9f,0x86,0x01,0x14,0x05,0x08,0x08,0x81,0xa6,0x62,0x10,0x68,0xe5,0x20,0x70,0x41,0x80,0x80,0x10,0xc4,0x34,0x48,0x04,0x2a,0x38,0x0d,0x99,0x16,0x02,0x1a,0xd5,0x10,0x6c,0x5e,0x2e,0x0b,0xa1,0x4b,0x0a,0x60,0xc1,0xa7,0x84,0xfc,0x58,0x01,0xb5,0x02,0x82,0xb4,0xc4,0x16,0x22,0xa5,0x06,0x96,0x19,0x20,0x20,0xd7,0x30,0x8c,0x0f,0x08,0x05,0x10,0x68,0xa1,0x44,0x1a,0x98,0x08,0x14,0x11,0x28,0x21,0x91,0x1d,0x8f,0x83,0xfe,0x07,0x1b,0x00,0x34,0x61,0x00,0xd3,0x1d,0x8c,0x7a,0x01,0x7e,0x80,0x56,0x30,0x06,0xb1,0x4a,0x08,0xd4,0xbf,0xc1,0x31,0xc0,0x7f,0xe8,0xf0,0x08,0x3c,0x40,0x1a,0x80,0x04,0x5a,0x8c,0x10,0x80,0x40,0xd7,0x05,0x08,0x36,0xc0,0xe2,0x0d,0xb8,0x30,0x34,0x45,0x82,0x0d,0x72,0x49,0x03,0x5a,0x41,0x55,0xf8,0x7f,0xff,0xe8,0x72,0x06,0xae,0x03,0xf4,0x0c,0x1d,0xf8,0x18,0x60,0x40,0xd2,0x4b,0x9f,0xd0,0x1a,0x35,0x71,0x48,0xc0,0x95,0x42,0x0d,0x4d,0x50,0x70,0x75,0x40,0xd1,0x80,0x83,0x5a,0xa1,0x55,0x00,0x0c,0x05,0xa4,0x20,0xd2,}; +const uint8_t *_I_iButtonDolphinSuccess_109x60[] = {_I_iButtonDolphinSuccess_109x60_0}; + const Icon I_Certification2_119x30 = {.width=119,.height=30,.frame_count=1,.frame_rate=0,.frames=_I_Certification2_119x30}; +const Icon I_Certification1_103x23 = {.width=103,.height=23,.frame_count=1,.frame_rate=0,.frames=_I_Certification1_103x23}; const Icon A_WatchingTV_128x64 = {.width=128,.height=64,.frame_count=4,.frame_rate=1,.frames=_A_WatchingTV_128x64}; const Icon A_Wink_128x64 = {.width=128,.height=64,.frame_count=9,.frame_rate=1,.frames=_A_Wink_128x64}; -const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; -const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; const Icon I_sub1_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_sub1_10px}; const Icon I_ir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ir_10px}; -const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; const Icon I_unknown_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_unknown_10px}; +const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; +const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; const Icon I_ble_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ble_10px}; const Icon I_125_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_125_10px}; +const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; const Icon I_BLE_Pairing_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_BLE_Pairing_128x64}; -const Icon I_ButtonRightSmall_3x5 = {.width=3,.height=5,.frame_count=1,.frame_rate=0,.frames=_I_ButtonRightSmall_3x5}; -const Icon I_ButtonLeft_4x7 = {.width=4,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_ButtonLeft_4x7}; -const Icon I_ButtonLeftSmall_3x5 = {.width=3,.height=5,.frame_count=1,.frame_rate=0,.frames=_I_ButtonLeftSmall_3x5}; -const Icon I_DFU_128x50 = {.width=128,.height=50,.frame_count=1,.frame_rate=0,.frames=_I_DFU_128x50}; -const Icon I_Warning_30x23 = {.width=30,.height=23,.frame_count=1,.frame_rate=0,.frames=_I_Warning_30x23}; const Icon I_ButtonDown_7x4 = {.width=7,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_ButtonDown_7x4}; -const Icon I_ButtonRight_4x7 = {.width=4,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_ButtonRight_4x7}; const Icon I_ButtonCenter_7x7 = {.width=7,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_ButtonCenter_7x7}; +const Icon I_ButtonLeft_4x7 = {.width=4,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_ButtonLeft_4x7}; const Icon I_ButtonUp_7x4 = {.width=7,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_ButtonUp_7x4}; -const Icon I_DolphinOkay_41x43 = {.width=41,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_DolphinOkay_41x43}; -const Icon I_DolphinFirstStart4_67x53 = {.width=67,.height=53,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart4_67x53}; +const Icon I_DFU_128x50 = {.width=128,.height=50,.frame_count=1,.frame_rate=0,.frames=_I_DFU_128x50}; +const Icon I_ButtonLeftSmall_3x5 = {.width=3,.height=5,.frame_count=1,.frame_rate=0,.frames=_I_ButtonLeftSmall_3x5}; +const Icon I_ButtonRightSmall_3x5 = {.width=3,.height=5,.frame_count=1,.frame_rate=0,.frames=_I_ButtonRightSmall_3x5}; +const Icon I_ButtonRight_4x7 = {.width=4,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_ButtonRight_4x7}; +const Icon I_Warning_30x23 = {.width=30,.height=23,.frame_count=1,.frame_rate=0,.frames=_I_Warning_30x23}; const Icon I_DolphinFirstStart2_59x51 = {.width=59,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart2_59x51}; const Icon I_DolphinFirstStart5_54x49 = {.width=54,.height=49,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart5_54x49}; -const Icon I_DolphinFirstStart0_70x53 = {.width=70,.height=53,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart0_70x53}; const Icon I_DolphinFirstStart6_58x54 = {.width=58,.height=54,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart6_58x54}; -const Icon I_DolphinFirstStart1_59x53 = {.width=59,.height=53,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart1_59x53}; -const Icon I_DolphinFirstStart8_56x51 = {.width=56,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart8_56x51}; -const Icon I_DolphinFirstStart7_61x51 = {.width=61,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart7_61x51}; const Icon I_Flipper_young_80x60 = {.width=80,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_Flipper_young_80x60}; +const Icon I_DolphinFirstStart8_56x51 = {.width=56,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart8_56x51}; +const Icon I_DolphinFirstStart1_59x53 = {.width=59,.height=53,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart1_59x53}; +const Icon I_DolphinOkay_41x43 = {.width=41,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_DolphinOkay_41x43}; const Icon I_DolphinFirstStart3_57x48 = {.width=57,.height=48,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart3_57x48}; +const Icon I_DolphinFirstStart7_61x51 = {.width=61,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart7_61x51}; +const Icon I_DolphinFirstStart0_70x53 = {.width=70,.height=53,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart0_70x53}; +const Icon I_DolphinFirstStart4_67x53 = {.width=67,.height=53,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart4_67x53}; const Icon I_PassportBottom_128x17 = {.width=128,.height=17,.frame_count=1,.frame_rate=0,.frames=_I_PassportBottom_128x17}; -const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; const Icon I_DoorLeft_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorLeft_70x55}; -const Icon I_PassportLeft_6x47 = {.width=6,.height=47,.frame_count=1,.frame_rate=0,.frames=_I_PassportLeft_6x47}; const Icon I_DoorRight_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorRight_70x55}; +const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; +const Icon I_PassportLeft_6x47 = {.width=6,.height=47,.frame_count=1,.frame_rate=0,.frames=_I_PassportLeft_6x47}; const Icon I_LockPopup_100x49 = {.width=100,.height=49,.frame_count=1,.frame_rate=0,.frames=_I_LockPopup_100x49}; -const Icon I_Mute_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Mute_25x27}; -const Icon I_IrdaArrowUp_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_IrdaArrowUp_4x8}; -const Icon I_Up_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Up_hvr_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_Vol_down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_down_25x27}; -const Icon I_Down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_25x27}; -const Icon I_Power_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Power_hvr_25x27}; -const Icon I_IrdaLearnShort_128x31 = {.width=128,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_IrdaLearnShort_128x31}; -const Icon I_IrdaArrowDown_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_IrdaArrowDown_4x8}; -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_IrdaLearn_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_IrdaLearn_128x64}; const Icon I_Down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_hvr_25x27}; +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_Down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_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_Power_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Power_25x27}; +const Icon I_Vol_down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_down_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_Up_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Up_25x27}; -const Icon I_Back_15x10 = {.width=15,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Back_15x10}; -const Icon I_IrdaSend_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_IrdaSend_128x64}; -const Icon I_IrdaSendShort_128x34 = {.width=128,.height=34,.frame_count=1,.frame_rate=0,.frames=_I_IrdaSendShort_128x34}; +const Icon I_Up_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Up_hvr_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_KeySave_24x11 = {.width=24,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_KeySave_24x11}; -const Icon I_KeyBackspaceSelected_16x9 = {.width=16,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_KeyBackspaceSelected_16x9}; +const Icon I_IrdaLearnShort_128x31 = {.width=128,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_IrdaLearnShort_128x31}; +const Icon I_IrdaSend_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_IrdaSend_128x64}; +const Icon I_DolphinReadingSuccess_59x63 = {.width=59,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinReadingSuccess_59x63}; +const Icon I_Mute_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Mute_hvr_25x27}; +const Icon I_Back_15x10 = {.width=15,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Back_15x10}; +const Icon I_Up_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Up_25x27}; +const Icon I_IrdaArrowUp_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_IrdaArrowUp_4x8}; +const Icon I_Mute_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Mute_25x27}; +const Icon I_Power_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Power_25x27}; +const Icon I_IrdaSendShort_128x34 = {.width=128,.height=34,.frame_count=1,.frame_rate=0,.frames=_I_IrdaSendShort_128x34}; +const Icon I_IrdaArrowDown_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_IrdaArrowDown_4x8}; +const Icon I_IrdaLearn_128x64 = {.width=128,.height=64,.frame_count=1,.frame_rate=0,.frames=_I_IrdaLearn_128x64}; +const Icon I_Power_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Power_hvr_25x27}; const Icon I_KeySaveSelected_24x11 = {.width=24,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_KeySaveSelected_24x11}; const Icon I_KeyBackspace_16x9 = {.width=16,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_KeyBackspace_16x9}; +const Icon I_KeyBackspaceSelected_16x9 = {.width=16,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_KeyBackspaceSelected_16x9}; +const Icon I_KeySave_24x11 = {.width=24,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_KeySave_24x11}; const Icon A_125khz_14 = {.width=14,.height=14,.frame_count=4,.frame_rate=3,.frames=_A_125khz_14}; const Icon A_Bluetooth_14 = {.width=14,.height=14,.frame_count=6,.frame_rate=3,.frames=_A_Bluetooth_14}; const Icon A_Debug_14 = {.width=14,.height=14,.frame_count=4,.frame_rate=3,.frames=_A_Debug_14}; @@ -548,45 +552,45 @@ const Icon A_U2F_14 = {.width=14,.height=14,.frame_count=4,.frame_rate=3,.frames const Icon A_iButton_14 = {.width=14,.height=14,.frame_count=7,.frame_rate=3,.frames=_A_iButton_14}; const Icon I_Detailed_chip_17x13 = {.width=17,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_Detailed_chip_17x13}; const Icon I_Medium_chip_22x21 = {.width=22,.height=21,.frame_count=1,.frame_rate=0,.frames=_I_Medium_chip_22x21}; -const Icon I_Health_16x16 = {.width=16,.height=16,.frame_count=1,.frame_rate=0,.frames=_I_Health_16x16}; -const Icon I_FaceCharging_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceCharging_29x14}; const Icon I_BatteryBody_52x28 = {.width=52,.height=28,.frame_count=1,.frame_rate=0,.frames=_I_BatteryBody_52x28}; -const Icon I_Voltage_16x16 = {.width=16,.height=16,.frame_count=1,.frame_rate=0,.frames=_I_Voltage_16x16}; +const Icon I_FaceCharging_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceCharging_29x14}; +const Icon I_Health_16x16 = {.width=16,.height=16,.frame_count=1,.frame_rate=0,.frames=_I_Health_16x16}; const Icon I_Temperature_16x16 = {.width=16,.height=16,.frame_count=1,.frame_rate=0,.frames=_I_Temperature_16x16}; -const Icon I_FaceNopower_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceNopower_29x14}; -const Icon I_FaceNormal_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceNormal_29x14}; const Icon I_Battery_16x16 = {.width=16,.height=16,.frame_count=1,.frame_rate=0,.frames=_I_Battery_16x16}; const Icon I_FaceConfused_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceConfused_29x14}; -const Icon I_RFIDDolphinSuccess_108x57 = {.width=108,.height=57,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSuccess_108x57}; -const Icon I_RFIDBigChip_37x36 = {.width=37,.height=36,.frame_count=1,.frame_rate=0,.frames=_I_RFIDBigChip_37x36}; +const Icon I_FaceNormal_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceNormal_29x14}; +const Icon I_Voltage_16x16 = {.width=16,.height=16,.frame_count=1,.frame_rate=0,.frames=_I_Voltage_16x16}; +const Icon I_FaceNopower_29x14 = {.width=29,.height=14,.frame_count=1,.frame_rate=0,.frames=_I_FaceNopower_29x14}; const Icon I_RFIDDolphinSend_97x61 = {.width=97,.height=61,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSend_97x61}; +const Icon I_RFIDDolphinSuccess_108x57 = {.width=108,.height=57,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSuccess_108x57}; const Icon I_RFIDDolphinReceive_97x61 = {.width=97,.height=61,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinReceive_97x61}; +const Icon I_RFIDBigChip_37x36 = {.width=37,.height=36,.frame_count=1,.frame_rate=0,.frames=_I_RFIDBigChip_37x36}; const Icon I_SDQuestion_35x43 = {.width=35,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_SDQuestion_35x43}; const Icon I_SDError_43x35 = {.width=43,.height=35,.frame_count=1,.frame_rate=0,.frames=_I_SDError_43x35}; const Icon I_Cry_dolph_55x52 = {.width=55,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_Cry_dolph_55x52}; -const Icon I_BadUsb_9x8 = {.width=9,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_BadUsb_9x8}; -const Icon I_PlaceholderR_30x13 = {.width=30,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_PlaceholderR_30x13}; -const Icon I_Background_128x8 = {.width=128,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Background_128x8}; +const Icon I_Background_128x11 = {.width=128,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_Background_128x11}; const Icon I_Lock_8x8 = {.width=8,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Lock_8x8}; const Icon I_Battery_26x8 = {.width=26,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Battery_26x8}; -const Icon I_PlaceholderL_11x13 = {.width=11,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_PlaceholderL_11x13}; const Icon I_Battery_19x8 = {.width=19,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Battery_19x8}; -const Icon I_SDcardMounted_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardMounted_11x8}; -const Icon I_SDcardFail_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardFail_11x8}; const Icon I_USBConnected_15x8 = {.width=15,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_USBConnected_15x8}; -const Icon I_Bluetooth_5x8 = {.width=5,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Bluetooth_5x8}; +const Icon I_Background_128x8 = {.width=128,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Background_128x8}; +const Icon I_BadUsb_9x8 = {.width=9,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_BadUsb_9x8}; const Icon I_BT_Pair_9x8 = {.width=9,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_BT_Pair_9x8}; -const Icon I_Background_128x11 = {.width=128,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_Background_128x11}; -const Icon I_Scanning_123x52 = {.width=123,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_Scanning_123x52}; +const Icon I_PlaceholderL_11x13 = {.width=11,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_PlaceholderL_11x13}; +const Icon I_SDcardFail_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardFail_11x8}; +const Icon I_Bluetooth_5x8 = {.width=5,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Bluetooth_5x8}; +const Icon I_PlaceholderR_30x13 = {.width=30,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_PlaceholderR_30x13}; +const Icon I_SDcardMounted_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardMounted_11x8}; const Icon I_Quest_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Quest_7x8}; -const Icon I_Unlock_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Unlock_7x8}; -const Icon I_MHz_25x11 = {.width=25,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_MHz_25x11}; const Icon I_Lock_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Lock_7x8}; -const Icon I_DolphinMafia_115x62 = {.width=115,.height=62,.frame_count=1,.frame_rate=0,.frames=_I_DolphinMafia_115x62}; -const Icon I_DolphinExcited_64x63 = {.width=64,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinExcited_64x63}; -const Icon I_iButtonDolphinSuccess_109x60 = {.width=109,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinSuccess_109x60}; -const Icon I_iButtonDolphinVerySuccess_108x52 = {.width=108,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinVerySuccess_108x52}; +const Icon I_Scanning_123x52 = {.width=123,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_Scanning_123x52}; +const Icon I_MHz_25x11 = {.width=25,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_MHz_25x11}; +const Icon I_Unlock_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Unlock_7x8}; const Icon I_iButtonKey_49x44 = {.width=49,.height=44,.frame_count=1,.frame_rate=0,.frames=_I_iButtonKey_49x44}; -const Icon I_DolphinNice_96x59 = {.width=96,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinNice_96x59}; +const Icon I_DolphinExcited_64x63 = {.width=64,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinExcited_64x63}; const Icon I_DolphinWait_61x59 = {.width=61,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinWait_61x59}; +const Icon I_iButtonDolphinVerySuccess_108x52 = {.width=108,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinVerySuccess_108x52}; +const Icon I_DolphinMafia_115x62 = {.width=115,.height=62,.frame_count=1,.frame_rate=0,.frames=_I_DolphinMafia_115x62}; +const Icon I_DolphinNice_96x59 = {.width=96,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinNice_96x59}; +const Icon I_iButtonDolphinSuccess_109x60 = {.width=109,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinSuccess_109x60}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index cad95865..e0c18aff 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -1,69 +1,70 @@ #pragma once #include -extern const Icon I_Certification1_103x23; extern const Icon I_Certification2_119x30; +extern const Icon I_Certification1_103x23; extern const Icon A_WatchingTV_128x64; extern const Icon A_Wink_128x64; -extern const Icon I_dir_10px; -extern const Icon I_Nfc_10px; extern const Icon I_sub1_10px; extern const Icon I_ir_10px; -extern const Icon I_ibutt_10px; extern const Icon I_unknown_10px; +extern const Icon I_ibutt_10px; +extern const Icon I_Nfc_10px; extern const Icon I_ble_10px; extern const Icon I_125_10px; +extern const Icon I_dir_10px; extern const Icon I_BLE_Pairing_128x64; -extern const Icon I_ButtonRightSmall_3x5; -extern const Icon I_ButtonLeft_4x7; -extern const Icon I_ButtonLeftSmall_3x5; -extern const Icon I_DFU_128x50; -extern const Icon I_Warning_30x23; extern const Icon I_ButtonDown_7x4; -extern const Icon I_ButtonRight_4x7; extern const Icon I_ButtonCenter_7x7; +extern const Icon I_ButtonLeft_4x7; extern const Icon I_ButtonUp_7x4; -extern const Icon I_DolphinOkay_41x43; -extern const Icon I_DolphinFirstStart4_67x53; +extern const Icon I_DFU_128x50; +extern const Icon I_ButtonLeftSmall_3x5; +extern const Icon I_ButtonRightSmall_3x5; +extern const Icon I_ButtonRight_4x7; +extern const Icon I_Warning_30x23; extern const Icon I_DolphinFirstStart2_59x51; extern const Icon I_DolphinFirstStart5_54x49; -extern const Icon I_DolphinFirstStart0_70x53; extern const Icon I_DolphinFirstStart6_58x54; -extern const Icon I_DolphinFirstStart1_59x53; -extern const Icon I_DolphinFirstStart8_56x51; -extern const Icon I_DolphinFirstStart7_61x51; extern const Icon I_Flipper_young_80x60; +extern const Icon I_DolphinFirstStart8_56x51; +extern const Icon I_DolphinFirstStart1_59x53; +extern const Icon I_DolphinOkay_41x43; extern const Icon I_DolphinFirstStart3_57x48; +extern const Icon I_DolphinFirstStart7_61x51; +extern const Icon I_DolphinFirstStart0_70x53; +extern const Icon I_DolphinFirstStart4_67x53; extern const Icon I_PassportBottom_128x17; -extern const Icon I_DoorLocked_10x56; extern const Icon I_DoorLeft_70x55; -extern const Icon I_PassportLeft_6x47; extern const Icon I_DoorRight_70x55; +extern const Icon I_DoorLocked_10x56; +extern const Icon I_PassportLeft_6x47; extern const Icon I_LockPopup_100x49; -extern const Icon I_Mute_25x27; -extern const Icon I_IrdaArrowUp_4x8; -extern const Icon I_Up_hvr_25x27; -extern const Icon I_Mute_hvr_25x27; -extern const Icon I_Vol_down_25x27; -extern const Icon I_Down_25x27; -extern const Icon I_Power_hvr_25x27; -extern const Icon I_IrdaLearnShort_128x31; -extern const Icon I_IrdaArrowDown_4x8; -extern const Icon I_Vol_down_hvr_25x27; -extern const Icon I_IrdaLearn_128x64; extern const Icon I_Down_hvr_25x27; +extern const Icon I_Vol_down_hvr_25x27; +extern const Icon I_Down_25x27; extern const Icon I_Fill_marker_7x7; -extern const Icon I_Power_25x27; +extern const Icon I_Vol_down_25x27; extern const Icon I_Vol_up_25x27; -extern const Icon I_Up_25x27; -extern const Icon I_Back_15x10; -extern const Icon I_IrdaSend_128x64; -extern const Icon I_IrdaSendShort_128x34; +extern const Icon I_Up_hvr_25x27; extern const Icon I_Vol_up_hvr_25x27; -extern const Icon I_KeySave_24x11; -extern const Icon I_KeyBackspaceSelected_16x9; +extern const Icon I_IrdaLearnShort_128x31; +extern const Icon I_IrdaSend_128x64; +extern const Icon I_DolphinReadingSuccess_59x63; +extern const Icon I_Mute_hvr_25x27; +extern const Icon I_Back_15x10; +extern const Icon I_Up_25x27; +extern const Icon I_IrdaArrowUp_4x8; +extern const Icon I_Mute_25x27; +extern const Icon I_Power_25x27; +extern const Icon I_IrdaSendShort_128x34; +extern const Icon I_IrdaArrowDown_4x8; +extern const Icon I_IrdaLearn_128x64; +extern const Icon I_Power_hvr_25x27; extern const Icon I_KeySaveSelected_24x11; extern const Icon I_KeyBackspace_16x9; +extern const Icon I_KeyBackspaceSelected_16x9; +extern const Icon I_KeySave_24x11; extern const Icon A_125khz_14; extern const Icon A_Bluetooth_14; extern const Icon A_Debug_14; @@ -82,44 +83,44 @@ extern const Icon A_U2F_14; extern const Icon A_iButton_14; extern const Icon I_Detailed_chip_17x13; extern const Icon I_Medium_chip_22x21; -extern const Icon I_Health_16x16; -extern const Icon I_FaceCharging_29x14; extern const Icon I_BatteryBody_52x28; -extern const Icon I_Voltage_16x16; +extern const Icon I_FaceCharging_29x14; +extern const Icon I_Health_16x16; extern const Icon I_Temperature_16x16; -extern const Icon I_FaceNopower_29x14; -extern const Icon I_FaceNormal_29x14; extern const Icon I_Battery_16x16; extern const Icon I_FaceConfused_29x14; -extern const Icon I_RFIDDolphinSuccess_108x57; -extern const Icon I_RFIDBigChip_37x36; +extern const Icon I_FaceNormal_29x14; +extern const Icon I_Voltage_16x16; +extern const Icon I_FaceNopower_29x14; extern const Icon I_RFIDDolphinSend_97x61; +extern const Icon I_RFIDDolphinSuccess_108x57; extern const Icon I_RFIDDolphinReceive_97x61; +extern const Icon I_RFIDBigChip_37x36; extern const Icon I_SDQuestion_35x43; extern const Icon I_SDError_43x35; extern const Icon I_Cry_dolph_55x52; -extern const Icon I_BadUsb_9x8; -extern const Icon I_PlaceholderR_30x13; -extern const Icon I_Background_128x8; +extern const Icon I_Background_128x11; extern const Icon I_Lock_8x8; extern const Icon I_Battery_26x8; -extern const Icon I_PlaceholderL_11x13; extern const Icon I_Battery_19x8; -extern const Icon I_SDcardMounted_11x8; -extern const Icon I_SDcardFail_11x8; extern const Icon I_USBConnected_15x8; -extern const Icon I_Bluetooth_5x8; +extern const Icon I_Background_128x8; +extern const Icon I_BadUsb_9x8; extern const Icon I_BT_Pair_9x8; -extern const Icon I_Background_128x11; -extern const Icon I_Scanning_123x52; +extern const Icon I_PlaceholderL_11x13; +extern const Icon I_SDcardFail_11x8; +extern const Icon I_Bluetooth_5x8; +extern const Icon I_PlaceholderR_30x13; +extern const Icon I_SDcardMounted_11x8; extern const Icon I_Quest_7x8; -extern const Icon I_Unlock_7x8; -extern const Icon I_MHz_25x11; extern const Icon I_Lock_7x8; -extern const Icon I_DolphinMafia_115x62; -extern const Icon I_DolphinExcited_64x63; -extern const Icon I_iButtonDolphinSuccess_109x60; -extern const Icon I_iButtonDolphinVerySuccess_108x52; +extern const Icon I_Scanning_123x52; +extern const Icon I_MHz_25x11; +extern const Icon I_Unlock_7x8; extern const Icon I_iButtonKey_49x44; -extern const Icon I_DolphinNice_96x59; +extern const Icon I_DolphinExcited_64x63; extern const Icon I_DolphinWait_61x59; +extern const Icon I_iButtonDolphinVerySuccess_108x52; +extern const Icon I_DolphinMafia_115x62; +extern const Icon I_DolphinNice_96x59; +extern const Icon I_iButtonDolphinSuccess_109x60; diff --git a/assets/compiled/flipper.pb.c b/assets/compiled/flipper.pb.c index d81c78ec..684ffe8c 100644 --- a/assets/compiled/flipper.pb.c +++ b/assets/compiled/flipper.pb.c @@ -9,6 +9,9 @@ PB_BIND(PB_Empty, PB_Empty, AUTO) +PB_BIND(PB_StopSession, PB_StopSession, AUTO) + + PB_BIND(PB_Main, PB_Main, AUTO) diff --git a/assets/compiled/flipper.pb.h b/assets/compiled/flipper.pb.h index d37a38a2..49b5fdee 100644 --- a/assets/compiled/flipper.pb.h +++ b/assets/compiled/flipper.pb.h @@ -7,6 +7,7 @@ #include "storage.pb.h" #include "status.pb.h" #include "application.pb.h" +#include "gui.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -15,12 +16,14 @@ /* Enum definitions */ typedef enum _PB_CommandStatus { PB_CommandStatus_OK = 0, + /* *< Common Errors */ PB_CommandStatus_ERROR = 1, /* *< Unknown error */ PB_CommandStatus_ERROR_DECODE = 2, /* *< Command can't be decoded successfully - command_id in response may be wrong! */ PB_CommandStatus_ERROR_NOT_IMPLEMENTED = 3, /* *< Command succesfully decoded, but not implemented (deprecated or not yet implemented) */ PB_CommandStatus_ERROR_BUSY = 4, /* *< Somebody took global lock, so not all commands are available */ PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED = 14, /* *< Not received has_next == 0 */ PB_CommandStatus_ERROR_INVALID_PARAMETERS = 15, /* *< not provided (or provided invalid) crucial parameters to perform rpc */ + /* *< Storage Errors */ PB_CommandStatus_ERROR_STORAGE_NOT_READY = 5, /* *< FS not ready */ PB_CommandStatus_ERROR_STORAGE_EXIST = 6, /* *< File/Dir alrady exist */ PB_CommandStatus_ERROR_STORAGE_NOT_EXIST = 7, /* *< File/Dir does not exist */ @@ -30,8 +33,13 @@ typedef enum _PB_CommandStatus { PB_CommandStatus_ERROR_STORAGE_INTERNAL = 11, /* *< Internal error */ PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED = 12, /* *< Functon not implemented */ PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN = 13, /* *< File/Dir already opened */ - PB_CommandStatus_ERROR_APP_CANT_START = 16, /* *< Can't start app - or internal error */ - PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED = 17 /* *< Another app is running */ + PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY = 18, /* *< Directory, you're going to remove is not empty */ + /* *< Application Errors */ + PB_CommandStatus_ERROR_APP_CANT_START = 16, /* *< Can't start app - internal error */ + PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED = 17, /* *< Another app is running */ + /* *< Virtual Display Errors */ + PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED = 19, /* *< Virtual Display session can't be started twice */ + PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED = 20 /* *< Virtual Display session can't be stopped when it's not started */ } PB_CommandStatus; /* Struct definitions */ @@ -42,6 +50,10 @@ typedef struct _PB_Empty { char dummy_field; } PB_Empty; +typedef struct _PB_StopSession { + char dummy_field; +} PB_StopSession; + typedef struct _PB_Main { uint32_t command_id; PB_CommandStatus command_status; @@ -61,17 +73,26 @@ typedef struct _PB_Main { PB_Storage_MkdirRequest storage_mkdir_request; PB_Storage_Md5sumRequest storage_md5sum_request; PB_Storage_Md5sumResponse storage_md5sum_response; - PB_App_Start app_start; + PB_App_StartRequest app_start_request; PB_App_LockStatusRequest app_lock_status_request; PB_App_LockStatusResponse app_lock_status_response; + PB_StopSession stop_session; + PB_Gui_StartScreenStreamRequest gui_start_screen_stream_request; + PB_Gui_StopScreenStreamRequest gui_stop_screen_stream_request; + PB_Gui_ScreenFrame gui_screen_frame; + PB_Gui_SendInputEventRequest gui_send_input_event_request; + PB_Storage_StatRequest storage_stat_request; + PB_Storage_StatResponse storage_stat_response; + PB_Gui_StartVirtualDisplayRequest gui_start_virtual_display_request; + PB_Gui_StopVirtualDisplayRequest gui_stop_virtual_display_request; } content; } PB_Main; /* Helper constants for enums */ #define _PB_CommandStatus_MIN PB_CommandStatus_OK -#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED -#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED+1)) +#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED +#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED+1)) #ifdef __cplusplus @@ -80,8 +101,10 @@ extern "C" { /* Initializer values for message structs */ #define PB_Empty_init_default {0} +#define PB_StopSession_init_default {0} #define PB_Main_init_default {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_default}} #define PB_Empty_init_zero {0} +#define PB_StopSession_init_zero {0} #define PB_Main_init_zero {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_zero}} /* Field tags (for use in manual encoding/decoding) */ @@ -100,9 +123,18 @@ extern "C" { #define PB_Main_storage_mkdir_request_tag 13 #define PB_Main_storage_md5sum_request_tag 14 #define PB_Main_storage_md5sum_response_tag 15 -#define PB_Main_app_start_tag 16 +#define PB_Main_app_start_request_tag 16 #define PB_Main_app_lock_status_request_tag 17 #define PB_Main_app_lock_status_response_tag 18 +#define PB_Main_stop_session_tag 19 +#define PB_Main_gui_start_screen_stream_request_tag 20 +#define PB_Main_gui_stop_screen_stream_request_tag 21 +#define PB_Main_gui_screen_frame_tag 22 +#define PB_Main_gui_send_input_event_request_tag 23 +#define PB_Main_storage_stat_request_tag 24 +#define PB_Main_storage_stat_response_tag 25 +#define PB_Main_gui_start_virtual_display_request_tag 26 +#define PB_Main_gui_stop_virtual_display_request_tag 27 /* Struct field encoding specification for nanopb */ #define PB_Empty_FIELDLIST(X, a) \ @@ -110,6 +142,11 @@ extern "C" { #define PB_Empty_CALLBACK NULL #define PB_Empty_DEFAULT NULL +#define PB_StopSession_FIELDLIST(X, a) \ + +#define PB_StopSession_CALLBACK NULL +#define PB_StopSession_DEFAULT NULL + #define PB_Main_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, command_id, 1) \ X(a, STATIC, SINGULAR, UENUM, command_status, 2) \ @@ -126,9 +163,18 @@ X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_delete_request,content.stora X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_mkdir_request,content.storage_mkdir_request), 13) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_md5sum_request,content.storage_md5sum_request), 14) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_md5sum_response,content.storage_md5sum_response), 15) \ -X(a, STATIC, ONEOF, MSG_W_CB, (content,app_start,content.app_start), 16) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,app_start_request,content.app_start_request), 16) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_request,content.app_lock_status_request), 17) \ -X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_response,content.app_lock_status_response), 18) +X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_response,content.app_lock_status_response), 18) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,stop_session,content.stop_session), 19) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,gui_start_screen_stream_request,content.gui_start_screen_stream_request), 20) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,gui_stop_screen_stream_request,content.gui_stop_screen_stream_request), 21) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,gui_screen_frame,content.gui_screen_frame), 22) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,gui_send_input_event_request,content.gui_send_input_event_request), 23) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_stat_request,content.storage_stat_request), 24) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_stat_response,content.storage_stat_response), 25) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,gui_start_virtual_display_request,content.gui_start_virtual_display_request), 26) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,gui_stop_virtual_display_request,content.gui_stop_virtual_display_request), 27) #define PB_Main_CALLBACK NULL #define PB_Main_DEFAULT NULL #define PB_Main_content_empty_MSGTYPE PB_Empty @@ -143,22 +189,34 @@ X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_response,content.app #define PB_Main_content_storage_mkdir_request_MSGTYPE PB_Storage_MkdirRequest #define PB_Main_content_storage_md5sum_request_MSGTYPE PB_Storage_Md5sumRequest #define PB_Main_content_storage_md5sum_response_MSGTYPE PB_Storage_Md5sumResponse -#define PB_Main_content_app_start_MSGTYPE PB_App_Start +#define PB_Main_content_app_start_request_MSGTYPE PB_App_StartRequest #define PB_Main_content_app_lock_status_request_MSGTYPE PB_App_LockStatusRequest #define PB_Main_content_app_lock_status_response_MSGTYPE PB_App_LockStatusResponse +#define PB_Main_content_stop_session_MSGTYPE PB_StopSession +#define PB_Main_content_gui_start_screen_stream_request_MSGTYPE PB_Gui_StartScreenStreamRequest +#define PB_Main_content_gui_stop_screen_stream_request_MSGTYPE PB_Gui_StopScreenStreamRequest +#define PB_Main_content_gui_screen_frame_MSGTYPE PB_Gui_ScreenFrame +#define PB_Main_content_gui_send_input_event_request_MSGTYPE PB_Gui_SendInputEventRequest +#define PB_Main_content_storage_stat_request_MSGTYPE PB_Storage_StatRequest +#define PB_Main_content_storage_stat_response_MSGTYPE PB_Storage_StatResponse +#define PB_Main_content_gui_start_virtual_display_request_MSGTYPE PB_Gui_StartVirtualDisplayRequest +#define PB_Main_content_gui_stop_virtual_display_request_MSGTYPE PB_Gui_StopVirtualDisplayRequest extern const pb_msgdesc_t PB_Empty_msg; +extern const pb_msgdesc_t PB_StopSession_msg; extern const pb_msgdesc_t PB_Main_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define PB_Empty_fields &PB_Empty_msg +#define PB_StopSession_fields &PB_StopSession_msg #define PB_Main_fields &PB_Main_msg /* Maximum encoded size of messages (where known) */ #define PB_Empty_size 0 -#if defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_Start_size) +#define PB_StopSession_size 0 +#if defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_StartRequest_size) && defined(PB_Gui_ScreenFrame_size) && defined(PB_Storage_StatRequest_size) && defined(PB_Storage_StatResponse_size) #define PB_Main_size (10 + sizeof(union PB_Main_content_size_union)) -union PB_Main_content_size_union {char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_Start_size)]; char f0[36];}; +union PB_Main_content_size_union {char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_StartRequest_size)]; char f22[(7 + PB_Gui_ScreenFrame_size)]; char f24[(7 + PB_Storage_StatRequest_size)]; char f25[(7 + PB_Storage_StatResponse_size)]; char f0[36];}; #endif #ifdef __cplusplus diff --git a/assets/compiled/gui.pb.c b/assets/compiled/gui.pb.c new file mode 100644 index 00000000..2c1d8490 --- /dev/null +++ b/assets/compiled/gui.pb.c @@ -0,0 +1,29 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.5 */ + +#include "gui.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(PB_Gui_ScreenFrame, PB_Gui_ScreenFrame, AUTO) + + +PB_BIND(PB_Gui_StartScreenStreamRequest, PB_Gui_StartScreenStreamRequest, AUTO) + + +PB_BIND(PB_Gui_StopScreenStreamRequest, PB_Gui_StopScreenStreamRequest, AUTO) + + +PB_BIND(PB_Gui_SendInputEventRequest, PB_Gui_SendInputEventRequest, AUTO) + + +PB_BIND(PB_Gui_StartVirtualDisplayRequest, PB_Gui_StartVirtualDisplayRequest, AUTO) + + +PB_BIND(PB_Gui_StopVirtualDisplayRequest, PB_Gui_StopVirtualDisplayRequest, AUTO) + + + + + diff --git a/assets/compiled/gui.pb.h b/assets/compiled/gui.pb.h new file mode 100644 index 00000000..84cf1d57 --- /dev/null +++ b/assets/compiled/gui.pb.h @@ -0,0 +1,149 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.5 */ + +#ifndef PB_PB_GUI_GUI_PB_H_INCLUDED +#define PB_PB_GUI_GUI_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +typedef enum _PB_Gui_InputKey { + PB_Gui_InputKey_UP = 0, + PB_Gui_InputKey_DOWN = 1, + PB_Gui_InputKey_RIGHT = 2, + PB_Gui_InputKey_LEFT = 3, + PB_Gui_InputKey_OK = 4, + PB_Gui_InputKey_BACK = 5 +} PB_Gui_InputKey; + +typedef enum _PB_Gui_InputType { + PB_Gui_InputType_PRESS = 0, /* *< Press event, emitted after debounce */ + PB_Gui_InputType_RELEASE = 1, /* *< Release event, emitted after debounce */ + PB_Gui_InputType_SHORT = 2, /* *< Short event, emitted after InputTypeRelease done withing INPUT_LONG_PRESS interval */ + PB_Gui_InputType_LONG = 3, /* *< Long event, emmited after INPUT_LONG_PRESS interval, asynchronouse to InputTypeRelease */ + PB_Gui_InputType_REPEAT = 4 /* *< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ +} PB_Gui_InputType; + +/* Struct definitions */ +typedef struct _PB_Gui_ScreenFrame { + pb_bytes_array_t *data; +} PB_Gui_ScreenFrame; + +typedef struct _PB_Gui_StartScreenStreamRequest { + char dummy_field; +} PB_Gui_StartScreenStreamRequest; + +typedef struct _PB_Gui_StartVirtualDisplayRequest { + char dummy_field; +} PB_Gui_StartVirtualDisplayRequest; + +typedef struct _PB_Gui_StopScreenStreamRequest { + char dummy_field; +} PB_Gui_StopScreenStreamRequest; + +typedef struct _PB_Gui_StopVirtualDisplayRequest { + char dummy_field; +} PB_Gui_StopVirtualDisplayRequest; + +typedef struct _PB_Gui_SendInputEventRequest { + PB_Gui_InputKey key; + PB_Gui_InputType type; +} PB_Gui_SendInputEventRequest; + + +/* Helper constants for enums */ +#define _PB_Gui_InputKey_MIN PB_Gui_InputKey_UP +#define _PB_Gui_InputKey_MAX PB_Gui_InputKey_BACK +#define _PB_Gui_InputKey_ARRAYSIZE ((PB_Gui_InputKey)(PB_Gui_InputKey_BACK+1)) + +#define _PB_Gui_InputType_MIN PB_Gui_InputType_PRESS +#define _PB_Gui_InputType_MAX PB_Gui_InputType_REPEAT +#define _PB_Gui_InputType_ARRAYSIZE ((PB_Gui_InputType)(PB_Gui_InputType_REPEAT+1)) + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define PB_Gui_ScreenFrame_init_default {NULL} +#define PB_Gui_StartScreenStreamRequest_init_default {0} +#define PB_Gui_StopScreenStreamRequest_init_default {0} +#define PB_Gui_SendInputEventRequest_init_default {_PB_Gui_InputKey_MIN, _PB_Gui_InputType_MIN} +#define PB_Gui_StartVirtualDisplayRequest_init_default {0} +#define PB_Gui_StopVirtualDisplayRequest_init_default {0} +#define PB_Gui_ScreenFrame_init_zero {NULL} +#define PB_Gui_StartScreenStreamRequest_init_zero {0} +#define PB_Gui_StopScreenStreamRequest_init_zero {0} +#define PB_Gui_SendInputEventRequest_init_zero {_PB_Gui_InputKey_MIN, _PB_Gui_InputType_MIN} +#define PB_Gui_StartVirtualDisplayRequest_init_zero {0} +#define PB_Gui_StopVirtualDisplayRequest_init_zero {0} + +/* Field tags (for use in manual encoding/decoding) */ +#define PB_Gui_ScreenFrame_data_tag 1 +#define PB_Gui_SendInputEventRequest_key_tag 1 +#define PB_Gui_SendInputEventRequest_type_tag 2 + +/* Struct field encoding specification for nanopb */ +#define PB_Gui_ScreenFrame_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, BYTES, data, 1) +#define PB_Gui_ScreenFrame_CALLBACK NULL +#define PB_Gui_ScreenFrame_DEFAULT NULL + +#define PB_Gui_StartScreenStreamRequest_FIELDLIST(X, a) \ + +#define PB_Gui_StartScreenStreamRequest_CALLBACK NULL +#define PB_Gui_StartScreenStreamRequest_DEFAULT NULL + +#define PB_Gui_StopScreenStreamRequest_FIELDLIST(X, a) \ + +#define PB_Gui_StopScreenStreamRequest_CALLBACK NULL +#define PB_Gui_StopScreenStreamRequest_DEFAULT NULL + +#define PB_Gui_SendInputEventRequest_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, key, 1) \ +X(a, STATIC, SINGULAR, UENUM, type, 2) +#define PB_Gui_SendInputEventRequest_CALLBACK NULL +#define PB_Gui_SendInputEventRequest_DEFAULT NULL + +#define PB_Gui_StartVirtualDisplayRequest_FIELDLIST(X, a) \ + +#define PB_Gui_StartVirtualDisplayRequest_CALLBACK NULL +#define PB_Gui_StartVirtualDisplayRequest_DEFAULT NULL + +#define PB_Gui_StopVirtualDisplayRequest_FIELDLIST(X, a) \ + +#define PB_Gui_StopVirtualDisplayRequest_CALLBACK NULL +#define PB_Gui_StopVirtualDisplayRequest_DEFAULT NULL + +extern const pb_msgdesc_t PB_Gui_ScreenFrame_msg; +extern const pb_msgdesc_t PB_Gui_StartScreenStreamRequest_msg; +extern const pb_msgdesc_t PB_Gui_StopScreenStreamRequest_msg; +extern const pb_msgdesc_t PB_Gui_SendInputEventRequest_msg; +extern const pb_msgdesc_t PB_Gui_StartVirtualDisplayRequest_msg; +extern const pb_msgdesc_t PB_Gui_StopVirtualDisplayRequest_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define PB_Gui_ScreenFrame_fields &PB_Gui_ScreenFrame_msg +#define PB_Gui_StartScreenStreamRequest_fields &PB_Gui_StartScreenStreamRequest_msg +#define PB_Gui_StopScreenStreamRequest_fields &PB_Gui_StopScreenStreamRequest_msg +#define PB_Gui_SendInputEventRequest_fields &PB_Gui_SendInputEventRequest_msg +#define PB_Gui_StartVirtualDisplayRequest_fields &PB_Gui_StartVirtualDisplayRequest_msg +#define PB_Gui_StopVirtualDisplayRequest_fields &PB_Gui_StopVirtualDisplayRequest_msg + +/* Maximum encoded size of messages (where known) */ +/* PB_Gui_ScreenFrame_size depends on runtime parameters */ +#define PB_Gui_SendInputEventRequest_size 4 +#define PB_Gui_StartScreenStreamRequest_size 0 +#define PB_Gui_StartVirtualDisplayRequest_size 0 +#define PB_Gui_StopScreenStreamRequest_size 0 +#define PB_Gui_StopVirtualDisplayRequest_size 0 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/assets/compiled/input.pb.c b/assets/compiled/input.pb.c new file mode 100644 index 00000000..6ce50704 --- /dev/null +++ b/assets/compiled/input.pb.c @@ -0,0 +1,14 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.5 */ + +#include "input.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(PB_Input_SendEventRequest, PB_Input_SendEventRequest, AUTO) + + + + + diff --git a/assets/compiled/input.pb.h b/assets/compiled/input.pb.h new file mode 100644 index 00000000..46f78113 --- /dev/null +++ b/assets/compiled/input.pb.h @@ -0,0 +1,78 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.5 */ + +#ifndef PB_PB_INPUT_INPUT_PB_H_INCLUDED +#define PB_PB_INPUT_INPUT_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +typedef enum _PB_Input_Key { + PB_Input_Key_UP = 0, + PB_Input_Key_DOWN = 1, + PB_Input_Key_RIGHT = 2, + PB_Input_Key_LEFT = 3, + PB_Input_Key_OK = 4, + PB_Input_Key_BACK = 5 +} PB_Input_Key; + +typedef enum _PB_Input_Type { + PB_Input_Type_PRESS = 0, /* *< Press event, emitted after debounce */ + PB_Input_Type_RELEASE = 1, /* *< Release event, emitted after debounce */ + PB_Input_Type_SHORT = 2, /* *< Short event, emitted after InputTypeRelease done withing INPUT_LONG_PRESS interval */ + PB_Input_Type_LONG = 3, /* *< Long event, emmited after INPUT_LONG_PRESS interval, asynchronouse to InputTypeRelease */ + PB_Input_Type_REPEAT = 4 /* *< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ +} PB_Input_Type; + +/* Struct definitions */ +typedef struct _PB_Input_SendEventRequest { + PB_Input_Key key; + PB_Input_Type type; +} PB_Input_SendEventRequest; + + +/* Helper constants for enums */ +#define _PB_Input_Key_MIN PB_Input_Key_UP +#define _PB_Input_Key_MAX PB_Input_Key_BACK +#define _PB_Input_Key_ARRAYSIZE ((PB_Input_Key)(PB_Input_Key_BACK+1)) + +#define _PB_Input_Type_MIN PB_Input_Type_PRESS +#define _PB_Input_Type_MAX PB_Input_Type_REPEAT +#define _PB_Input_Type_ARRAYSIZE ((PB_Input_Type)(PB_Input_Type_REPEAT+1)) + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define PB_Input_SendEventRequest_init_default {_PB_Input_Key_MIN, _PB_Input_Type_MIN} +#define PB_Input_SendEventRequest_init_zero {_PB_Input_Key_MIN, _PB_Input_Type_MIN} + +/* Field tags (for use in manual encoding/decoding) */ +#define PB_Input_SendEventRequest_key_tag 1 +#define PB_Input_SendEventRequest_type_tag 2 + +/* Struct field encoding specification for nanopb */ +#define PB_Input_SendEventRequest_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, key, 1) \ +X(a, STATIC, SINGULAR, UENUM, type, 2) +#define PB_Input_SendEventRequest_CALLBACK NULL +#define PB_Input_SendEventRequest_DEFAULT NULL + +extern const pb_msgdesc_t PB_Input_SendEventRequest_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define PB_Input_SendEventRequest_fields &PB_Input_SendEventRequest_msg + +/* Maximum encoded size of messages (where known) */ +#define PB_Input_SendEventRequest_size 4 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/assets/compiled/screen.pb.c b/assets/compiled/screen.pb.c new file mode 100644 index 00000000..a87a3dd6 --- /dev/null +++ b/assets/compiled/screen.pb.c @@ -0,0 +1,18 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.5 */ + +#include "screen.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(PB_Screen_StartStreamRequest, PB_Screen_StartStreamRequest, AUTO) + + +PB_BIND(PB_Screen_StopStreamRequest, PB_Screen_StopStreamRequest, AUTO) + + +PB_BIND(PB_Screen_StreamFrame, PB_Screen_StreamFrame, AUTO) + + + diff --git a/assets/compiled/screen.pb.h b/assets/compiled/screen.pb.h new file mode 100644 index 00000000..1c409a6f --- /dev/null +++ b/assets/compiled/screen.pb.h @@ -0,0 +1,75 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.5 */ + +#ifndef PB_PB_SCREEN_SCREEN_PB_H_INCLUDED +#define PB_PB_SCREEN_SCREEN_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +typedef struct _PB_Screen_StartStreamRequest { + char dummy_field; +} PB_Screen_StartStreamRequest; + +typedef struct _PB_Screen_StopStreamRequest { + char dummy_field; +} PB_Screen_StopStreamRequest; + +typedef struct _PB_Screen_StreamFrame { + pb_bytes_array_t *data; +} PB_Screen_StreamFrame; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define PB_Screen_StartStreamRequest_init_default {0} +#define PB_Screen_StopStreamRequest_init_default {0} +#define PB_Screen_StreamFrame_init_default {NULL} +#define PB_Screen_StartStreamRequest_init_zero {0} +#define PB_Screen_StopStreamRequest_init_zero {0} +#define PB_Screen_StreamFrame_init_zero {NULL} + +/* Field tags (for use in manual encoding/decoding) */ +#define PB_Screen_StreamFrame_data_tag 1 + +/* Struct field encoding specification for nanopb */ +#define PB_Screen_StartStreamRequest_FIELDLIST(X, a) \ + +#define PB_Screen_StartStreamRequest_CALLBACK NULL +#define PB_Screen_StartStreamRequest_DEFAULT NULL + +#define PB_Screen_StopStreamRequest_FIELDLIST(X, a) \ + +#define PB_Screen_StopStreamRequest_CALLBACK NULL +#define PB_Screen_StopStreamRequest_DEFAULT NULL + +#define PB_Screen_StreamFrame_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, BYTES, data, 1) +#define PB_Screen_StreamFrame_CALLBACK NULL +#define PB_Screen_StreamFrame_DEFAULT NULL + +extern const pb_msgdesc_t PB_Screen_StartStreamRequest_msg; +extern const pb_msgdesc_t PB_Screen_StopStreamRequest_msg; +extern const pb_msgdesc_t PB_Screen_StreamFrame_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define PB_Screen_StartStreamRequest_fields &PB_Screen_StartStreamRequest_msg +#define PB_Screen_StopStreamRequest_fields &PB_Screen_StopStreamRequest_msg +#define PB_Screen_StreamFrame_fields &PB_Screen_StreamFrame_msg + +/* Maximum encoded size of messages (where known) */ +/* PB_Screen_StreamFrame_size depends on runtime parameters */ +#define PB_Screen_StartStreamRequest_size 0 +#define PB_Screen_StopStreamRequest_size 0 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/assets/compiled/storage.pb.c b/assets/compiled/storage.pb.c index a74477e1..24577c23 100644 --- a/assets/compiled/storage.pb.c +++ b/assets/compiled/storage.pb.c @@ -9,6 +9,12 @@ PB_BIND(PB_Storage_File, PB_Storage_File, AUTO) +PB_BIND(PB_Storage_StatRequest, PB_Storage_StatRequest, AUTO) + + +PB_BIND(PB_Storage_StatResponse, PB_Storage_StatResponse, AUTO) + + PB_BIND(PB_Storage_ListRequest, PB_Storage_ListRequest, AUTO) diff --git a/assets/compiled/storage.pb.h b/assets/compiled/storage.pb.h index cdb47372..2e82a8de 100644 --- a/assets/compiled/storage.pb.h +++ b/assets/compiled/storage.pb.h @@ -16,10 +16,6 @@ typedef enum _PB_Storage_File_FileType { } PB_Storage_File_FileType; /* Struct definitions */ -typedef struct _PB_Storage_DeleteRequest { - char *path; -} PB_Storage_DeleteRequest; - typedef struct _PB_Storage_ListRequest { char *path; } PB_Storage_ListRequest; @@ -36,6 +32,15 @@ typedef struct _PB_Storage_ReadRequest { char *path; } PB_Storage_ReadRequest; +typedef struct _PB_Storage_StatRequest { + char *path; +} PB_Storage_StatRequest; + +typedef struct _PB_Storage_DeleteRequest { + char *path; + bool recursive; +} PB_Storage_DeleteRequest; + typedef struct _PB_Storage_File { PB_Storage_File_FileType type; char *name; @@ -57,6 +62,11 @@ typedef struct _PB_Storage_ReadResponse { PB_Storage_File file; } PB_Storage_ReadResponse; +typedef struct _PB_Storage_StatResponse { + bool has_file; + PB_Storage_File file; +} PB_Storage_StatResponse; + typedef struct _PB_Storage_WriteRequest { char *path; bool has_file; @@ -76,32 +86,38 @@ extern "C" { /* Initializer values for message structs */ #define PB_Storage_File_init_default {_PB_Storage_File_FileType_MIN, NULL, 0, NULL} +#define PB_Storage_StatRequest_init_default {NULL} +#define PB_Storage_StatResponse_init_default {false, PB_Storage_File_init_default} #define PB_Storage_ListRequest_init_default {NULL} #define PB_Storage_ListResponse_init_default {0, {PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default}} #define PB_Storage_ReadRequest_init_default {NULL} #define PB_Storage_ReadResponse_init_default {false, PB_Storage_File_init_default} #define PB_Storage_WriteRequest_init_default {NULL, false, PB_Storage_File_init_default} -#define PB_Storage_DeleteRequest_init_default {NULL} +#define PB_Storage_DeleteRequest_init_default {NULL, 0} #define PB_Storage_MkdirRequest_init_default {NULL} #define PB_Storage_Md5sumRequest_init_default {NULL} #define PB_Storage_Md5sumResponse_init_default {""} #define PB_Storage_File_init_zero {_PB_Storage_File_FileType_MIN, NULL, 0, NULL} +#define PB_Storage_StatRequest_init_zero {NULL} +#define PB_Storage_StatResponse_init_zero {false, PB_Storage_File_init_zero} #define PB_Storage_ListRequest_init_zero {NULL} #define PB_Storage_ListResponse_init_zero {0, {PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero}} #define PB_Storage_ReadRequest_init_zero {NULL} #define PB_Storage_ReadResponse_init_zero {false, PB_Storage_File_init_zero} #define PB_Storage_WriteRequest_init_zero {NULL, false, PB_Storage_File_init_zero} -#define PB_Storage_DeleteRequest_init_zero {NULL} +#define PB_Storage_DeleteRequest_init_zero {NULL, 0} #define PB_Storage_MkdirRequest_init_zero {NULL} #define PB_Storage_Md5sumRequest_init_zero {NULL} #define PB_Storage_Md5sumResponse_init_zero {""} /* Field tags (for use in manual encoding/decoding) */ -#define PB_Storage_DeleteRequest_path_tag 1 #define PB_Storage_ListRequest_path_tag 1 #define PB_Storage_Md5sumRequest_path_tag 1 #define PB_Storage_MkdirRequest_path_tag 1 #define PB_Storage_ReadRequest_path_tag 1 +#define PB_Storage_StatRequest_path_tag 1 +#define PB_Storage_DeleteRequest_path_tag 1 +#define PB_Storage_DeleteRequest_recursive_tag 2 #define PB_Storage_File_type_tag 1 #define PB_Storage_File_name_tag 2 #define PB_Storage_File_size_tag 3 @@ -109,6 +125,7 @@ extern "C" { #define PB_Storage_Md5sumResponse_md5sum_tag 1 #define PB_Storage_ListResponse_file_tag 1 #define PB_Storage_ReadResponse_file_tag 1 +#define PB_Storage_StatResponse_file_tag 1 #define PB_Storage_WriteRequest_path_tag 1 #define PB_Storage_WriteRequest_file_tag 2 @@ -121,6 +138,17 @@ X(a, POINTER, SINGULAR, BYTES, data, 4) #define PB_Storage_File_CALLBACK NULL #define PB_Storage_File_DEFAULT NULL +#define PB_Storage_StatRequest_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, STRING, path, 1) +#define PB_Storage_StatRequest_CALLBACK NULL +#define PB_Storage_StatRequest_DEFAULT NULL + +#define PB_Storage_StatResponse_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, file, 1) +#define PB_Storage_StatResponse_CALLBACK NULL +#define PB_Storage_StatResponse_DEFAULT NULL +#define PB_Storage_StatResponse_file_MSGTYPE PB_Storage_File + #define PB_Storage_ListRequest_FIELDLIST(X, a) \ X(a, POINTER, SINGULAR, STRING, path, 1) #define PB_Storage_ListRequest_CALLBACK NULL @@ -151,7 +179,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, file, 2) #define PB_Storage_WriteRequest_file_MSGTYPE PB_Storage_File #define PB_Storage_DeleteRequest_FIELDLIST(X, a) \ -X(a, POINTER, SINGULAR, STRING, path, 1) +X(a, POINTER, SINGULAR, STRING, path, 1) \ +X(a, STATIC, SINGULAR, BOOL, recursive, 2) #define PB_Storage_DeleteRequest_CALLBACK NULL #define PB_Storage_DeleteRequest_DEFAULT NULL @@ -171,6 +200,8 @@ X(a, STATIC, SINGULAR, STRING, md5sum, 1) #define PB_Storage_Md5sumResponse_DEFAULT NULL extern const pb_msgdesc_t PB_Storage_File_msg; +extern const pb_msgdesc_t PB_Storage_StatRequest_msg; +extern const pb_msgdesc_t PB_Storage_StatResponse_msg; extern const pb_msgdesc_t PB_Storage_ListRequest_msg; extern const pb_msgdesc_t PB_Storage_ListResponse_msg; extern const pb_msgdesc_t PB_Storage_ReadRequest_msg; @@ -183,6 +214,8 @@ extern const pb_msgdesc_t PB_Storage_Md5sumResponse_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define PB_Storage_File_fields &PB_Storage_File_msg +#define PB_Storage_StatRequest_fields &PB_Storage_StatRequest_msg +#define PB_Storage_StatResponse_fields &PB_Storage_StatResponse_msg #define PB_Storage_ListRequest_fields &PB_Storage_ListRequest_msg #define PB_Storage_ListResponse_fields &PB_Storage_ListResponse_msg #define PB_Storage_ReadRequest_fields &PB_Storage_ReadRequest_msg @@ -195,6 +228,8 @@ extern const pb_msgdesc_t PB_Storage_Md5sumResponse_msg; /* Maximum encoded size of messages (where known) */ /* PB_Storage_File_size depends on runtime parameters */ +/* PB_Storage_StatRequest_size depends on runtime parameters */ +/* PB_Storage_StatResponse_size depends on runtime parameters */ /* PB_Storage_ListRequest_size depends on runtime parameters */ /* PB_Storage_ListResponse_size depends on runtime parameters */ /* PB_Storage_ReadRequest_size depends on runtime parameters */ diff --git a/assets/icons/Irda/DolphinReadingSuccess_59x63.png b/assets/icons/Irda/DolphinReadingSuccess_59x63.png new file mode 100644 index 00000000..46f559f6 Binary files /dev/null and b/assets/icons/Irda/DolphinReadingSuccess_59x63.png differ diff --git a/assets/protobuf b/assets/protobuf index 8e6db414..6be7def6 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 8e6db414beed5aff0902f2cca2f4146a0dffb7a1 +Subproject commit 6be7def6087c4d277386381ff2792fa622933668 diff --git a/assets/resources/nfc/emv/aid.nfc b/assets/resources/nfc/emv/aid.nfc index 2655df56..43854f3d 100644 --- a/assets/resources/nfc/emv/aid.nfc +++ b/assets/resources/nfc/emv/aid.nfc @@ -1,148 +1,151 @@ -A00000000305076010 VISA ELO Credit -A0000000031010 VISA Debit/Credit (Classic) -A000000003101001 VISA Credit -A000000003101002 VISA Debit -A0000000032010 VISA Electron -A0000000032020 VISA -A0000000033010 VISA Interlink -A0000000034010 VISA Specific -A0000000035010 VISA Specific -A0000000036010 Domestic Visa Cash -A0000000036020 International Visa Cash -A0000000038002 VISA Auth EMV-CAP (DPA) -A0000000038010 VISA Plus -A0000000039010 VISA Loyalty -A000000003999910 VISA Proprietary ATM -A00000000401 MasterCard PayPass -A0000000041010 MasterCard Global -A00000000410101213 MasterCard Credit -A00000000410101215 MasterCard Credit -A0000000042010 MasterCard Specific -A0000000043010 MasterCard Specific -A0000000043060 Maestro (Debit) -A000000004306001 Maestro (Debit) -A0000000044010 MasterCard Specific -A0000000045010 MasterCard Specific -A0000000046000 Cirrus -A0000000048002 SecureCode EMV-CAP -A0000000049999 MasterCard PayPass -A0000000050001 Maestro UK -A0000000050002 Solo -A00000002401 Self Service -A000000025 American Express -A0000000250000 American Express -A00000002501 American Express -A000000025010402 American Express -A000000025010701 ExpressPay -A000000025010801 American Express -A0000000291010 Link / American Express -A0000000421010 Cartes Bancaire EMV Card -A0000000426010 Apple Pay -A00000006510 JCB -A0000000651010 JCB J Smart Credit -A00000006900 Moneo -A000000077010000021000000000003B Visa AEPN -A000000098 Debit Card -A0000000980848 Debit Card -A0000001211010 Dankort VISA GEM Vision -A0000001410001 PagoBANCOMAT -A0000001523010 Discover, Pulse D Pas -A0000001524010 Discover -A0000001544442 Banricompras Debito -A000000172950001 BAROC Taiwan -A0000002281010 SPAN (M/Chip) -A0000002282010 SPAN (VIS) -A0000002771010 INTERAC -A00000031510100528 Currence PuC -A0000003156020 Chipknip -A0000003591010028001 Girocard EAPS -A0000003710001 InterSwitch Verve Card -A0000004540010 Etranzact Genesis Card -A0000004540011 Etranzact Genesis Card 2 -A0000004766C GOOGLE_PAYMENT -A0000005241010 RuPay -A0000006723010 TROY chip credit card -A0000006723020 TROY chip debit card -A0000007705850 XTRAPOWER -B012345678 Maestro TEST -D27600002545500100 Girocard -D5780000021010 Bankaxept -F0000000030001 BRADESCO -A000000003000000 (VISA) Card Manager -A000000003534441 Schlumberger SD -A0000000035350 Security Domain -A000000003535041 Security Domain -A0000000040000 MasterCard Card Manager -A000000018434D Gemplus card manager -A000000018434D00 Gemplus Security Domain -A0000000960200 Proton WISD -A0000001510000 Global Platform SD -A00000015153504341534400 CASD_AID -A000000476A010 GSD_MANAGER_AID -A000000476A110 GSD_MANAGER_AID -315041592E5359532E4444463031 Visa PSE -325041592E5359532E4444463031 Visa PPSE -A0000000042203 MasterCard Specific -A0000000045555 APDULogger -A0000000090001FF44FF1289 Orange -A0000000101030 Maestro-CH -A00000001800 Gemplus -A0000000181001 gemplus util packages -A000000025010104 American Express -A00000002949034010100001 HSBC -A00000002949282010100000 Barclay -A00000005945430100 Girocard Electronic Cash -A0000000980840 Visa Common Debit -A0000001570010 AMEX -A0000001570020 MasterCard -A0000001570021 Maestro -A0000001570022 Maestro -A0000001570023 CASH -A0000001570030 VISA -A0000001570031 VISA -A0000001570040 JCB -A0000001570050 Postcard -A0000001570051 Postcard -A0000001570100 MCard -A0000001570104 MyOne -A000000157010C WIRCard -A000000157010D Power Card -A0000001574443 DINERS CLUB -A0000001574444 Supercard Plus -A00000022820101010 SPAN -A000000308000010000100 ID-ONE PIV BIO -A0000003241010 Discover Zip -A000000333010101 UnionPay Debit -A000000333010102 UnionPay Credit -A000000333010103 UnionPay Quasi Credit -A000000333010106 UnionPay Electronic Cash -A000000333010108 U.S. UnionPay Common Debit -A000000337102000 Classic -A000000337101001 Prepaye Online -A000000337102001 Prepaye Possibile Offiline -A000000337601001 Porte Monnaie Electronique -A0000006581010 MIR Credit -A0000006581011 MIR Credit -A0000006582010 MIR Debit -D040000001000002 Paylife Quick IEP -D040000002000002 RFU -D040000003000002 POS -D040000004000002 ATM -D04000000B000002 Retail -D04000000C000002 Bank_Data -D04000000D000002 Shopping -D040000013000001 DF_UNI_Kepler1 -D040000013000001 DF_Schüler1 -D040000013000002 DF_UNI_Kepler2 -D040000013000002 DF_Schüler2 -D040000014000001 DF_Mensa -D040000015000001 DF_UNI_Ausweis -D040000015000001 DF_Ausweis -D0400000190001 EMV ATM Maestro -D0400000190002 EMV POS Maestro -D0400000190003 EMV ATM MasterCard -D0400000190004 EMV POS MasterCard -D276000025 Girocard -D27600002547410100 Girocard ATM -D7560000010101 Reka Card -D7560000300101 M Budget \ No newline at end of file +Filetype: Flipper EMV resources +Version: 1 +# EMV Application ID code: Application ID name +A00000000305076010: VISA ELO Credit +A0000000031010: VISA Debit/Credit (Classic) +A000000003101001: VISA Credit +A000000003101002: VISA Debit +A0000000032010: VISA Electron +A0000000032020: VISA +A0000000033010: VISA Interlink +A0000000034010: VISA Specific +A0000000035010: VISA Specific +A0000000036010: Domestic Visa Cash +A0000000036020: International Visa Cash +A0000000038002: VISA Auth EMV-CAP (DPA) +A0000000038010: VISA Plus +A0000000039010: VISA Loyalty +A000000003999910: VISA Proprietary ATM +A00000000401: MasterCard PayPass +A0000000041010: MasterCard Global +A00000000410101213: MasterCard Credit +A00000000410101215: MasterCard Credit +A0000000042010: MasterCard Specific +A0000000043010: MasterCard Specific +A0000000043060: Maestro (Debit) +A000000004306001: Maestro (Debit) +A0000000044010: MasterCard Specific +A0000000045010: MasterCard Specific +A0000000046000: Cirrus +A0000000048002: SecureCode EMV-CAP +A0000000049999: MasterCard PayPass +A0000000050001: Maestro UK +A0000000050002: Solo +A00000002401: Self Service +A000000025: American Express +A0000000250000: American Express +A00000002501: American Express +A000000025010402: American Express +A000000025010701: ExpressPay +A000000025010801: American Express +A0000000291010: Link / American Express +A0000000421010: Cartes Bancaire EMV Card +A0000000426010: Apple Pay +A00000006510: JCB +A0000000651010: JCB J Smart Credit +A00000006900: Moneo +A000000077010000021000000000003B: Visa AEPN +A000000098: Debit Card +A0000000980848: Debit Card +A0000001211010: Dankort VISA GEM Vision +A0000001410001: PagoBANCOMAT +A0000001523010: Discover, Pulse D Pas +A0000001524010: Discover +A0000001544442: Banricompras Debito +A000000172950001: BAROC Taiwan +A0000002281010: SPAN (M/Chip) +A0000002282010: SPAN (VIS) +A0000002771010: INTERAC +A00000031510100528: Currence PuC +A0000003156020: Chipknip +A0000003591010028001: Girocard EAPS +A0000003710001: InterSwitch Verve Card +A0000004540010: Etranzact Genesis Card +A0000004540011: Etranzact Genesis Card 2 +A0000004766C: GOOGLE_PAYMENT +A0000005241010: RuPay +A0000006723010: TROY chip credit card +A0000006723020: TROY chip debit card +A0000007705850: XTRAPOWER +B012345678: Maestro TEST +D27600002545500100: Girocard +D5780000021010: Bankaxept +F0000000030001: BRADESCO +A000000003000000: (VISA) Card Manager +A000000003534441: Schlumberger SD +A0000000035350: Security Domain +A000000003535041: Security Domain +A0000000040000: MasterCard Card Manager +A000000018434D: Gemplus card manager +A000000018434D00: Gemplus Security Domain +A0000000960200: Proton WISD +A0000001510000: Global Platform SD +A00000015153504341534400: CASD_AID +A000000476A010: GSD_MANAGER_AID +A000000476A110: GSD_MANAGER_AID +315041592E5359532E4444463031: Visa PSE +325041592E5359532E4444463031: Visa PPSE +A0000000042203: MasterCard Specific +A0000000045555: APDULogger +A0000000090001FF44FF1289: Orange +A0000000101030: Maestro-CH +A00000001800: Gemplus +A0000000181001: gemplus util packages +A000000025010104: American Express +A00000002949034010100001: HSBC +A00000002949282010100000: Barclay +A00000005945430100: Girocard Electronic Cash +A0000000980840: Visa Common Debit +A0000001570010: AMEX +A0000001570020: MasterCard +A0000001570021: Maestro +A0000001570022: Maestro +A0000001570023: CASH +A0000001570030: VISA +A0000001570031: VISA +A0000001570040: JCB +A0000001570050: Postcard +A0000001570051: Postcard +A0000001570100: MCard +A0000001570104: MyOne +A000000157010C: WIRCard +A000000157010D: Power Card +A0000001574443: DINERS CLUB +A0000001574444: Supercard Plus +A00000022820101010: SPAN +A000000308000010000100: ID-ONE PIV BIO +A0000003241010: Discover Zip +A000000333010101: UnionPay Debit +A000000333010102: UnionPay Credit +A000000333010103: UnionPay Quasi Credit +A000000333010106: UnionPay Electronic Cash +A000000333010108: U.S. UnionPay Common Debit +A000000337102000: Classic +A000000337101001: Prepaye Online +A000000337102001: Prepaye Possibile Offiline +A000000337601001: Porte Monnaie Electronique +A0000006581010: MIR Credit +A0000006581011: MIR Credit +A0000006582010: MIR Debit +D040000001000002: Paylife Quick IEP +D040000002000002: RFU +D040000003000002: POS +D040000004000002: ATM +D04000000B000002: Retail +D04000000C000002: Bank_Data +D04000000D000002: Shopping +D040000013000001: DF_UNI_Kepler1 +D040000013000001: DF_Schüler1 +D040000013000002: DF_UNI_Kepler2 +D040000013000002: DF_Schüler2 +D040000014000001: DF_Mensa +D040000015000001: DF_UNI_Ausweis +D040000015000001: DF_Ausweis +D0400000190001: EMV ATM Maestro +D0400000190002: EMV POS Maestro +D0400000190003: EMV ATM MasterCard +D0400000190004: EMV POS MasterCard +D276000025: Girocard +D27600002547410100: Girocard ATM +D7560000010101: Reka Card +D7560000300101: M Budget \ No newline at end of file diff --git a/assets/resources/nfc/emv/country_code.nfc b/assets/resources/nfc/emv/country_code.nfc index e43c35f3..8b19ab83 100644 --- a/assets/resources/nfc/emv/country_code.nfc +++ b/assets/resources/nfc/emv/country_code.nfc @@ -1,249 +1,252 @@ -0004 AFG -0008 ALB -0010 ATA -0012 DZA -0016 ASM -0020 AND -0024 AGO -0028 ATG -0031 AZE -0032 ARG -0036 AUS -0040 AUT -0044 BHS -0048 BHR -0050 BGD -0051 ARM -0052 BRB -0056 BEL -0060 BMU -0064 BTN -0068 BOL -0070 BIH -0072 BWA -0074 BVT -0076 BRA -0084 BLZ -0086 IOT -0090 SLB -0092 VGB -0096 BRN -0100 BGR -0104 MMR -0108 BDI -0112 BLR -0116 KHM -0120 CMR -0124 CAN -0132 CPV -0136 CYM -0140 CAF -0144 LKA -0148 TCD -0152 CHL -0156 CHN -0158 TWN -0162 CXR -0166 CCK -0170 COL -0174 COM -0175 MYT -0178 COG -0180 COD -0184 COK -0188 CRI -0191 HRV -0192 CUB -0196 CYP -0203 CZE -0204 BEN -0208 DNK -0212 DMA -0214 DOM -0218 ECU -0222 SLV -0226 GNQ -0231 ETH -0232 ERI -0233 EST -0234 FRO -0238 FLK -0239 SGS -0242 FJI -0246 FIN -0248 ALA -0250 FRA -0254 GUF -0258 PYF -0260 ATF -0262 DJI -0266 GAB -0268 GEO -0270 GMB -0275 PSE -0276 DEU -0288 GHA -0292 GIB -0296 KIR -0300 GRC -0304 GRL -0308 GRD -0312 GLP -0316 GUM -0320 GTM -0324 GIN -0328 GUY -0332 HTI -0334 HMD -0336 VAT -0340 HND -0344 HKG -0348 HUN -0352 ISL -0356 IND -0360 IDN -0364 IRN -0368 IRQ -0372 IRL -0376 ISR -0380 ITA -0384 CIV -0388 JAM -0392 JPN -0398 KAZ -0400 JOR -0404 KEN -0408 PRK -0410 KOR -0414 KWT -0417 KGZ -0418 LAO -0422 LBN -0426 LSO -0428 LVA -0430 LBR -0434 LBY -0438 LIE -0440 LTU -0442 LUX -0446 MAC -0450 MDG -0454 MWI -0458 MYS -0462 MDV -0466 MLI -0470 MLT -0474 MTQ -0478 MRT -0480 MUS -0484 MEX -0492 MCO -0496 MNG -0498 MDA -0499 MNE -0500 MSR -0504 MAR -0508 MOZ -0512 OMN -0516 NAM -0520 NRU -0524 NPL -0528 NLD -0531 CUW -0533 ABW -0534 SXM -0535 BES -0540 NCL -0548 VUT -0554 NZL -0558 NIC -0562 NER -0566 NGA -0570 NIU -0574 NFK -0578 NOR -0580 MNP -0581 UMI -0583 FSM -0584 MHL -0585 PLW -0586 PAK -0591 PAN -0598 PNG -0600 PRY -0604 PER -0608 PHL -0612 PCN -0616 POL -0620 PRT -0624 GNB -0626 TLS -0630 PRI -0634 QAT -0638 REU -0642 ROU -0643 RUS -0646 RWA -0652 BLM -0654 SHN -0659 KNA -0660 AIA -0662 LCA -0663 MAF -0666 SPM -0670 VCT -0674 SMR -0678 STP -0682 SAU -0686 SEN -0688 SRB -0690 SYC -0694 SLE -0702 SGP -0703 SVK -0704 VNM -0705 SVN -0706 SOM -0710 ZAF -0716 ZWE -0724 ESP -0728 SSD -0729 SDN -0732 ESH -0740 SUR -0744 SJM -0748 SWZ -0752 SWE -0756 CHE -0760 SYR -0762 TJK -0764 THA -0768 TGO -0772 TKL -0776 TON -0780 TTO -0784 ARE -0788 TUN -0792 TUR -0795 TKM -0796 TCA -0798 TUV -0800 UGA -0804 UKR -0807 MKD -0818 EGY -0826 GBR -0831 GGY -0832 JEY -0833 IMN -0834 TZA -0840 USA -0850 VIR -0854 BFA -0858 URY -0860 UZB -0862 VEN -0876 WLF -0882 WSM -0887 YEM -0894 ZMB \ No newline at end of file +Filetype: Flipper EMV resources +Version: 1 +# EMV country code: country name +0004: AFG +0008: ALB +0010: ATA +0012: DZA +0016: ASM +0020: AND +0024: AGO +0028: ATG +0031: AZE +0032: ARG +0036: AUS +0040: AUT +0044: BHS +0048: BHR +0050: BGD +0051: ARM +0052: BRB +0056: BEL +0060: BMU +0064: BTN +0068: BOL +0070: BIH +0072: BWA +0074: BVT +0076: BRA +0084: BLZ +0086: IOT +0090: SLB +0092: VGB +0096: BRN +0100: BGR +0104: MMR +0108: BDI +0112: BLR +0116: KHM +0120: CMR +0124: CAN +0132: CPV +0136: CYM +0140: CAF +0144: LKA +0148: TCD +0152: CHL +0156: CHN +0158: TWN +0162: CXR +0166: CCK +0170: COL +0174: COM +0175: MYT +0178: COG +0180: COD +0184: COK +0188: CRI +0191: HRV +0192: CUB +0196: CYP +0203: CZE +0204: BEN +0208: DNK +0212: DMA +0214: DOM +0218: ECU +0222: SLV +0226: GNQ +0231: ETH +0232: ERI +0233: EST +0234: FRO +0238: FLK +0239: SGS +0242: FJI +0246: FIN +0248: ALA +0250: FRA +0254: GUF +0258: PYF +0260: ATF +0262: DJI +0266: GAB +0268: GEO +0270: GMB +0275: PSE +0276: DEU +0288: GHA +0292: GIB +0296: KIR +0300: GRC +0304: GRL +0308: GRD +0312: GLP +0316: GUM +0320: GTM +0324: GIN +0328: GUY +0332: HTI +0334: HMD +0336: VAT +0340: HND +0344: HKG +0348: HUN +0352: ISL +0356: IND +0360: IDN +0364: IRN +0368: IRQ +0372: IRL +0376: ISR +0380: ITA +0384: CIV +0388: JAM +0392: JPN +0398: KAZ +0400: JOR +0404: KEN +0408: PRK +0410: KOR +0414: KWT +0417: KGZ +0418: LAO +0422: LBN +0426: LSO +0428: LVA +0430: LBR +0434: LBY +0438: LIE +0440: LTU +0442: LUX +0446: MAC +0450: MDG +0454: MWI +0458: MYS +0462: MDV +0466: MLI +0470: MLT +0474: MTQ +0478: MRT +0480: MUS +0484: MEX +0492: MCO +0496: MNG +0498: MDA +0499: MNE +0500: MSR +0504: MAR +0508: MOZ +0512: OMN +0516: NAM +0520: NRU +0524: NPL +0528: NLD +0531: CUW +0533: ABW +0534: SXM +0535: BES +0540: NCL +0548: VUT +0554: NZL +0558: NIC +0562: NER +0566: NGA +0570: NIU +0574: NFK +0578: NOR +0580: MNP +0581: UMI +0583: FSM +0584: MHL +0585: PLW +0586: PAK +0591: PAN +0598: PNG +0600: PRY +0604: PER +0608: PHL +0612: PCN +0616: POL +0620: PRT +0624: GNB +0626: TLS +0630: PRI +0634: QAT +0638: REU +0642: ROU +0643: RUS +0646: RWA +0652: BLM +0654: SHN +0659: KNA +0660: AIA +0662: LCA +0663: MAF +0666: SPM +0670: VCT +0674: SMR +0678: STP +0682: SAU +0686: SEN +0688: SRB +0690: SYC +0694: SLE +0702: SGP +0703: SVK +0704: VNM +0705: SVN +0706: SOM +0710: ZAF +0716: ZWE +0724: ESP +0728: SSD +0729: SDN +0732: ESH +0740: SUR +0744: SJM +0748: SWZ +0752: SWE +0756: CHE +0760: SYR +0762: TJK +0764: THA +0768: TGO +0772: TKL +0776: TON +0780: TTO +0784: ARE +0788: TUN +0792: TUR +0795: TKM +0796: TCA +0798: TUV +0800: UGA +0804: UKR +0807: MKD +0818: EGY +0826: GBR +0831: GGY +0832: JEY +0833: IMN +0834: TZA +0840: USA +0850: VIR +0854: BFA +0858: URY +0860: UZB +0862: VEN +0876: WLF +0882: WSM +0887: YEM +0894: ZMB \ No newline at end of file diff --git a/assets/resources/nfc/emv/currency_code.nfc b/assets/resources/nfc/emv/currency_code.nfc index 4ee69181..31c575b3 100644 --- a/assets/resources/nfc/emv/currency_code.nfc +++ b/assets/resources/nfc/emv/currency_code.nfc @@ -1,168 +1,171 @@ -0997 USN -0994 XSU -0990 CLF -0986 BRL -0985 PLN -0984 BOV -0981 GEL -0980 UAH -0979 MXV -0978 EUR -0977 BAM -0976 CDF -0975 BGN -0973 AOA -0972 TJS -0971 AFN -0970 COU -0969 MGA -0968 SRD -0967 ZMW -0965 XUA -0960 XDR -0953 XPF -0952 XOF -0951 XCD -0950 XAF -0949 TRY -0948 CHW -0947 CHE -0946 RON -0944 AZN -0943 MZN -0941 RSD -0940 UYI -0938 SDG -0937 VEF -0936 GHS -0934 TMT -0933 BYN -0932 ZWL -0931 CUC -0930 STN -0929 MRU -0901 TWD -0886 YER -0882 WST -0860 UZS -0858 UYU -0840 USD -0834 TZS -0826 GBP -0818 EGP -0807 MKD -0800 UGX -0788 TND -0784 AED -0780 TTD -0776 TOP -0764 THB -0760 SYP -0756 CHF -0752 SEK -0748 SZL -0728 SSP -0710 ZAR -0706 SOS -0704 VND -0702 SGD -0694 SLL -0690 SCR -0682 SAR -0654 SHP -0646 RWF -0643 RUB -0634 QAR -0608 PHP -0604 PEN -0600 PYG -0598 PGK -0590 PAB -0586 PKR -0578 NOK -0566 NGN -0558 NIO -0554 NZD -0548 VUV -0533 AWG -0532 ANG -0524 NPR -0516 NAD -0512 OMR -0504 MAD -0498 MDL -0496 MNT -0484 MXN -0480 MUR -0462 MVR -0458 MYR -0454 MWK -0446 MOP -0434 LYD -0430 LRD -0426 LSL -0422 LBP -0418 LAK -0417 KGS -0414 KWD -0410 KRW -0408 KPW -0404 KES -0400 JOD -0398 KZT -0392 JPY -0388 JMD -0376 ILS -0368 IQD -0364 IRR -0360 IDR -0356 INR -0352 ISK -0348 HUF -0344 HKD -0340 HNL -0332 HTG -0328 GYD -0324 GNF -0320 GTQ -0292 GIP -0270 GMD -0262 DJF -0242 FJD -0238 FKP -0232 ERN -0230 ETB -0222 SVC -0214 DOP -0208 DKK -0203 CZK -0192 CUP -0191 HRK -0188 CRC -0174 KMF -0170 COP -0156 CNY -0152 CLP -0144 LKR -0136 KYD -0132 CVE -0124 CAD -0116 KHR -0108 BIF -0104 MMK -0096 BND -0090 SBD -0084 BZD -0072 BWP -0068 BOB -0064 BTN -0060 BMD -0052 BBD -0051 AMD -0050 BDT -0048 BHD -0044 BSD -0036 AUD -0032 ARS -0012 DZD -0008 ALL \ No newline at end of file +Filetype: Flipper EMV resources +Version: 1 +# EMV currency code: currency name +0997: USN +0994: XSU +0990: CLF +0986: BRL +0985: PLN +0984: BOV +0981: GEL +0980: UAH +0979: MXV +0978: EUR +0977: BAM +0976: CDF +0975: BGN +0973: AOA +0972: TJS +0971: AFN +0970: COU +0969: MGA +0968: SRD +0967: ZMW +0965: XUA +0960: XDR +0953: XPF +0952: XOF +0951: XCD +0950: XAF +0949: TRY +0948: CHW +0947: CHE +0946: RON +0944: AZN +0943: MZN +0941: RSD +0940: UYI +0938: SDG +0937: VEF +0936: GHS +0934: TMT +0933: BYN +0932: ZWL +0931: CUC +0930: STN +0929: MRU +0901: TWD +0886: YER +0882: WST +0860: UZS +0858: UYU +0840: USD +0834: TZS +0826: GBP +0818: EGP +0807: MKD +0800: UGX +0788: TND +0784: AED +0780: TTD +0776: TOP +0764: THB +0760: SYP +0756: CHF +0752: SEK +0748: SZL +0728: SSP +0710: ZAR +0706: SOS +0704: VND +0702: SGD +0694: SLL +0690: SCR +0682: SAR +0654: SHP +0646: RWF +0643: RUB +0634: QAR +0608: PHP +0604: PEN +0600: PYG +0598: PGK +0590: PAB +0586: PKR +0578: NOK +0566: NGN +0558: NIO +0554: NZD +0548: VUV +0533: AWG +0532: ANG +0524: NPR +0516: NAD +0512: OMR +0504: MAD +0498: MDL +0496: MNT +0484: MXN +0480: MUR +0462: MVR +0458: MYR +0454: MWK +0446: MOP +0434: LYD +0430: LRD +0426: LSL +0422: LBP +0418: LAK +0417: KGS +0414: KWD +0410: KRW +0408: KPW +0404: KES +0400: JOD +0398: KZT +0392: JPY +0388: JMD +0376: ILS +0368: IQD +0364: IRR +0360: IDR +0356: INR +0352: ISK +0348: HUF +0344: HKD +0340: HNL +0332: HTG +0328: GYD +0324: GNF +0320: GTQ +0292: GIP +0270: GMD +0262: DJF +0242: FJD +0238: FKP +0232: ERN +0230: ETB +0222: SVC +0214: DOP +0208: DKK +0203: CZK +0192: CUP +0191: HRK +0188: CRC +0174: KMF +0170: COP +0156: CNY +0152: CLP +0144: LKR +0136: KYD +0132: CVE +0124: CAD +0116: KHR +0108: BIF +0104: MMK +0096: BND +0090: SBD +0084: BZD +0072: BWP +0068: BOB +0064: BTN +0060: BMD +0052: BBD +0051: AMD +0050: BDT +0048: BHD +0044: BSD +0036: AUD +0032: ARS +0012: DZD +0008: ALL \ No newline at end of file diff --git a/assets/resources/subghz/came_atomo b/assets/resources/subghz/came_atomo new file mode 100644 index 00000000..0952f3f2 --- /dev/null +++ b/assets/resources/subghz/came_atomo @@ -0,0 +1,6 @@ +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 +E0BDF15D68F29AE787E7FCEE6C3611C90A92305D677B8FFFBE225196F5DC04CAEAE1102B4AB830E76C9C14DBA7FA5BD1F30864ABAF51387222FDCC0BA87E4FF709812D5C59DD953859AFD698A0EA2ECEFA0DC49652861EF4CF1864843F135DB8680E0C5C9EEC3BC548E2EB696DC8CA1B0F627347D2C476B410CF03A0F4D1EFB36DFB0574FE8F48FB61910CA539EC04583CA170A51822225A1D3E86C07219FE92E5DE9F557A45E611D74028BAF56E7F2C53DBA8DC53DA89B642FAC0A9A0BAC1756BDA0833ACB09F56E0FA3080CBE9E6A04B235F3AC82BB955FBFD779C59725E6D1875E04E0E84ABD0C3C1C8FAB247EC2755ACEC9961244E0D79AE710E4C7D33E9 \ No newline at end of file diff --git a/assets/resources/subghz/keeloq_mfcodes b/assets/resources/subghz/keeloq_mfcodes new file mode 100644 index 00000000..9f5ff755 --- /dev/null +++ b/assets/resources/subghz/keeloq_mfcodes @@ -0,0 +1,27 @@ +Filetype: Flipper SubGhz Keystore File +Version: 0 +Encryption: 1 +IV: 7A 44 FE 5D C3 B3 65 13 73 A6 F4 2D 1E B6 7D F0 +89153B35033574AAA06D7E792CB92A486B37A2CCDF0B0152BF1A563E321518C8 +F6583A3E4148439E8A8D7ED6A095ACC0C3E22A48F1637E78DF023CAC9272934E +AA0439E6B76CD43F3FCC27CF69C5F3B6508E8103B164E4ECDDF8B2FB222D46FF +A9826C663033D25AE21CB8790406997ADCE84360B258F2B989D967009659859C +3474E7BBFA0173928F414CFD5EE19B27A558D9C171D96FE7B7840A01323A7E7446FAE3E98EA9A8C69B4A6B781BD7906A +2873939A8E0EAC16D748967E987BB0F1079C106E4235B7D35B4BF37F54B21F8E +EF6F1DC0201FCB8CEBC5642A5194A1FDCFBE1FA772A79CEAD54D2F0DA3AC4F6C +3F595EAA0E81E96C5D6DB41799D314E3E81E7F4197E19A3341C55592B1B6C4B0 +7B2D75FE11B27E99CA7610E47D712C8CFB619EC69EBC976A70CFD9574C9F4FF8 +39735CF1D009D132A33B9C546D95FA6D3E69BF3A57EF219392E57C9560E7B037 +D56FDDFB0C4E808143D3ED5F15D6FF47F6EDEBD01192FC7ACF3ACCE9FD5162FC297D0089D65ED2CBE3CE05DDA7B96446 +2750D4F0650061C3AF72C88FD080BE241F2BDD8D8C1B0EFE781120EBEFFE2C72D0EECC42CDDED50CFE4AC51C48AE68C6 +F8CE64921CB73015F2672A9EF0A8359269CAE0E515D6DBB3130CFC9E5E1A98AD +ACF6ADB9E02D67B44EB6C6F126BF64BDAB37926B8BE39E27F323E8F5A0F8FC38 +FBB1302D697F94ECED681CE047819001EDE6E013850258F61E97091DD37D24F2 +D8CD53AB5A94898EB53D4FF46546ADBAA24691181A396052A58AAC657D6817AB +43200E08C21747CABC59538888A259238E782545732A1A6EEE00A6929EC9DD97A8BA9812372374046AC66652CC561D60 +C38CBE29F22D0E83E58A52E94AA2485DA8E702FBDB89D27249473CB8A19AEF61 +9F0EB580F7474985E8460E1682451E213778B77A9CAB4734B75C5386851050BF +2364EBB8237363B21226565675B9F478482CADAE41E795C27287E26137797C10 +775C9A28BA50D759FB438D0200121F01F7DB11986D44D3960F745EAA1E7A2CE2AD92AD718AFCD98BC3269C39F65ADC53 +6911E7EAFFAC15B4E3ABDAD271E92EAEFE1F2E288789EC7599AAA32986273306 +5387D67534234AFD8BAB90DC74BA39598B938526CBFAF14F75AA36A29C13836A31897A86D2E1178AE66191E771A7FEA4 diff --git a/assets/resources/subghz/keeloq_mfcodes_user b/assets/resources/subghz/keeloq_mfcodes_user new file mode 100644 index 00000000..6361d85e --- /dev/null +++ b/assets/resources/subghz/keeloq_mfcodes_user @@ -0,0 +1,11 @@ +# for adding manufacture keys +# AABBCCDDEEFFAABB:X:NAME\r\n +# AABBCCDDEEFFAABB - man 64 bit +# X - encryption method 1 - Simple Learning, 2 - Normal_Learning, +# 0 - iterates over both previous and man in direct and reverse byte sequence +# NAME - name (string without spaces) max 64 characters long +Filetype: Flipper SubGhz Keystore File +Version: 0 +Encryption: 0 +AABBCCDDEEFFAABB:1:Test1 +AABBCCDDEEFFAABB:1:Test2 diff --git a/assets/resources/subghz/nice_flor_s_rx b/assets/resources/subghz/nice_flor_s_rx new file mode 100644 index 00000000..e63e47b0 --- /dev/null +++ b/assets/resources/subghz/nice_flor_s_rx @@ -0,0 +1,6 @@ +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/nice_flor_s_tx b/assets/resources/subghz/nice_flor_s_tx new file mode 100644 index 00000000..a98dcb07 --- /dev/null +++ b/assets/resources/subghz/nice_flor_s_tx @@ -0,0 +1,6 @@ +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/bootloader/targets/f6/furi-hal/furi-hal-version.c b/bootloader/targets/f6/furi-hal/furi-hal-version.c index ff3b2699..df6d61b0 100644 --- a/bootloader/targets/f6/furi-hal/furi-hal-version.c +++ b/bootloader/targets/f6/furi-hal/furi-hal-version.c @@ -272,7 +272,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) { return version_get(); } -const struct Version* furi_hal_version_get_boot_version(void) { +const struct Version* furi_hal_version_get_bootloader_version(void) { #ifdef NO_BOOTLOADER return 0; #else diff --git a/bootloader/targets/f7/furi-hal/furi-hal-version.c b/bootloader/targets/f7/furi-hal/furi-hal-version.c index ff3b2699..df6d61b0 100644 --- a/bootloader/targets/f7/furi-hal/furi-hal-version.c +++ b/bootloader/targets/f7/furi-hal/furi-hal-version.c @@ -272,7 +272,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) { return version_get(); } -const struct Version* furi_hal_version_get_boot_version(void) { +const struct Version* furi_hal_version_get_bootloader_version(void) { #ifdef NO_BOOTLOADER return 0; #else diff --git a/bootloader/targets/furi-hal-include/furi-hal-version.h b/bootloader/targets/furi-hal-include/furi-hal-version.h index 99a00533..3ed8b60b 100644 --- a/bootloader/targets/furi-hal-include/furi-hal-version.h +++ b/bootloader/targets/furi-hal-include/furi-hal-version.h @@ -148,7 +148,7 @@ const uint8_t* furi_hal_version_get_ble_mac(); * * @return Address of boot version structure. */ -const struct Version* furi_hal_version_get_boot_version(); +const struct Version* furi_hal_version_get_bootloader_version(); /** Get address of version structure of firmware. * diff --git a/core/flipper.c b/core/flipper.c index 785a4463..59c1823a 100755 --- a/core/flipper.c +++ b/core/flipper.c @@ -3,10 +3,12 @@ #include #include +#define TAG "Flipper" + static void flipper_print_version(const char* target, const Version* version) { if(version) { FURI_LOG_I( - "FLIPPER", + TAG, "\r\n\t%s version:\t%s\r\n" "\tBuild date:\t\t%s\r\n" "\tGit Commit:\t\t%s (%s)\r\n" @@ -18,23 +20,23 @@ static void flipper_print_version(const char* target, const Version* version) { version_get_gitbranchnum(version), version_get_gitbranch(version)); } else { - FURI_LOG_I("FLIPPER", "No build info for %s", target); + FURI_LOG_I(TAG, "No build info for %s", target); } } void flipper_init() { const Version* version; - version = (const Version*)furi_hal_version_get_boot_version(); + version = (const Version*)furi_hal_version_get_bootloader_version(); flipper_print_version("Bootloader", version); version = (const Version*)furi_hal_version_get_firmware_version(); flipper_print_version("Firmware", version); - FURI_LOG_I("FLIPPER", "starting services"); + FURI_LOG_I(TAG, "starting services"); for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { - FURI_LOG_I("FLIPPER", "starting service %s", FLIPPER_SERVICES[i].name); + FURI_LOG_I(TAG, "starting service %s", FLIPPER_SERVICES[i].name); FuriThread* thread = furi_thread_alloc(); @@ -45,5 +47,5 @@ void flipper_init() { furi_thread_start(thread); } - FURI_LOG_I("FLIPPER", "services startup complete"); + FURI_LOG_I(TAG, "services startup complete"); } diff --git a/core/furi.h b/core/furi.h index 681855d0..80aea70c 100644 --- a/core/furi.h +++ b/core/furi.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include #include diff --git a/core/furi/common_defines.h b/core/furi/common_defines.h old mode 100644 new mode 100755 index bb4aadfa..00a68529 --- a/core/furi/common_defines.h +++ b/core/furi/common_defines.h @@ -64,4 +64,10 @@ #ifndef TOSTRING #define TOSTRING(x) STRINGIFY(x) +#endif + +#ifndef REVERSE_BYTES_U32 +#define REVERSE_BYTES_U32(x) \ + ((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \ + (((x)&0xFF000000) >> 24)) #endif \ No newline at end of file diff --git a/core/furi/memmgr_heap.c b/core/furi/memmgr_heap.c index 8e57914e..3fe7822c 100644 --- a/core/furi/memmgr_heap.c +++ b/core/furi/memmgr_heap.c @@ -387,6 +387,7 @@ void vPortFree(void* pv) { /* Add this block to the list of free blocks. */ xFreeBytesRemaining += pxLink->xBlockSize; traceFREE(pv, pxLink->xBlockSize); + memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); } (void)xTaskResumeAll(); diff --git a/core/furi/pubsub.c b/core/furi/pubsub.c index 0722d2a0..3fbcb51c 100644 --- a/core/furi/pubsub.c +++ b/core/furi/pubsub.c @@ -1,88 +1,95 @@ #include "pubsub.h" -#include +#include "memmgr.h" +#include "check.h" + +#include +#include + +struct FuriPubSubSubscription { + FuriPubSubCallback callback; + void* callback_context; +}; + +LIST_DEF(FuriPubSubSubscriptionList, FuriPubSubSubscription, M_POD_OPLIST); + +struct FuriPubSub { + FuriPubSubSubscriptionList_t items; + osMutexId_t mutex; +}; + +FuriPubSub* furi_pubsub_alloc() { + FuriPubSub* pubsub = furi_alloc(sizeof(FuriPubSub)); -bool init_pubsub(PubSub* pubsub) { - // mutex without name, - // no attributes (unfortunatly robust mutex is not supported by FreeRTOS), - // with dynamic memory allocation pubsub->mutex = osMutexNew(NULL); - if(pubsub->mutex == NULL) return false; + furi_assert(pubsub->mutex); - // construct list - list_pubsub_cb_init(pubsub->items); + FuriPubSubSubscriptionList_init(pubsub->items); - return true; + return pubsub; } -bool delete_pubsub(PubSub* pubsub) { - if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) { - bool result = osMutexDelete(pubsub->mutex) == osOK; - list_pubsub_cb_clear(pubsub->items); - return result; - } else { - return false; - } +void furi_pubsub_free(FuriPubSub* pubsub) { + furi_assert(pubsub); + + furi_check(FuriPubSubSubscriptionList_size(pubsub->items) == 0); + + FuriPubSubSubscriptionList_clear(pubsub->items); + + furi_check(osMutexDelete(pubsub->mutex) == osOK); + + free(pubsub); } -PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) { - if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) { - // put uninitialized item to the list - PubSubItem* item = list_pubsub_cb_push_raw(pubsub->items); +FuriPubSubSubscription* + furi_pubsub_subscribe(FuriPubSub* pubsub, FuriPubSubCallback callback, void* callback_context) { + furi_check(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK); + // put uninitialized item to the list + FuriPubSubSubscription* item = FuriPubSubSubscriptionList_push_raw(pubsub->items); - // initialize item - item->cb = cb; - item->ctx = ctx; - item->self = pubsub; + // initialize item + item->callback = callback; + item->callback_context = callback_context; - // TODO unsubscribe pubsub on app exit - //flapp_on_exit(unsubscribe_pubsub, item); + furi_check(osMutexRelease(pubsub->mutex) == osOK); - osMutexRelease(pubsub->mutex); - - return item; - } else { - return NULL; - } + return item; } -bool unsubscribe_pubsub(PubSubItem* pubsub_id) { - if(osMutexAcquire(pubsub_id->self->mutex, osWaitForever) == osOK) { - bool result = false; +void furi_pubsub_unsubscribe(FuriPubSub* pubsub, FuriPubSubSubscription* pubsub_subscription) { + furi_assert(pubsub); + furi_assert(pubsub_subscription); - // iterate over items - list_pubsub_cb_it_t it; - for(list_pubsub_cb_it(it, pubsub_id->self->items); !list_pubsub_cb_end_p(it); - list_pubsub_cb_next(it)) { - const PubSubItem* item = list_pubsub_cb_cref(it); + furi_check(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK); + bool result = false; - // if the iterator is equal to our element - if(item == pubsub_id) { - list_pubsub_cb_remove(pubsub_id->self->items, it); - result = true; - break; - } + // iterate over items + FuriPubSubSubscriptionList_it_t it; + for(FuriPubSubSubscriptionList_it(it, pubsub->items); !FuriPubSubSubscriptionList_end_p(it); + FuriPubSubSubscriptionList_next(it)) { + const FuriPubSubSubscription* item = FuriPubSubSubscriptionList_cref(it); + + // if the iterator is equal to our element + if(item == pubsub_subscription) { + FuriPubSubSubscriptionList_remove(pubsub->items, it); + result = true; + break; } - - osMutexRelease(pubsub_id->self->mutex); - return result; - } else { - return false; } + + furi_check(osMutexRelease(pubsub->mutex) == osOK); + furi_check(result); } -bool notify_pubsub(PubSub* pubsub, void* arg) { - if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) { - // iterate over subscribers - list_pubsub_cb_it_t it; - for(list_pubsub_cb_it(it, pubsub->items); !list_pubsub_cb_end_p(it); - list_pubsub_cb_next(it)) { - const PubSubItem* item = list_pubsub_cb_cref(it); - item->cb(arg, item->ctx); - } +void furi_pubsub_publish(FuriPubSub* pubsub, void* message) { + furi_check(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK); - osMutexRelease(pubsub->mutex); - return true; - } else { - return false; + // iterate over subscribers + FuriPubSubSubscriptionList_it_t it; + for(FuriPubSubSubscriptionList_it(it, pubsub->items); !FuriPubSubSubscriptionList_end_p(it); + FuriPubSubSubscriptionList_next(it)) { + const FuriPubSubSubscription* item = FuriPubSubSubscriptionList_cref(it); + item->callback(message, item->callback_context); } + + furi_check(osMutexRelease(pubsub->mutex) == osOK); } diff --git a/core/furi/pubsub.h b/core/furi/pubsub.h index 2f440e1c..446d423f 100644 --- a/core/furi/pubsub.h +++ b/core/furi/pubsub.h @@ -1,95 +1,64 @@ #pragma once -#include "cmsis_os.h" -#include "m-list.h" - #ifdef __cplusplus extern "C" { #endif -/** -== PubSub == +/** FuriPubSub Callback type */ +typedef void (*FuriPubSubCallback)(const void* message, void* context); - * PubSub allows users to subscribe on notifies and notify subscribers. - * Notifier side can pass `void*` arg to subscriber callback, - * and also subscriber can set `void*` context pointer that pass into - * callback (you can see callback signature below). +/** FuriPubSub type */ +typedef struct FuriPubSub FuriPubSub; + +/** FuriPubSubSubscription type */ +typedef struct FuriPubSubSubscription FuriPubSubSubscription; + +/** Allocate FuriPubSub + * + * Reentrable, Not threadsafe, one owner + * + * @return pointer to FuriPubSub instance */ +FuriPubSub* furi_pubsub_alloc(); -typedef void (*PubSubCallback)(const void*, void*); -typedef struct PubSubType PubSub; - -typedef struct { - PubSubCallback cb; - void* ctx; - PubSub* self; -} PubSubItem; - -LIST_DEF(list_pubsub_cb, PubSubItem, M_POD_OPLIST); - -struct PubSubType { - list_pubsub_cb_t items; - osMutexId_t mutex; -}; - -/** - * To create PubSub you should create PubSub instance and call `init_pubsub`. +/** Free FuriPubSub + * + * @param pubsub FuriPubSub instance */ -bool init_pubsub(PubSub* pubsub); +void furi_pubsub_free(FuriPubSub* pubsub); -/** - * Since we use dynamic memory - we must explicity delete pubsub +/** Subscribe to FuriPubSub + * + * Threadsafe, Reentrable + * + * @param pubsub pointer to FuriPubSub instance + * @param[in] callback The callback + * @param callback_context The callback context + * + * @return pointer to FuriPubSubSubscription instance */ -bool delete_pubsub(PubSub* pubsub); +FuriPubSubSubscription* + furi_pubsub_subscribe(FuriPubSub* pubsub, FuriPubSubCallback callback, void* callback_context); -/** - * Use `subscribe_pubsub` to register your callback. +/** Unsubscribe from FuriPubSub + * + * No use of `pubsub_subscription` allowed after call of this method + * Threadsafe, Reentrable. + * + * @param pubsub pointer to FuriPubSub instance + * @param pubsub_subscription pointer to FuriPubSubSubscription instance */ -PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx); +void furi_pubsub_unsubscribe(FuriPubSub* pubsub, FuriPubSubSubscription* pubsub_subscription); -/** - * Use `unsubscribe_pubsub` to unregister callback. +/** Publish message to FuriPubSub + * + * Threadsafe, Reentrable. + * + * @param pubsub pointer to FuriPubSub instance + * @param message message pointer to publish */ -bool unsubscribe_pubsub(PubSubItem* pubsub_id); - -/** - * Use `notify_pubsub` to notify subscribers. - */ -bool notify_pubsub(PubSub* pubsub, void* arg); +void furi_pubsub_publish(FuriPubSub* pubsub, void* message); #ifdef __cplusplus } #endif - -/* - -```C -// MANIFEST -// name="test" -// stack=128 - -void example_pubsub_handler(void* arg, void* ctx) { - printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx); -} - -void pubsub_test() { - const char* app_name = "test app"; - - PubSub example_pubsub; - init_pubsub(&example_pubsub); - - if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) { - printf("critical error\n"); - flapp_exit(NULL); - } - - uint32_t counter = 0; - while(1) { - notify_pubsub(&example_pubsub, (void*)&counter); - counter++; - - osDelay(100); - } -} -``` -*/ diff --git a/core/furi/record.c b/core/furi/record.c index 7bc736df..29e33caf 100644 --- a/core/furi/record.c +++ b/core/furi/record.c @@ -84,6 +84,7 @@ bool furi_record_destroy(const char* name) { FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); furi_assert(record_data); if(record_data->holders_count == 0) { + furi_check(osOK == osEventFlagsDelete(record_data->flags)); FuriRecordDataDict_erase(furi_record->records, name_str); ret = true; } diff --git a/core/furi/stdglue.c b/core/furi/stdglue.c index 4755c7ab..bd4b11c9 100644 --- a/core/furi/stdglue.c +++ b/core/furi/stdglue.c @@ -2,6 +2,9 @@ #include "check.h" #include "memmgr.h" +#include +#include + #include #include diff --git a/debug/PyCortexMDebug/cmdebug/svd_gdb.py b/debug/PyCortexMDebug/cmdebug/svd_gdb.py index 3acd014f..2629a3ce 100755 --- a/debug/PyCortexMDebug/cmdebug/svd_gdb.py +++ b/debug/PyCortexMDebug/cmdebug/svd_gdb.py @@ -22,6 +22,7 @@ import math import sys import struct import pkg_resources +import fnmatch from .svd import SVDFile @@ -101,13 +102,6 @@ class LoadSVD(gdb.Command): raise gdb.GdbError("Could not load SVD file {} : {}...\n".format(f, e)) -if __name__ == "__main__": - # This will also get executed by GDB - - # Create just the svd_load command - LoadSVD() - - class SVD(gdb.Command): """The CMSIS SVD (System View Description) inspector command @@ -321,13 +315,19 @@ class SVD(gdb.Command): container = peripheral.name + " > " + register.name self._print_register_fields(container, form, register) - else: - gdb.write( - "Register/cluster {} in peripheral {} does not exist!\n".format( - s[1], peripheral.name + found = False + for key in fnmatch.filter(peripheral.registers.keys(), s[1]): + register = peripheral.registers[key] + container = peripheral.name + " > " + register.name + self._print_register_fields(container, form, register) + found = True + if not found: + gdb.write( + "Register/cluster {} in peripheral {} does not exist!\n".format( + s[1], peripheral.name + ) ) - ) return if len(s) == 3: diff --git a/debug/STM32WB55_CM4.svd b/debug/STM32WB55_CM4.svd index 380e4c3d..9011748d 100755 --- a/debug/STM32WB55_CM4.svd +++ b/debug/STM32WB55_CM4.svd @@ -1,37004 +1,15 @@ - - - - STM32WB55_CM4 - 1.8 - STM32WB55_CM4 - - CM4 - r0p1 - little - true - true - 4 - false - - - - 8 - - 32 - - 0x20 - 0x0 - 0xFFFFFFFF - - - DMA1 - Direct memory access controller - DMA - 0x40020000 - - 0x0 - 0x400 - registers - - - DMA1_Channel1 - DMA1 Channel1 global interrupt - 11 - - - DMA1_Channel2 - DMA1 Channel2 global interrupt - 12 - - - DMA1_Channel3 - DMA1 Channel3 interrupt - 13 - - - DMA1_Channel4 - DMA1 Channel4 interrupt - 14 - - - DMA1_Channel5 - DMA1 Channel5 interrupt - 15 - - - DMA1_Channel6 - DMA1 Channel6 interrupt - 16 - - - DMA1_Channel7 - DMA1 Channel 7 interrupt - 17 - - - - ISR - ISR - interrupt status register - 0x0 - 0x20 - read-only - 0x00000000 - - - TEIF7 - Channel x transfer error flag (x = 1 ..7) - 27 - 1 - - - HTIF7 - Channel x half transfer flag (x = 1 ..7) - 26 - 1 - - - TCIF7 - Channel x transfer complete flag (x = 1 ..7) - 25 - 1 - - - GIF7 - Channel x global interrupt flag (x = 1 ..7) - 24 - 1 - - - TEIF6 - Channel x transfer error flag (x = 1 ..7) - 23 - 1 - - - HTIF6 - Channel x half transfer flag (x = 1 ..7) - 22 - 1 - - - TCIF6 - Channel x transfer complete flag (x = 1 ..7) - 21 - 1 - - - GIF6 - Channel x global interrupt flag (x = 1 ..7) - 20 - 1 - - - TEIF5 - Channel x transfer error flag (x = 1 ..7) - 19 - 1 - - - HTIF5 - Channel x half transfer flag (x = 1 ..7) - 18 - 1 - - - TCIF5 - Channel x transfer complete flag (x = 1 ..7) - 17 - 1 - - - GIF5 - Channel x global interrupt flag (x = 1 ..7) - 16 - 1 - - - TEIF4 - Channel x transfer error flag (x = 1 ..7) - 15 - 1 - - - HTIF4 - Channel x half transfer flag (x = 1 ..7) - 14 - 1 - - - TCIF4 - Channel x transfer complete flag (x = 1 ..7) - 13 - 1 - - - GIF4 - Channel x global interrupt flag (x = 1 ..7) - 12 - 1 - - - TEIF3 - Channel x transfer error flag (x = 1 ..7) - 11 - 1 - - - HTIF3 - Channel x half transfer flag (x = 1 ..7) - 10 - 1 - - - TCIF3 - Channel x transfer complete flag (x = 1 ..7) - 9 - 1 - - - GIF3 - Channel x global interrupt flag (x = 1 ..7) - 8 - 1 - - - TEIF2 - Channel x transfer error flag (x = 1 ..7) - 7 - 1 - - - HTIF2 - Channel x half transfer flag (x = 1 ..7) - 6 - 1 - - - TCIF2 - Channel x transfer complete flag (x = 1 ..7) - 5 - 1 - - - GIF2 - Channel x global interrupt flag (x = 1 ..7) - 4 - 1 - - - TEIF1 - Channel x transfer error flag (x = 1 ..7) - 3 - 1 - - - HTIF1 - Channel x half transfer flag (x = 1 ..7) - 2 - 1 - - - TCIF1 - Channel x transfer complete flag (x = 1 ..7) - 1 - 1 - - - GIF1 - Channel x global interrupt flag (x = 1 ..7) - 0 - 1 - - - - - IFCR - IFCR - interrupt flag clear register - 0x4 - 0x20 - write-only - 0x00000000 - - - CTEIF7 - Channel x transfer error clear (x = 1 ..7) - 27 - 1 - - - CHTIF7 - Channel x half transfer clear (x = 1 ..7) - 26 - 1 - - - CTCIF7 - Channel x transfer complete clear (x = 1 ..7) - 25 - 1 - - - CGIF7 - Channel x global interrupt clear (x = 1 ..7) - 24 - 1 - - - CTEIF6 - Channel x transfer error clear (x = 1 ..7) - 23 - 1 - - - CHTIF6 - Channel x half transfer clear (x = 1 ..7) - 22 - 1 - - - CTCIF6 - Channel x transfer complete clear (x = 1 ..7) - 21 - 1 - - - CGIF6 - Channel x global interrupt clear (x = 1 ..7) - 20 - 1 - - - CTEIF5 - Channel x transfer error clear (x = 1 ..7) - 19 - 1 - - - CHTIF5 - Channel x half transfer clear (x = 1 ..7) - 18 - 1 - - - CTCIF5 - Channel x transfer complete clear (x = 1 ..7) - 17 - 1 - - - CGIF5 - Channel x global interrupt clear (x = 1 ..7) - 16 - 1 - - - CTEIF4 - Channel x transfer error clear (x = 1 ..7) - 15 - 1 - - - CHTIF4 - Channel x half transfer clear (x = 1 ..7) - 14 - 1 - - - CTCIF4 - Channel x transfer complete clear (x = 1 ..7) - 13 - 1 - - - CGIF4 - Channel x global interrupt clear (x = 1 ..7) - 12 - 1 - - - CTEIF3 - Channel x transfer error clear (x = 1 ..7) - 11 - 1 - - - CHTIF3 - Channel x half transfer clear (x = 1 ..7) - 10 - 1 - - - CTCIF3 - Channel x transfer complete clear (x = 1 ..7) - 9 - 1 - - - CGIF3 - Channel x global interrupt clear (x = 1 ..7) - 8 - 1 - - - CTEIF2 - Channel x transfer error clear (x = 1 ..7) - 7 - 1 - - - CHTIF2 - Channel x half transfer clear (x = 1 ..7) - 6 - 1 - - - CTCIF2 - Channel x transfer complete clear (x = 1 ..7) - 5 - 1 - - - CGIF2 - Channel x global interrupt clear (x = 1 ..7) - 4 - 1 - - - CTEIF1 - Channel x transfer error clear (x = 1 ..7) - 3 - 1 - - - CHTIF1 - Channel x half transfer clear (x = 1 ..7) - 2 - 1 - - - CTCIF1 - Channel x transfer complete clear (x = 1 ..7) - 1 - 1 - - - CGIF1 - Channel x global interrupt clear (x = 1 ..7) - 0 - 1 - - - - - CCR1 - CCR1 - channel x configuration register - 0x8 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR1 - CNDTR1 - channel x number of data register - 0xC - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR1 - CPAR1 - channel x peripheral address register - 0x10 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR1 - CMAR1 - channel x memory address register - 0x14 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR2 - CCR2 - channel x configuration register - 0x1C - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR2 - CNDTR2 - channel x number of data register - 0x20 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR2 - CPAR2 - channel x peripheral address register - 0x24 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR2 - CMAR2 - channel x memory address register - 0x28 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR3 - CCR3 - channel x configuration register - 0x30 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR3 - CNDTR3 - channel x number of data register - 0x34 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR3 - CPAR3 - channel x peripheral address register - 0x38 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR3 - CMAR3 - channel x memory address register - 0x3C - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR4 - CCR4 - channel x configuration register - 0x44 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR4 - CNDTR4 - channel x number of data register - 0x48 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR4 - CPAR4 - channel x peripheral address register - 0x4C - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR4 - CMAR4 - channel x memory address register - 0x50 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR5 - CCR5 - channel x configuration register - 0x58 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR5 - CNDTR5 - channel x number of data register - 0x5C - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR5 - CPAR5 - channel x peripheral address register - 0x60 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR5 - CMAR5 - channel x memory address register - 0x64 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR6 - CCR6 - channel x configuration register - 0x6C - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR6 - CNDTR6 - channel x number of data register - 0x70 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR6 - CPAR6 - channel x peripheral address register - 0x74 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR6 - CMAR6 - channel x memory address register - 0x78 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR7 - CCR7 - channel x configuration register - 0x80 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR7 - CNDTR7 - channel x number of data register - 0x84 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR7 - CPAR7 - channel x peripheral address register - 0x88 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR7 - CMAR7 - channel x memory address register - 0x8C - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - - - DMA2 - Direct memory access controller - DMA - 0x40020400 - - 0x0 - 0x400 - registers - - - DMA2_CH1 - DMA2 channel 1 interrupt - 55 - - - DMA2_CH2 - DMA2 channel 2 interrupt - 56 - - - DMA2_CH3 - DMA2 channel 3 interrupt - 57 - - - DMA2_CH4 - DMA2 channel 4 interrupt - 58 - - - DMA2_CH5 - DMA2 channel 5 interrupt - 59 - - - DMA2_CH6 - DMA2 channel 6 interrupt - 60 - - - DMA2_CH7 - DMA2 channel 7 interrupt - 61 - - - - ISR - ISR - interrupt status register - 0x0 - 0x20 - read-only - 0x00000000 - - - TEIF7 - Channel x transfer error flag (x = 1 ..7) - 27 - 1 - - - HTIF7 - Channel x half transfer flag (x = 1 ..7) - 26 - 1 - - - TCIF7 - Channel x transfer complete flag (x = 1 ..7) - 25 - 1 - - - GIF7 - Channel x global interrupt flag (x = 1 ..7) - 24 - 1 - - - TEIF6 - Channel x transfer error flag (x = 1 ..7) - 23 - 1 - - - HTIF6 - Channel x half transfer flag (x = 1 ..7) - 22 - 1 - - - TCIF6 - Channel x transfer complete flag (x = 1 ..7) - 21 - 1 - - - GIF6 - Channel x global interrupt flag (x = 1 ..7) - 20 - 1 - - - TEIF5 - Channel x transfer error flag (x = 1 ..7) - 19 - 1 - - - HTIF5 - Channel x half transfer flag (x = 1 ..7) - 18 - 1 - - - TCIF5 - Channel x transfer complete flag (x = 1 ..7) - 17 - 1 - - - GIF5 - Channel x global interrupt flag (x = 1 ..7) - 16 - 1 - - - TEIF4 - Channel x transfer error flag (x = 1 ..7) - 15 - 1 - - - HTIF4 - Channel x half transfer flag (x = 1 ..7) - 14 - 1 - - - TCIF4 - Channel x transfer complete flag (x = 1 ..7) - 13 - 1 - - - GIF4 - Channel x global interrupt flag (x = 1 ..7) - 12 - 1 - - - TEIF3 - Channel x transfer error flag (x = 1 ..7) - 11 - 1 - - - HTIF3 - Channel x half transfer flag (x = 1 ..7) - 10 - 1 - - - TCIF3 - Channel x transfer complete flag (x = 1 ..7) - 9 - 1 - - - GIF3 - Channel x global interrupt flag (x = 1 ..7) - 8 - 1 - - - TEIF2 - Channel x transfer error flag (x = 1 ..7) - 7 - 1 - - - HTIF2 - Channel x half transfer flag (x = 1 ..7) - 6 - 1 - - - TCIF2 - Channel x transfer complete flag (x = 1 ..7) - 5 - 1 - - - GIF2 - Channel x global interrupt flag (x = 1 ..7) - 4 - 1 - - - TEIF1 - Channel x transfer error flag (x = 1 ..7) - 3 - 1 - - - HTIF1 - Channel x half transfer flag (x = 1 ..7) - 2 - 1 - - - TCIF1 - Channel x transfer complete flag (x = 1 ..7) - 1 - 1 - - - GIF1 - Channel x global interrupt flag (x = 1 ..7) - 0 - 1 - - - - - IFCR - IFCR - interrupt flag clear register - 0x4 - 0x20 - write-only - 0x00000000 - - - CTEIF7 - Channel x transfer error clear (x = 1 ..7) - 27 - 1 - - - CHTIF7 - Channel x half transfer clear (x = 1 ..7) - 26 - 1 - - - CTCIF7 - Channel x transfer complete clear (x = 1 ..7) - 25 - 1 - - - CGIF7 - Channel x global interrupt clear (x = 1 ..7) - 24 - 1 - - - CTEIF6 - Channel x transfer error clear (x = 1 ..7) - 23 - 1 - - - CHTIF6 - Channel x half transfer clear (x = 1 ..7) - 22 - 1 - - - CTCIF6 - Channel x transfer complete clear (x = 1 ..7) - 21 - 1 - - - CGIF6 - Channel x global interrupt clear (x = 1 ..7) - 20 - 1 - - - CTEIF5 - Channel x transfer error clear (x = 1 ..7) - 19 - 1 - - - CHTIF5 - Channel x half transfer clear (x = 1 ..7) - 18 - 1 - - - CTCIF5 - Channel x transfer complete clear (x = 1 ..7) - 17 - 1 - - - CGIF5 - Channel x global interrupt clear (x = 1 ..7) - 16 - 1 - - - CTEIF4 - Channel x transfer error clear (x = 1 ..7) - 15 - 1 - - - CHTIF4 - Channel x half transfer clear (x = 1 ..7) - 14 - 1 - - - CTCIF4 - Channel x transfer complete clear (x = 1 ..7) - 13 - 1 - - - CGIF4 - Channel x global interrupt clear (x = 1 ..7) - 12 - 1 - - - CTEIF3 - Channel x transfer error clear (x = 1 ..7) - 11 - 1 - - - CHTIF3 - Channel x half transfer clear (x = 1 ..7) - 10 - 1 - - - CTCIF3 - Channel x transfer complete clear (x = 1 ..7) - 9 - 1 - - - CGIF3 - Channel x global interrupt clear (x = 1 ..7) - 8 - 1 - - - CTEIF2 - Channel x transfer error clear (x = 1 ..7) - 7 - 1 - - - CHTIF2 - Channel x half transfer clear (x = 1 ..7) - 6 - 1 - - - CTCIF2 - Channel x transfer complete clear (x = 1 ..7) - 5 - 1 - - - CGIF2 - Channel x global interrupt clear (x = 1 ..7) - 4 - 1 - - - CTEIF1 - Channel x transfer error clear (x = 1 ..7) - 3 - 1 - - - CHTIF1 - Channel x half transfer clear (x = 1 ..7) - 2 - 1 - - - CTCIF1 - Channel x transfer complete clear (x = 1 ..7) - 1 - 1 - - - CGIF1 - Channel x global interrupt clear (x = 1 ..7) - 0 - 1 - - - - - CCR1 - CCR1 - channel x configuration register - 0x8 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR1 - CNDTR1 - channel x number of data register - 0xC - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR1 - CPAR1 - channel x peripheral address register - 0x10 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR1 - CMAR1 - channel x memory address register - 0x14 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR2 - CCR2 - channel x configuration register - 0x1C - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR2 - CNDTR2 - channel x number of data register - 0x20 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR2 - CPAR2 - channel x peripheral address register - 0x24 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR2 - CMAR2 - channel x memory address register - 0x28 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR3 - CCR3 - channel x configuration register - 0x30 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR3 - CNDTR3 - channel x number of data register - 0x34 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR3 - CPAR3 - channel x peripheral address register - 0x38 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR3 - CMAR3 - channel x memory address register - 0x3C - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR4 - CCR4 - channel x configuration register - 0x44 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR4 - CNDTR4 - channel x number of data register - 0x48 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR4 - CPAR4 - channel x peripheral address register - 0x4C - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR4 - CMAR4 - channel x memory address register - 0x50 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR5 - CCR5 - channel x configuration register - 0x58 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR5 - CNDTR5 - channel x number of data register - 0x5C - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR5 - CPAR5 - channel x peripheral address register - 0x60 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR5 - CMAR5 - channel x memory address register - 0x64 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR6 - CCR6 - channel x configuration register - 0x6C - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR6 - CNDTR6 - channel x number of data register - 0x70 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR6 - CPAR6 - channel x peripheral address register - 0x74 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR6 - CMAR6 - channel x memory address register - 0x78 - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CCR7 - CCR7 - channel x configuration register - 0x80 - 0x20 - read-write - 0x00000000 - - - MEM2MEM - Memory to memory mode - 14 - 1 - - - PL - Channel priority level - 12 - 2 - - - MSIZE - Memory size - 10 - 2 - - - PSIZE - Peripheral size - 8 - 2 - - - MINC - Memory increment mode - 7 - 1 - - - PINC - Peripheral increment mode - 6 - 1 - - - CIRC - Circular mode - 5 - 1 - - - DIR - Data transfer direction - 4 - 1 - - - TEIE - Transfer error interrupt enable - 3 - 1 - - - HTIE - Half transfer interrupt enable - 2 - 1 - - - TCIE - Transfer complete interrupt enable - 1 - 1 - - - EN - Channel enable - 0 - 1 - - - - - CNDTR7 - CNDTR7 - channel x number of data register - 0x84 - 0x20 - read-write - 0x00000000 - - - NDT - Number of data to transfer - 0 - 16 - - - - - CPAR7 - CPAR7 - channel x peripheral address register - 0x88 - 0x20 - read-write - 0x00000000 - - - PA - Peripheral address - 0 - 32 - - - - - CMAR7 - CMAR7 - channel x memory address register - 0x8C - 0x20 - read-write - 0x00000000 - - - MA - Memory address - 0 - 32 - - - - - CSELR - CSELR - channel selection register - 0xA8 - 0x20 - read-write - 0x00000000 - - - C7S - DMA channel 7 selection - 24 - 4 - - - C6S - DMA channel 6 selection - 20 - 4 - - - C5S - DMA channel 5 selection - 16 - 4 - - - C4S - DMA channel 4 selection - 12 - 4 - - - C3S - DMA channel 3 selection - 8 - 4 - - - C2S - DMA channel 2 selection - 4 - 4 - - - C1S - DMA channel 1 selection - 0 - 4 - - - - - - - DMAMUX1 - Direct memory access Multiplexer - DMAMUX - 0x40020800 - - 0x0 - 0x400 - registers - - - DMAMUX_OVR - DMAMUX overrun interrupt - 62 - - - - C0CR - C0CR - DMA Multiplexer Channel 0 Control register - 0x0 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C1CR - C1CR - DMA Multiplexer Channel 1 Control register - 0x4 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C2CR - C2CR - DMA Multiplexer Channel 2 Control register - 0x8 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C3CR - C3CR - DMA Multiplexer Channel 3 Control register - 0xC - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C4CR - C4CR - DMA Multiplexer Channel 4 Control register - 0x10 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C5CR - C5CR - DMA Multiplexer Channel 5 Control register - 0x14 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C6CR - C6CR - DMA Multiplexer Channel 6 Control register - 0x18 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C7CR - C7CR - DMA Multiplexer Channel 7 Control register - 0x1C - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C8CR - C8CR - DMA Multiplexer Channel 8 Control register - 0x20 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C9CR - C9CR - DMA Multiplexer Channel 9 Control register - 0x24 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C10CR - C10CR - DMA Multiplexer Channel 10 Control register - 0x28 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C11CR - C11CR - DMA Multiplexer Channel 11 Control register - 0x2C - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C12CR - C12CR - DMA Multiplexer Channel 12 Control register - 0x30 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - C13CR - C13CR - DMA Multiplexer Channel 13 Control register - 0x34 - 0x20 - read-write - 0x00000000 - - - SYNC_ID - SYNC_ID - 24 - 5 - - - NBREQ - Nb request - 19 - 5 - - - SPOL - Sync polarity - 17 - 2 - - - SE - Synchronization enable - 16 - 1 - - - EGE - Event Generation Enable - 9 - 1 - - - SOIE - Synchronization Overrun Interrupt Enable - 8 - 1 - - - DMAREQ_ID - DMA Request ID - 0 - 8 - - - - - CSR - CSR - DMA Multiplexer Channel Status register - 0x80 - 0x20 - read-only - 0x00000000 - - - SOF0 - Synchronization Overrun Flag 0 - 0 - 1 - - - SOF1 - Synchronization Overrun Flag 1 - 1 - 1 - - - SOF2 - Synchronization Overrun Flag 2 - 2 - 1 - - - SOF3 - Synchronization Overrun Flag 3 - 3 - 1 - - - SOF4 - Synchronization Overrun Flag 4 - 4 - 1 - - - SOF5 - Synchronization Overrun Flag 5 - 5 - 1 - - - SOF6 - Synchronization Overrun Flag 6 - 6 - 1 - - - SOF7 - Synchronization Overrun Flag 7 - 7 - 1 - - - SOF8 - Synchronization Overrun Flag 8 - 8 - 1 - - - SOF9 - Synchronization Overrun Flag 9 - 9 - 1 - - - SOF10 - Synchronization Overrun Flag 10 - 10 - 1 - - - SOF11 - Synchronization Overrun Flag 11 - 11 - 1 - - - SOF12 - Synchronization Overrun Flag 12 - 12 - 1 - - - SOF13 - Synchronization Overrun Flag 13 - 13 - 1 - - - - - CFR - CFR - DMA Channel Clear Flag Register - 0x84 - 0x20 - write-only - 0x00000000 - - - CSOF0 - Synchronization Clear Overrun Flag 0 - 0 - 1 - - - CSOF1 - Synchronization Clear Overrun Flag 1 - 1 - 1 - - - CSOF2 - Synchronization Clear Overrun Flag 2 - 2 - 1 - - - CSOF3 - Synchronization Clear Overrun Flag 3 - 3 - 1 - - - CSOF4 - Synchronization Clear Overrun Flag 4 - 4 - 1 - - - CSOF5 - Synchronization Clear Overrun Flag 5 - 5 - 1 - - - CSOF6 - Synchronization Clear Overrun Flag 6 - 6 - 1 - - - CSOF7 - Synchronization Clear Overrun Flag 7 - 7 - 1 - - - CSOF8 - Synchronization Clear Overrun Flag 8 - 8 - 1 - - - CSOF9 - Synchronization Clear Overrun Flag 9 - 9 - 1 - - - CSOF10 - Synchronization Clear Overrun Flag 10 - 10 - 1 - - - CSOF11 - Synchronization Clear Overrun Flag 11 - 11 - 1 - - - CSOF12 - Synchronization Clear Overrun Flag 12 - 12 - 1 - - - CSOF13 - Synchronization Clear Overrun Flag 13 - 13 - 1 - - - - - RG0CR - RG0CR - DMA Request Generator 0 Control Register - 0x100 - 0x20 - read-write - 0x00000000 - - - GNBREQ - Number of Request - 19 - 5 - - - GPOL - Generation Polarity - 17 - 2 - - - GE - Generation Enable - 16 - 1 - - - OIE - Overrun Interrupt Enable - 8 - 1 - - - SIG_ID - Signal ID - 0 - 5 - - - - - RG1CR - RG1CR - DMA Request Generator 1 Control Register - 0x104 - 0x20 - read-write - 0x00000000 - - - GNBREQ - Number of Request - 19 - 5 - - - GPOL - Generation Polarity - 17 - 2 - - - GE - Generation Enable - 16 - 1 - - - OIE - Overrun Interrupt Enable - 8 - 1 - - - SIG_ID - Signal ID - 0 - 5 - - - - - RG2CR - RG2CR - DMA Request Generator 2 Control Register - 0x108 - 0x20 - read-write - 0x00000000 - - - GNBREQ - Number of Request - 19 - 5 - - - GPOL - Generation Polarity - 17 - 2 - - - GE - Generation Enable - 16 - 1 - - - OIE - Overrun Interrupt Enable - 8 - 1 - - - SIG_ID - Signal ID - 0 - 5 - - - - - RG3CR - RG3CR - DMA Request Generator 3 Control Register - 0x10C - 0x20 - read-write - 0x00000000 - - - GNBREQ - Number of Request - 19 - 5 - - - GPOL - Generation Polarity - 17 - 2 - - - GE - Generation Enable - 16 - 1 - - - OIE - Overrun Interrupt Enable - 8 - 1 - - - SIG_ID - Signal ID - 0 - 5 - - - - - RGSR - RGSR - DMA Request Generator Status Register - 0x140 - 0x20 - read-only - 0x00000000 - - - OF0 - Generator Overrun Flag 0 - 0 - 1 - - - OF1 - Generator Overrun Flag 1 - 1 - 1 - - - OF2 - Generator Overrun Flag 2 - 2 - 1 - - - OF3 - Generator Overrun Flag 3 - 3 - 1 - - - - - RGCFR - RGCFR - DMA Request Generator Clear Flag Register - 0x144 - 0x20 - write-only - 0x00000000 - - - COF0 - Clear trigger Overrun Flag 0 - 0 - 1 - - - COF1 - Clear trigger Overrun Flag 1 - 1 - 1 - - - COF2 - Clear trigger Overrun Flag 2 - 2 - 1 - - - COF3 - Clear trigger Overrun Flag 3 - 3 - 1 - - - - - - - CRC - Cyclic redundancy check calculation unit - CRC - 0x40023000 - - 0x0 - 0x400 - registers - - - - DR - DR - Data register - 0x0 - 0x20 - read-write - 0xFFFFFFFF - - - DR - Data register bits - 0 - 32 - - - - - IDR - IDR - Independent data register - 0x4 - 0x20 - read-write - 0x00000000 - - - IDR - General-purpose 32-bit data register bits - 0 - 32 - - - - - CR - CR - Control register - 0x8 - 0x20 - read-write - 0x00000000 - - - REV_OUT - Reverse output data - 7 - 1 - - - REV_IN - Reverse input data - 5 - 2 - - - POLYSIZE - Polynomial size - 3 - 2 - - - RESET - RESET bit - 0 - 1 - - - - - INIT - INIT - Initial CRC value - 0x10 - 0x20 - read-write - 0xFFFFFFFF - - - CRC_INIT - Programmable initial CRC value - 0 - 32 - - - - - POL - POL - polynomial - 0x14 - 0x20 - read-write - 0x04C11DB7 - - - POL - Programmable polynomial - 0 - 32 - - - - - - - LCD - Liquid crystal display controller - LCD - 0x40002400 - - 0x0 - 0x400 - registers - - - LCD - LCD global interrupt - 49 - - - - CR - CR - control register - 0x0 - 0x20 - read-write - 0x00000000 - - - BIAS - Bias selector - 5 - 2 - - - DUTY - Duty selection - 2 - 3 - - - VSEL - Voltage source selection - 1 - 1 - - - LCDEN - LCD controller enable - 0 - 1 - - - MUX_SEG - Mux segment enable - 7 - 1 - - - BUFEN - Voltage output buffer enable - 8 - 1 - - - - - FCR - FCR - frame control register - 0x4 - 0x20 - read-write - 0x00000000 - - - PS - PS 16-bit prescaler - 22 - 4 - - - DIV - DIV clock divider - 18 - 4 - - - BLINK - Blink mode selection - 16 - 2 - - - BLINKF - Blink frequency selection - 13 - 3 - - - CC - Contrast control - 10 - 3 - - - DEAD - Dead time duration - 7 - 3 - - - PON - Pulse ON duration - 4 - 3 - - - UDDIE - Update display done interrupt enable - 3 - 1 - - - SOFIE - Start of frame interrupt enable - 1 - 1 - - - HD - High drive enable - 0 - 1 - - - - - SR - SR - status register - 0x8 - 0x20 - 0x00000020 - - - FCRSF - LCD Frame Control Register Synchronization flag - 5 - 1 - read-only - - - RDY - Ready flag - 4 - 1 - read-only - - - UDD - Update Display Done - 3 - 1 - read-only - - - UDR - Update display request - 2 - 1 - read-write - - - SOF - Start of frame flag - 1 - 1 - read-only - - - ENS - ENS - 0 - 1 - read-only - - - - - CLR - CLR - clear register - 0xC - 0x20 - write-only - 0x00000000 - - - UDDC - Update display done clear - 3 - 1 - - - SOFC - Start of frame flag clear - 1 - 1 - - - - - RAM_COM0 - RAM_COM0 - display memory - 0x14 - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM1 - RAM_COM1 - display memory - 0x1C - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM2 - RAM_COM2 - display memory - 0x24 - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM3 - RAM_COM3 - display memory - 0x2C - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM4 - RAM_COM4 - display memory - 0x34 - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM5 - RAM_COM5 - display memory - 0x3C - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM6 - RAM_COM6 - display memory - 0x44 - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - RAM_COM7 - RAM_COM7 - display memory - 0x4C - 0x20 - read-write - 0x00000000 - - - S31 - S31 - 31 - 1 - - - S30 - S30 - 30 - 1 - - - S29 - S29 - 29 - 1 - - - S28 - S28 - 28 - 1 - - - S27 - S27 - 27 - 1 - - - S26 - S26 - 26 - 1 - - - S25 - S25 - 25 - 1 - - - S24 - S24 - 24 - 1 - - - S23 - S23 - 23 - 1 - - - S22 - S22 - 22 - 1 - - - S21 - S21 - 21 - 1 - - - S20 - S20 - 20 - 1 - - - S19 - S19 - 19 - 1 - - - S18 - S18 - 18 - 1 - - - S17 - S17 - 17 - 1 - - - S16 - S16 - 16 - 1 - - - S15 - S15 - 15 - 1 - - - S14 - S14 - 14 - 1 - - - S13 - S13 - 13 - 1 - - - S12 - S12 - 12 - 1 - - - S11 - S11 - 11 - 1 - - - S10 - S10 - 10 - 1 - - - S09 - S09 - 9 - 1 - - - S08 - S08 - 8 - 1 - - - S07 - S07 - 7 - 1 - - - S06 - S06 - 6 - 1 - - - S05 - S05 - 5 - 1 - - - S04 - S04 - 4 - 1 - - - S03 - S03 - 3 - 1 - - - S02 - S02 - 2 - 1 - - - S01 - S01 - 1 - 1 - - - S00 - S00 - 0 - 1 - - - - - - - TSC - Touch sensing controller - TSC - 0x40024000 - - 0x0 - 0x400 - registers - - - TSC - TSC global interrupt - 39 - - - - CR - CR - control register - 0x0 - 0x20 - read-write - 0x00000000 - - - CTPH - Charge transfer pulse high - 28 - 4 - - - CTPL - Charge transfer pulse low - 24 - 4 - - - SSD - Spread spectrum deviation - 17 - 7 - - - SSE - Spread spectrum enable - 16 - 1 - - - SSPSC - Spread spectrum prescaler - 15 - 1 - - - PGPSC - pulse generator prescaler - 12 - 3 - - - MCV - Max count value - 5 - 3 - - - IODEF - I/O Default mode - 4 - 1 - - - SYNCPOL - Synchronization pin polarity - 3 - 1 - - - AM - Acquisition mode - 2 - 1 - - - START - Start a new acquisition - 1 - 1 - - - TSCE - Touch sensing controller enable - 0 - 1 - - - - - IER - IER - interrupt enable register - 0x4 - 0x20 - read-write - 0x00000000 - - - MCEIE - Max count error interrupt enable - 1 - 1 - - - EOAIE - End of acquisition interrupt enable - 0 - 1 - - - - - ICR - ICR - interrupt clear register - 0x8 - 0x20 - read-write - 0x00000000 - - - MCEIC - Max count error interrupt clear - 1 - 1 - - - EOAIC - End of acquisition interrupt clear - 0 - 1 - - - - - ISR - ISR - interrupt status register - 0xC - 0x20 - read-write - 0x00000000 - - - MCEF - Max count error flag - 1 - 1 - - - EOAF - End of acquisition flag - 0 - 1 - - - - - IOHCR - IOHCR - I/O hysteresis control register - 0x10 - 0x20 - read-write - 0xFFFFFFFF - - - G7_IO4 - G7_IO4 - 27 - 1 - - - G7_IO3 - G7_IO3 - 26 - 1 - - - G7_IO2 - G7_IO2 - 25 - 1 - - - G7_IO1 - G7_IO1 - 24 - 1 - - - G6_IO4 - G6_IO4 - 23 - 1 - - - G6_IO3 - G6_IO3 - 22 - 1 - - - G6_IO2 - G6_IO2 - 21 - 1 - - - G6_IO1 - G6_IO1 - 20 - 1 - - - G5_IO4 - G5_IO4 - 19 - 1 - - - G5_IO3 - G5_IO3 - 18 - 1 - - - G5_IO2 - G5_IO2 - 17 - 1 - - - G5_IO1 - G5_IO1 - 16 - 1 - - - G4_IO4 - G4_IO4 - 15 - 1 - - - G4_IO3 - G4_IO3 - 14 - 1 - - - G4_IO2 - G4_IO2 - 13 - 1 - - - G4_IO1 - G4_IO1 - 12 - 1 - - - G3_IO4 - G3_IO4 - 11 - 1 - - - G3_IO3 - G3_IO3 - 10 - 1 - - - G3_IO2 - G3_IO2 - 9 - 1 - - - G3_IO1 - G3_IO1 - 8 - 1 - - - G2_IO4 - G2_IO4 - 7 - 1 - - - G2_IO3 - G2_IO3 - 6 - 1 - - - G2_IO2 - G2_IO2 - 5 - 1 - - - G2_IO1 - G2_IO1 - 4 - 1 - - - G1_IO4 - G1_IO4 - 3 - 1 - - - G1_IO3 - G1_IO3 - 2 - 1 - - - G1_IO2 - G1_IO2 - 1 - 1 - - - G1_IO1 - G1_IO1 - 0 - 1 - - - - - IOASCR - IOASCR - I/O analog switch control register - 0x18 - 0x20 - read-write - 0x00000000 - - - G7_IO4 - G7_IO4 - 27 - 1 - - - G7_IO3 - G7_IO3 - 26 - 1 - - - G7_IO2 - G7_IO2 - 25 - 1 - - - G7_IO1 - G7_IO1 - 24 - 1 - - - G6_IO4 - G6_IO4 - 23 - 1 - - - G6_IO3 - G6_IO3 - 22 - 1 - - - G6_IO2 - G6_IO2 - 21 - 1 - - - G6_IO1 - G6_IO1 - 20 - 1 - - - G5_IO4 - G5_IO4 - 19 - 1 - - - G5_IO3 - G5_IO3 - 18 - 1 - - - G5_IO2 - G5_IO2 - 17 - 1 - - - G5_IO1 - G5_IO1 - 16 - 1 - - - G4_IO4 - G4_IO4 - 15 - 1 - - - G4_IO3 - G4_IO3 - 14 - 1 - - - G4_IO2 - G4_IO2 - 13 - 1 - - - G4_IO1 - G4_IO1 - 12 - 1 - - - G3_IO4 - G3_IO4 - 11 - 1 - - - G3_IO3 - G3_IO3 - 10 - 1 - - - G3_IO2 - G3_IO2 - 9 - 1 - - - G3_IO1 - G3_IO1 - 8 - 1 - - - G2_IO4 - G2_IO4 - 7 - 1 - - - G2_IO3 - G2_IO3 - 6 - 1 - - - G2_IO2 - G2_IO2 - 5 - 1 - - - G2_IO1 - G2_IO1 - 4 - 1 - - - G1_IO4 - G1_IO4 - 3 - 1 - - - G1_IO3 - G1_IO3 - 2 - 1 - - - G1_IO2 - G1_IO2 - 1 - 1 - - - G1_IO1 - G1_IO1 - 0 - 1 - - - - - IOSCR - IOSCR - I/O sampling control register - 0x20 - 0x20 - read-write - 0x00000000 - - - G7_IO4 - G7_IO4 - 27 - 1 - - - G7_IO3 - G7_IO3 - 26 - 1 - - - G7_IO2 - G7_IO2 - 25 - 1 - - - G7_IO1 - G7_IO1 - 24 - 1 - - - G6_IO4 - G6_IO4 - 23 - 1 - - - G6_IO3 - G6_IO3 - 22 - 1 - - - G6_IO2 - G6_IO2 - 21 - 1 - - - G6_IO1 - G6_IO1 - 20 - 1 - - - G5_IO4 - G5_IO4 - 19 - 1 - - - G5_IO3 - G5_IO3 - 18 - 1 - - - G5_IO2 - G5_IO2 - 17 - 1 - - - G5_IO1 - G5_IO1 - 16 - 1 - - - G4_IO4 - G4_IO4 - 15 - 1 - - - G4_IO3 - G4_IO3 - 14 - 1 - - - G4_IO2 - G4_IO2 - 13 - 1 - - - G4_IO1 - G4_IO1 - 12 - 1 - - - G3_IO4 - G3_IO4 - 11 - 1 - - - G3_IO3 - G3_IO3 - 10 - 1 - - - G3_IO2 - G3_IO2 - 9 - 1 - - - G3_IO1 - G3_IO1 - 8 - 1 - - - G2_IO4 - G2_IO4 - 7 - 1 - - - G2_IO3 - G2_IO3 - 6 - 1 - - - G2_IO2 - G2_IO2 - 5 - 1 - - - G2_IO1 - G2_IO1 - 4 - 1 - - - G1_IO4 - G1_IO4 - 3 - 1 - - - G1_IO3 - G1_IO3 - 2 - 1 - - - G1_IO2 - G1_IO2 - 1 - 1 - - - G1_IO1 - G1_IO1 - 0 - 1 - - - - - IOCCR - IOCCR - I/O channel control register - 0x28 - 0x20 - read-write - 0x00000000 - - - G7_IO4 - G7_IO4 - 27 - 1 - - - G7_IO3 - G7_IO3 - 26 - 1 - - - G7_IO2 - G7_IO2 - 25 - 1 - - - G7_IO1 - G7_IO1 - 24 - 1 - - - G6_IO4 - G6_IO4 - 23 - 1 - - - G6_IO3 - G6_IO3 - 22 - 1 - - - G6_IO2 - G6_IO2 - 21 - 1 - - - G6_IO1 - G6_IO1 - 20 - 1 - - - G5_IO4 - G5_IO4 - 19 - 1 - - - G5_IO3 - G5_IO3 - 18 - 1 - - - G5_IO2 - G5_IO2 - 17 - 1 - - - G5_IO1 - G5_IO1 - 16 - 1 - - - G4_IO4 - G4_IO4 - 15 - 1 - - - G4_IO3 - G4_IO3 - 14 - 1 - - - G4_IO2 - G4_IO2 - 13 - 1 - - - G4_IO1 - G4_IO1 - 12 - 1 - - - G3_IO4 - G3_IO4 - 11 - 1 - - - G3_IO3 - G3_IO3 - 10 - 1 - - - G3_IO2 - G3_IO2 - 9 - 1 - - - G3_IO1 - G3_IO1 - 8 - 1 - - - G2_IO4 - G2_IO4 - 7 - 1 - - - G2_IO3 - G2_IO3 - 6 - 1 - - - G2_IO2 - G2_IO2 - 5 - 1 - - - G2_IO1 - G2_IO1 - 4 - 1 - - - G1_IO4 - G1_IO4 - 3 - 1 - - - G1_IO3 - G1_IO3 - 2 - 1 - - - G1_IO2 - G1_IO2 - 1 - 1 - - - G1_IO1 - G1_IO1 - 0 - 1 - - - - - IOGCSR - IOGCSR - I/O group control status register - 0x30 - 0x20 - 0x00000000 - - - G7S - Analog I/O group x status - 22 - 1 - read-only - - - G6S - Analog I/O group x status - 21 - 1 - read-only - - - G5S - Analog I/O group x status - 20 - 1 - read-only - - - G4S - Analog I/O group x status - 19 - 1 - read-only - - - G3S - Analog I/O group x status - 18 - 1 - read-only - - - G2S - Analog I/O group x status - 17 - 1 - read-only - - - G1S - Analog I/O group x status - 16 - 1 - read-only - - - G7E - Analog I/O group x enable - 6 - 1 - read-write - - - G6E - Analog I/O group x enable - 5 - 1 - read-write - - - G5E - Analog I/O group x enable - 4 - 1 - read-write - - - G4E - Analog I/O group x enable - 3 - 1 - read-write - - - G3E - Analog I/O group x enable - 2 - 1 - read-write - - - G2E - Analog I/O group x enable - 1 - 1 - read-write - - - G1E - Analog I/O group x enable - 0 - 1 - read-write - - - - - IOG1CR - IOG1CR - I/O group x counter register - 0x34 - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - IOG2CR - IOG2CR - I/O group x counter register - 0x38 - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - IOG3CR - IOG3CR - I/O group x counter register - 0x3C - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - IOG4CR - IOG4CR - I/O group x counter register - 0x40 - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - IOG5CR - IOG5CR - I/O group x counter register - 0x44 - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - IOG6CR - IOG6CR - I/O group x counter register - 0x48 - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - IOG7CR - IOG7CR - I/O group x counter register - 0x4C - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 14 - - - - - - - IWDG - Independent watchdog - IWDG - 0x40003000 - - 0x0 - 0x400 - registers - - - - KR - KR - Key register - 0x0 - 0x20 - write-only - 0x00000000 - - - KEY - Key value (write only, read 0x0000) - 0 - 16 - - - - - PR - PR - Prescaler register - 0x4 - 0x20 - read-write - 0x00000000 - - - PR - Prescaler divider - 0 - 3 - - - - - RLR - RLR - Reload register - 0x8 - 0x20 - read-write - 0x00000FFF - - - RL - Watchdog counter reload value - 0 - 12 - - - - - SR - SR - Status register - 0xC - 0x20 - read-only - 0x00000000 - - - WVU - Watchdog counter window value update - 2 - 1 - - - RVU - Watchdog counter reload value update - 1 - 1 - - - PVU - Watchdog prescaler value update - 0 - 1 - - - - - WINR - WINR - Window register - 0x10 - 0x20 - read-write - 0x00000FFF - - - WIN - Watchdog counter window value - 0 - 12 - - - - - - - WWDG - System window watchdog - WWDG - 0x40002C00 - - 0x0 - 0x400 - registers - - - WWDG - Window Watchdog interrupt - 0 - - - - CR - CR - Control register - 0x0 - 0x20 - read-write - 0x0000007F - - - WDGA - Activation bit - 7 - 1 - - - T - 7-bit counter (MSB to LSB) - 0 - 7 - - - - - CFR - CFR - Configuration register - 0x4 - 0x20 - read-write - 0x0000007F - - - WDGTB - Timer base - 11 - 3 - - - EWI - Early wakeup interrupt - 9 - 1 - - - W - 7-bit window value - 0 - 7 - - - - - SR - SR - Status register - 0x8 - 0x20 - read-write - 0x00000000 - - - EWIF - Early wakeup interrupt flag - 0 - 1 - - - - - - - I2C1 - Inter-integrated circuit - I2C - 0x40005400 - - 0x0 - 0x400 - registers - - - I2C1_EV - I2C1 event interrupt - 30 - - - I2C1_ER - I2C1 error interrupt - 31 - - - - CR1 - CR1 - Control register 1 - 0x0 - 0x20 - read-write - 0x00000000 - - - PE - Peripheral enable - 0 - 1 - - - TXIE - TX Interrupt enable - 1 - 1 - - - RXIE - RX Interrupt enable - 2 - 1 - - - ADDRIE - Address match interrupt enable (slave only) - 3 - 1 - - - NACKIE - Not acknowledge received interrupt enable - 4 - 1 - - - STOPIE - STOP detection Interrupt enable - 5 - 1 - - - TCIE - Transfer Complete interrupt enable - 6 - 1 - - - ERRIE - Error interrupts enable - 7 - 1 - - - DNF - Digital noise filter - 8 - 4 - - - ANFOFF - Analog noise filter OFF - 12 - 1 - - - TXDMAEN - DMA transmission requests enable - 14 - 1 - - - RXDMAEN - DMA reception requests enable - 15 - 1 - - - SBC - Slave byte control - 16 - 1 - - - NOSTRETCH - Clock stretching disable - 17 - 1 - - - WUPEN - Wakeup from STOP enable - 18 - 1 - - - GCEN - General call enable - 19 - 1 - - - SMBHEN - SMBus Host address enable - 20 - 1 - - - SMBDEN - SMBus Device Default address enable - 21 - 1 - - - ALERTEN - SMBUS alert enable - 22 - 1 - - - PECEN - PEC enable - 23 - 1 - - - - - CR2 - CR2 - Control register 2 - 0x4 - 0x20 - read-write - 0x00000000 - - - PECBYTE - Packet error checking byte - 26 - 1 - - - AUTOEND - Automatic end mode (master mode) - 25 - 1 - - - RELOAD - NBYTES reload mode - 24 - 1 - - - NBYTES - Number of bytes - 16 - 8 - - - NACK - NACK generation (slave mode) - 15 - 1 - - - STOP - Stop generation (master mode) - 14 - 1 - - - START - Start generation - 13 - 1 - - - HEAD10R - 10-bit address header only read direction (master receiver mode) - 12 - 1 - - - ADD10 - 10-bit addressing mode (master mode) - 11 - 1 - - - RD_WRN - Transfer direction (master mode) - 10 - 1 - - - SADD - Slave address bit (master mode) - 0 - 10 - - - - - OAR1 - OAR1 - Own address register 1 - 0x8 - 0x20 - read-write - 0x00000000 - - - OA1 - Interface address - 0 - 10 - - - OA1MODE - Own Address 1 10-bit mode - 10 - 1 - - - OA1EN - Own Address 1 enable - 15 - 1 - - - - - OAR2 - OAR2 - Own address register 2 - 0xC - 0x20 - read-write - 0x00000000 - - - OA2 - Interface address - 1 - 7 - - - OA2MSK - Own Address 2 masks - 8 - 3 - - - OA2EN - Own Address 2 enable - 15 - 1 - - - - - TIMINGR - TIMINGR - Timing register - 0x10 - 0x20 - read-write - 0x00000000 - - - SCLL - SCL low period (master mode) - 0 - 8 - - - SCLH - SCL high period (master mode) - 8 - 8 - - - SDADEL - Data hold time - 16 - 4 - - - SCLDEL - Data setup time - 20 - 4 - - - PRESC - Timing prescaler - 28 - 4 - - - - - TIMEOUTR - TIMEOUTR - Status register 1 - 0x14 - 0x20 - read-write - 0x00000000 - - - TIMEOUTA - Bus timeout A - 0 - 12 - - - TIDLE - Idle clock timeout detection - 12 - 1 - - - TIMOUTEN - Clock timeout enable - 15 - 1 - - - TIMEOUTB - Bus timeout B - 16 - 12 - - - TEXTEN - Extended clock timeout enable - 31 - 1 - - - - - ISR - ISR - Interrupt and Status register - 0x18 - 0x20 - 0x00000001 - - - ADDCODE - Address match code (Slave mode) - 17 - 7 - read-only - - - DIR - Transfer direction (Slave mode) - 16 - 1 - read-only - - - BUSY - Bus busy - 15 - 1 - read-only - - - ALERT - SMBus alert - 13 - 1 - read-only - - - TIMEOUT - Timeout or t_low detection flag - 12 - 1 - read-only - - - PECERR - PEC Error in reception - 11 - 1 - read-only - - - OVR - Overrun/Underrun (slave mode) - 10 - 1 - read-only - - - ARLO - Arbitration lost - 9 - 1 - read-only - - - BERR - Bus error - 8 - 1 - read-only - - - TCR - Transfer Complete Reload - 7 - 1 - read-only - - - TC - Transfer Complete (master mode) - 6 - 1 - read-only - - - STOPF - Stop detection flag - 5 - 1 - read-only - - - NACKF - Not acknowledge received flag - 4 - 1 - read-only - - - ADDR - Address matched (slave mode) - 3 - 1 - read-only - - - RXNE - Receive data register not empty (receivers) - 2 - 1 - read-only - - - TXIS - Transmit interrupt status (transmitters) - 1 - 1 - read-write - - - TXE - Transmit data register empty (transmitters) - 0 - 1 - read-write - - - - - ICR - ICR - Interrupt clear register - 0x1C - 0x20 - write-only - 0x00000000 - - - ALERTCF - Alert flag clear - 13 - 1 - - - TIMOUTCF - Timeout detection flag clear - 12 - 1 - - - PECCF - PEC Error flag clear - 11 - 1 - - - OVRCF - Overrun/Underrun flag clear - 10 - 1 - - - ARLOCF - Arbitration lost flag clear - 9 - 1 - - - BERRCF - Bus error flag clear - 8 - 1 - - - STOPCF - Stop detection flag clear - 5 - 1 - - - NACKCF - Not Acknowledge flag clear - 4 - 1 - - - ADDRCF - Address Matched flag clear - 3 - 1 - - - - - PECR - PECR - PEC register - 0x20 - 0x20 - read-only - 0x00000000 - - - PEC - Packet error checking register - 0 - 8 - - - - - RXDR - RXDR - Receive data register - 0x24 - 0x20 - read-only - 0x00000000 - - - RXDATA - 8-bit receive data - 0 - 8 - - - - - TXDR - TXDR - Transmit data register - 0x28 - 0x20 - read-write - 0x00000000 - - - TXDATA - 8-bit transmit data - 0 - 8 - - - - - - - I2C3 - 0x40005C00 - - I2C3_EV - I2C3 event interrupt - 32 - - - I2C3_ER - I2C3 error interrupt - 33 - - - - Flash - Flash - Flash - 0x58004000 - - 0x0 - 0x90 - registers - - - FLASH - Flash global interrupt - 4 - - - - ACR - ACR - Access control register - 0x0 - 0x20 - read-write - 0x00000600 - - - LATENCY - Latency - 0 - 3 - - - PRFTEN - Prefetch enable - 8 - 1 - - - ICEN - Instruction cache enable - 9 - 1 - - - DCEN - Data cache enable - 10 - 1 - - - ICRST - Instruction cache reset - 11 - 1 - - - DCRST - Data cache reset - 12 - 1 - - - PES - CPU1 CortexM4 program erase suspend request - 15 - 1 - - - EMPTY - Flash User area empty - 16 - 1 - - - - - KEYR - KEYR - Flash key register - 0x8 - 0x20 - write-only - 0x00000000 - - - KEYR - KEYR - 0 - 32 - - - - - OPTKEYR - OPTKEYR - Option byte key register - 0xC - 0x20 - write-only - 0x00000000 - - - OPTKEYR - Option byte key - 0 - 32 - - - - - SR - SR - Status register - 0x10 - 0x20 - 0x00000000 - - - EOP - End of operation - 0 - 1 - read-write - - - OPERR - Operation error - 1 - 1 - read-write - - - PROGERR - Programming error - 3 - 1 - read-write - - - WRPERR - Write protected error - 4 - 1 - read-write - - - PGAERR - Programming alignment error - 5 - 1 - read-write - - - SIZERR - Size error - 6 - 1 - read-write - - - PGSERR - Programming sequence error - 7 - 1 - read-write - - - MISERR - Fast programming data miss error - 8 - 1 - read-write - - - FASTERR - Fast programming error - 9 - 1 - read-write - - - OPTNV - User Option OPTVAL indication - 13 - 1 - read-only - - - RDERR - PCROP read error - 14 - 1 - read-write - - - OPTVERR - Option validity error - 15 - 1 - read-write - - - BSY - Busy - 16 - 1 - read-only - - - CFGBSY - Programming or erase configuration busy - 18 - 1 - read-only - - - PESD - Programming or erase operation suspended - 19 - 1 - read-only - - - - - CR - CR - Flash control register - 0x14 - 0x20 - read-write - 0xC0000000 - - - PG - Programming - 0 - 1 - - - PER - Page erase - 1 - 1 - - - MER - This bit triggers the mass erase (all user pages) when set - 2 - 1 - - - PNB - Page number selection - 3 - 8 - - - STRT - Start - 16 - 1 - - - OPTSTRT - Options modification start - 17 - 1 - - - FSTPG - Fast programming - 18 - 1 - - - EOPIE - End of operation interrupt enable - 24 - 1 - - - ERRIE - Error interrupt enable - 25 - 1 - - - RDERRIE - PCROP read error interrupt enable - 26 - 1 - - - OBL_LAUNCH - Force the option byte loading - 27 - 1 - - - OPTLOCK - Options Lock - 30 - 1 - - - LOCK - FLASH_CR Lock - 31 - 1 - - - - - ECCR - ECCR - Flash ECC register - 0x18 - 0x20 - 0x00000000 - - - ADDR_ECC - ECC fail address - 0 - 17 - read-only - - - SYSF_ECC - System Flash ECC fail - 20 - 1 - read-only - - - ECCCIE - ECC correction interrupt enable - 24 - 1 - read-write - - - CPUID - CPU identification - 26 - 3 - read-only - - - ECCC - ECC correction - 30 - 1 - read-write - - - ECCD - ECC detection - 31 - 1 - read-write - - - - - OPTR - OPTR - Flash option register - 0x20 - 0x20 - read-write - 0x10708000 - - - RDP - Read protection level - 0 - 8 - - - ESE - Security enabled - 8 - 1 - - - BOR_LEV - BOR reset Level - 9 - 3 - - - nRST_STOP - nRST_STOP - 12 - 1 - - - nRST_STDBY - nRST_STDBY - 13 - 1 - - - nRST_SHDW - nRST_SHDW - 14 - 1 - - - IDWG_SW - Independent watchdog selection - 16 - 1 - - - IWDG_STOP - Independent watchdog counter freeze in Stop mode - 17 - 1 - - - IWDG_STDBY - Independent watchdog counter freeze in Standby mode - 18 - 1 - - - WWDG_SW - Window watchdog selection - 19 - 1 - - - nBOOT1 - Boot configuration - 23 - 1 - - - SRAM2_PE - SRAM2 parity check enable - 24 - 1 - - - SRAM2_RST - SRAM2 Erase when system reset - 25 - 1 - - - nSWBOOT0 - Software Boot0 - 26 - 1 - - - nBOOT0 - nBoot0 option bit - 27 - 1 - - - AGC_TRIM - Radio Automatic Gain Control Trimming - 29 - 3 - - - - - PCROP1ASR - PCROP1ASR - Flash Bank 1 PCROP Start address zone A register - 0x24 - 0x20 - read-write - 0xFFFFFE00 - - - PCROP1A_STRT - Bank 1 PCROPQ area start offset - 0 - 9 - - - - - PCROP1AER - PCROP1AER - Flash Bank 1 PCROP End address zone A register - 0x28 - 0x20 - read-write - 0x7FFFFE00 - - - PCROP1A_END - Bank 1 PCROP area end offset - 0 - 9 - - - PCROP_RDP - PCROP area preserved when RDP level decreased - 31 - 1 - - - - - WRP1AR - WRP1AR - Flash Bank 1 WRP area A address register - 0x2C - 0x20 - read-write - 0xFF00FF00 - - - WRP1A_STRT - Bank 1 WRP first area A start offset - 0 - 8 - - - WRP1A_END - Bank 1 WRP first area A end offset - 16 - 8 - - - - - WRP1BR - WRP1BR - Flash Bank 1 WRP area B address register - 0x30 - 0x20 - read-write - 0xFF00FF00 - - - WRP1B_STRT - Bank 1 WRP second area B end offset - 16 - 8 - - - WRP1B_END - Bank 1 WRP second area B start offset - 0 - 8 - - - - - PCROP1BSR - PCROP1BSR - Flash Bank 1 PCROP Start address area B register - 0x34 - 0x20 - read-write - 0xFFFFFE00 - - - PCROP1B_STRT - Bank 1 PCROP area B start offset - 0 - 9 - - - - - PCROP1BER - PCROP1BER - Flash Bank 1 PCROP End address area B register - 0x38 - 0x20 - read-write - 0xFFFFFE00 - - - PCROP1B_END - Bank 1 PCROP area end area B offset - 0 - 9 - - - - - IPCCBR - IPCCBR - IPCC mailbox data buffer address register - 0x3C - 0x20 - read-write - 0xFFFFC000 - - - IPCCDBA - PCC mailbox data buffer base address - 0 - 14 - - - - - C2ACR - C2ACR - CPU2 cortex M0 access control register - 0x5C - 0x20 - read-write - 0x00000600 - - - PRFTEN - CPU2 cortex M0 prefetch enable - 8 - 1 - - - ICEN - CPU2 cortex M0 instruction cache enable - 9 - 1 - - - ICRST - CPU2 cortex M0 instruction cache reset - 11 - 1 - - - PES - CPU2 cortex M0 program erase suspend request - 15 - 1 - - - - - C2SR - C2SR - CPU2 cortex M0 status register - 0x60 - 0x20 - read-write - 0x00000000 - - - EOP - End of operation - 0 - 1 - - - OPERR - Operation error - 1 - 1 - - - PROGERR - Programming error - 3 - 1 - - - WRPERR - write protection error - 4 - 1 - - - PGAERR - Programming alignment error - 5 - 1 - - - SIZERR - Size error - 6 - 1 - - - PGSERR - Programming sequence error - 7 - 1 - - - MISSERR - Fast programming data miss error - 8 - 1 - - - FASTERR - Fast programming error - 9 - 1 - - - RDERR - PCROP read error - 14 - 1 - - - BSY - Busy - 16 - 1 - - - CFGBSY - Programming or erase configuration busy - 18 - 1 - - - PESD - Programming or erase operation suspended - 19 - 1 - - - - - C2CR - C2CR - CPU2 cortex M0 control register - 0x64 - 0x20 - read-write - 0x00000000 - - - PG - Programming - 0 - 1 - - - PER - Page erase - 1 - 1 - - - MER - Masse erase - 2 - 1 - - - PNB - Page Number selection - 3 - 8 - - - STRT - Start - 16 - 1 - - - FSTPG - Fast programming - 18 - 1 - - - EOPIE - End of operation interrupt enable - 24 - 1 - - - ERRIE - Error interrupt enable - 25 - 1 - - - RDERRIE - PCROP read error interrupt enable - 26 - 1 - - - - - SFR - SFR - Secure flash start address register - 0x80 - 0x20 - read-write - 0xFFFFEE00 - - - SFSA - Secure flash start address - 0 - 8 - - - DDS - Disable Cortex M0 debug access - 12 - 1 - - - FSD - Flash security disable - 8 - 1 - - - - - SRRVR - SRRVR - Secure SRAM2 start address and cortex M0 reset vector register - 0x84 - 0x20 - read-write - 0x01000000 - - - SBRV - cortex M0 access control register - 0 - 18 - - - SBRSA - Secure backup SRAM2a start address - 18 - 5 - - - BRSD - backup SRAM2a security disable - 23 - 1 - - - SNBRSA - Secure non backup SRAM2a start address - 25 - 5 - - - C2OPT - CPU2 cortex M0 boot reset vector memory selection - 31 - 1 - - - NBRSD - non-backup SRAM2b security disable - 30 - 1 - - - - - - - QUADSPI - QuadSPI interface - QUADSPI - 0xA0001000 - - 0x0 - 0x400 - registers - - - QUADSPI - QSPI global interrupt - 50 - - - - CR - CR - control register - 0x0 - 0x20 - read-write - 0x00000000 - - - PRESCALER - Clock prescaler - 24 - 8 - - - PMM - Polling match mode - 23 - 1 - - - APMS - Automatic poll mode stop - 22 - 1 - - - TOIE - TimeOut interrupt enable - 20 - 1 - - - SMIE - Status match interrupt enable - 19 - 1 - - - FTIE - FIFO threshold interrupt enable - 18 - 1 - - - TCIE - Transfer complete interrupt enable - 17 - 1 - - - TEIE - Transfer error interrupt enable - 16 - 1 - - - FTHRES - FIFO threshold level - 8 - 5 - - - SSHIFT - Sample shift - 4 - 1 - - - TCEN - Timeout counter enable - 3 - 1 - - - DMAEN - DMA enable - 2 - 1 - - - ABORT - Abort request - 1 - 1 - - - EN - Enable - 0 - 1 - - - - - DCR - DCR - device configuration register - 0x4 - 0x20 - read-write - 0x00000000 - - - FSIZE - FLASH memory size - 16 - 5 - - - CSHT - Chip select high time - 8 - 3 - - - CKMODE - Mode 0 / mode 3 - 0 - 1 - - - - - SR - SR - status register - 0x8 - 0x20 - read-only - 0x00000000 - - - FLEVEL - FIFO level - 8 - 6 - - - BUSY - Busy - 5 - 1 - - - TOF - Timeout flag - 4 - 1 - - - SMF - Status match flag - 3 - 1 - - - FTF - FIFO threshold flag - 2 - 1 - - - TCF - Transfer complete flag - 1 - 1 - - - TEF - Transfer error flag - 0 - 1 - - - - - FCR - FCR - flag clear register - 0xC - 0x20 - read-write - 0x00000000 - - - CTOF - Clear timeout flag - 4 - 1 - - - CSMF - Clear status match flag - 3 - 1 - - - CTCF - Clear transfer complete flag - 1 - 1 - - - CTEF - Clear transfer error flag - 0 - 1 - - - - - DLR - DLR - data length register - 0x10 - 0x20 - read-write - 0x00000000 - - - DL - Data length - 0 - 32 - - - - - CCR - CCR - communication configuration register - 0x14 - 0x20 - read-write - 0x00000000 - - - DDRM - Double data rate mode - 31 - 1 - - - SIOO - Send instruction only once mode - 28 - 1 - - - FMODE - Functional mode - 26 - 2 - - - DMODE - Data mode - 24 - 2 - - - DCYC - Number of dummy cycles - 18 - 5 - - - ABSIZE - Alternate bytes size - 16 - 2 - - - ABMODE - Alternate bytes mode - 14 - 2 - - - ADSIZE - Address size - 12 - 2 - - - ADMODE - Address mode - 10 - 2 - - - IMODE - Instruction mode - 8 - 2 - - - INSTRUCTION - Instruction - 0 - 8 - - - - - AR - AR - address register - 0x18 - 0x20 - read-write - 0x00000000 - - - ADDRESS - Address - 0 - 32 - - - - - ABR - ABR - ABR - 0x1C - 0x20 - read-write - 0x00000000 - - - ALTERNATE - ALTERNATE - 0 - 32 - - - - - DR - DR - data register - 0x20 - 0x20 - read-write - 0x00000000 - - - DATA - Data - 0 - 32 - - - - - PSMKR - PSMKR - polling status mask register - 0x24 - 0x20 - read-write - 0x00000000 - - - MASK - Status mask - 0 - 32 - - - - - PSMAR - PSMAR - polling status match register - 0x28 - 0x20 - read-write - 0x00000000 - - - MATCH - Status match - 0 - 32 - - - - - PIR - PIR - polling interval register - 0x2C - 0x20 - read-write - 0x00000000 - - - INTERVAL - Polling interval - 0 - 16 - - - - - LPTR - LPTR - low-power timeout register - 0x30 - 0x20 - read-write - 0x00000000 - - - TIMEOUT - Timeout period - 0 - 16 - - - - - - - RCC - Reset and clock control - RCC - 0x58000000 - - 0x0 - 0x400 - registers - - - RCC - RCC global interrupt - 5 - - - - CR - CR - Clock control register - 0x0 - 0x20 - 0x00000061 - - - PLLSAI1RDY - SAI1 PLL clock ready flag - 27 - 1 - read-only - - - PLLSAI1ON - SAI1 PLL enable - 26 - 1 - read-write - - - PLLRDY - Main PLL clock ready flag - 25 - 1 - read-only - - - PLLON - Main PLL enable - 24 - 1 - read-write - - - HSEPRE - HSE sysclk and PLL M divider prescaler - 20 - 1 - read-write - - - CSSON - HSE Clock security system enable - 19 - 1 - write-only - - - HSEBYP - HSE crystal oscillator bypass - 18 - 1 - read-write - - - HSERDY - HSE clock ready flag - 17 - 1 - read-only - - - HSEON - HSE clock enabled - 16 - 1 - read-write - - - HSIKERDY - HSI kernel clock ready flag for peripherals requests - 12 - 1 - read-only - - - HSIASFS - HSI automatic start from Stop - 11 - 1 - read-write - - - HSIRDY - HSI clock ready flag - 10 - 1 - read-only - - - HSIKERON - HSI always enable for peripheral kernels - 9 - 1 - read-write - - - HSION - HSI clock enabled - 8 - 1 - read-write - - - MSIRANGE - MSI clock ranges - 4 - 4 - read-write - - - MSIPLLEN - MSI clock PLL enable - 2 - 1 - read-write - - - MSIRDY - MSI clock ready flag - 1 - 1 - read-only - - - MSION - MSI clock enable - 0 - 1 - read-write - - - - - ICSCR - ICSCR - Internal clock sources calibration register - 0x4 - 0x20 - 0x40000000 - - - HSITRIM - HSI clock trimming - 24 - 7 - read-write - - - HSICAL - HSI clock calibration - 16 - 8 - read-only - - - MSITRIM - MSI clock trimming - 8 - 8 - read-write - - - MSICAL - MSI clock calibration - 0 - 8 - read-only - - - - - CFGR - CFGR - Clock configuration register - 0x8 - 0x20 - 0x00070000 - - - MCOPRE - Microcontroller clock output prescaler - 28 - 3 - read-write - - - MCOSEL - Microcontroller clock output - 24 - 4 - read-write - - - PPRE2F - APB2 prescaler flag - 18 - 1 - read-only - - - PPRE1F - APB1 prescaler flag - 17 - 1 - read-only - - - HPREF - AHB prescaler flag - 16 - 1 - read-only - - - STOPWUCK - Wakeup from Stop and CSS backup clock selection - 15 - 1 - read-write - - - PPRE2 - APB high-speed prescaler (APB2) - 11 - 3 - read-write - - - PPRE1 - PB low-speed prescaler (APB1) - 8 - 3 - read-write - - - HPRE - AHB prescaler - 4 - 4 - read-write - - - SWS - System clock switch status - 2 - 2 - read-only - - - SW - System clock switch - 0 - 2 - read-write - - - - - PLLCFGR - PLLCFGR - PLLSYS configuration register - 0xC - 0x20 - read-write - 0x22040100 - - - PLLR - Main PLLSYS division factor R for SYSCLK (system clock) - 29 - 3 - - - PLLREN - Main PLLSYSR PLLCLK output enable - 28 - 1 - - - PLLQ - Main PLLSYS division factor Q for PLLSYSUSBCLK - 25 - 3 - - - PLLQEN - Main PLLSYSQ output enable - 24 - 1 - - - PLLP - Main PLL division factor P for PPLSYSSAICLK - 17 - 5 - - - PLLPEN - Main PLLSYSP output enable - 16 - 1 - - - PLLN - Main PLLSYS multiplication factor N - 8 - 7 - - - PLLM - Division factor M for the main PLL and audio PLL (PLLSAI1 and PLLSAI2) input clock - 4 - 3 - - - PLLSRC - Main PLL, PLLSAI1 and PLLSAI2 entry clock source - 0 - 2 - - - - - PLLSAI1CFGR - PLLSAI1CFGR - PLLSAI1 configuration register - 0x10 - 0x20 - read-write - 0x22040100 - - - PLLR - PLLSAI division factor R for PLLADC1CLK (ADC clock) - 29 - 3 - - - PLLREN - PLLSAI PLLADC1CLK output enable - 28 - 1 - - - PLLQ - SAIPLL division factor Q for PLLSAIUSBCLK (48 MHz clock) - 25 - 3 - - - PLLQEN - SAIPLL PLLSAIUSBCLK output enable - 24 - 1 - - - PLLP - SAI1PLL division factor P for PLLSAICLK (SAI1clock) - 17 - 5 - - - PLLPEN - SAIPLL PLLSAI1CLK output enable - 16 - 1 - - - PLLN - SAIPLL multiplication factor for VCO - 8 - 7 - - - - - CIER - CIER - Clock interrupt enable register - 0x18 - 0x20 - read-write - 0x00000000 - - - LSI2RDYIE - LSI2 ready interrupt enable - 11 - 1 - - - HSI48RDYIE - HSI48 ready interrupt enable - 10 - 1 - - - LSECSSIE - LSE clock security system interrupt enable - 9 - 1 - - - PLLSAI1RDYIE - PLLSAI1 ready interrupt enable - 6 - 1 - - - PLLRDYIE - PLLSYS ready interrupt enable - 5 - 1 - - - HSERDYIE - HSE ready interrupt enable - 4 - 1 - - - HSIRDYIE - HSI ready interrupt enable - 3 - 1 - - - MSIRDYIE - MSI ready interrupt enable - 2 - 1 - - - LSERDYIE - LSE ready interrupt enable - 1 - 1 - - - LSI1RDYIE - LSI1 ready interrupt enable - 0 - 1 - - - - - CIFR - CIFR - Clock interrupt flag register - 0x1C - 0x20 - read-only - 0x00000000 - - - LSI2RDYF - LSI2 ready interrupt flag - 11 - 1 - - - HSI48RDYF - HSI48 ready interrupt flag - 10 - 1 - - - LSECSSF - LSE Clock security system interrupt flag - 9 - 1 - - - HSECSSF - HSE Clock security system interrupt flag - 8 - 1 - - - PLLSAI1RDYF - PLLSAI1 ready interrupt flag - 6 - 1 - - - PLLRDYF - PLL ready interrupt flag - 5 - 1 - - - HSERDYF - HSE ready interrupt flag - 4 - 1 - - - HSIRDYF - HSI ready interrupt flag - 3 - 1 - - - MSIRDYF - MSI ready interrupt flag - 2 - 1 - - - LSERDYF - LSE ready interrupt flag - 1 - 1 - - - LSI1RDYF - LSI1 ready interrupt flag - 0 - 1 - - - - - CICR - CICR - Clock interrupt clear register - 0x20 - 0x20 - write-only - 0x00000000 - - - LSI2RDYC - LSI2 ready interrupt clear - 11 - 1 - - - HSI48RDYC - HSI48 ready interrupt clear - 10 - 1 - - - LSECSSC - LSE Clock security system interrupt clear - 9 - 1 - - - HSECSSC - HSE Clock security system interrupt clear - 8 - 1 - - - PLLSAI1RDYC - PLLSAI1 ready interrupt clear - 6 - 1 - - - PLLRDYC - PLL ready interrupt clear - 5 - 1 - - - HSERDYC - HSE ready interrupt clear - 4 - 1 - - - HSIRDYC - HSI ready interrupt clear - 3 - 1 - - - MSIRDYC - MSI ready interrupt clear - 2 - 1 - - - LSERDYC - LSE ready interrupt clear - 1 - 1 - - - LSI1RDYC - LSI1 ready interrupt clear - 0 - 1 - - - - - SMPSCR - SMPSCR - Step Down converter control register - 0x24 - 0x20 - 0x00000301 - - - SMPSSWS - Step Down converter clock switch status - 8 - 2 - read-only - - - SMPSDIV - Step Down converter clock prescaler - 4 - 2 - read-write - - - SMPSSEL - Step Down converter clock selection - 0 - 2 - read-write - - - - - AHB1RSTR - AHB1RSTR - AHB1 peripheral reset register - 0x28 - 0x20 - read-write - 0x00000000 - - - TSCRST - Touch Sensing Controller reset - 16 - 1 - - - CRCRST - CRC reset - 12 - 1 - - - DMAMUXRST - DMAMUX reset - 2 - 1 - - - DMA2RST - DMA2 reset - 1 - 1 - - - DMA1RST - DMA1 reset - 0 - 1 - - - - - AHB2RSTR - AHB2RSTR - AHB2 peripheral reset register - 0x2C - 0x20 - read-write - 0x00000000 - - - AES1RST - AES1 hardware accelerator reset - 16 - 1 - - - ADCRST - ADC reset - 13 - 1 - - - GPIOHRST - IO port H reset - 7 - 1 - - - GPIOERST - IO port E reset - 4 - 1 - - - GPIODRST - IO port D reset - 3 - 1 - - - GPIOCRST - IO port C reset - 2 - 1 - - - GPIOBRST - IO port B reset - 1 - 1 - - - GPIOARST - IO port A reset - 0 - 1 - - - - - AHB3RSTR - AHB3RSTR - AHB3 peripheral reset register - 0x30 - 0x20 - read-write - 0x00000000 - - - FLASHRST - Flash interface reset - 25 - 1 - - - IPCCRST - IPCC interface reset - 20 - 1 - - - HSEMRST - HSEM interface reset - 19 - 1 - - - RNGRST - RNG interface reset - 18 - 1 - - - AES2RST - AES2 interface reset - 17 - 1 - - - PKARST - PKA interface reset - 16 - 1 - - - QSPIRST - Quad SPI memory interface reset - 8 - 1 - - - - - APB1RSTR1 - APB1RSTR1 - APB1 peripheral reset register 1 - 0x38 - 0x20 - read-write - 0x00000000 - - - LPTIM1RST - Low Power Timer 1 reset - 31 - 1 - - - USBFSRST - USB FS reset - 26 - 1 - - - CRSRST - CRS reset - 24 - 1 - - - I2C3RST - I2C3 reset - 23 - 1 - - - I2C1RST - I2C1 reset - 21 - 1 - - - SPI2RST - SPI2 reset - 14 - 1 - - - LCDRST - LCD interface reset - 9 - 1 - - - TIM2RST - TIM2 timer reset - 0 - 1 - - - - - APB1RSTR2 - APB1RSTR2 - APB1 peripheral reset register 2 - 0x3C - 0x20 - read-write - 0x00000000 - - - LPTIM2RST - Low-power timer 2 reset - 5 - 1 - - - LPUART1RST - Low-power UART 1 reset - 0 - 1 - - - - - APB2RSTR - APB2RSTR - APB2 peripheral reset register - 0x40 - 0x20 - read-write - 0x00000000 - - - SAI1RST - Serial audio interface 1 (SAI1) reset - 21 - 1 - - - TIM17RST - TIM17 timer reset - 18 - 1 - - - TIM16RST - TIM16 timer reset - 17 - 1 - - - USART1RST - USART1 reset - 14 - 1 - - - SPI1RST - SPI1 reset - 12 - 1 - - - TIM1RST - TIM1 timer reset - 11 - 1 - - - - - APB3RSTR - APB3RSTR - APB3 peripheral reset register - 0x44 - 0x20 - read-write - 0x00000000 - - - RFRST - Radio system BLE reset - 0 - 1 - - - - - AHB1ENR - AHB1ENR - AHB1 peripheral clock enable register - 0x48 - 0x20 - read-write - 0x00000100 - - - TSCEN - Touch Sensing Controller clock enable - 16 - 1 - - - CRCEN - CPU1 CRC clock enable - 12 - 1 - - - DMAMUXEN - DMAMUX clock enable - 2 - 1 - - - DMA2EN - DMA2 clock enable - 1 - 1 - - - DMA1EN - DMA1 clock enable - 0 - 1 - - - - - AHB2ENR - AHB2ENR - AHB2 peripheral clock enable register - 0x4C - 0x20 - read-write - 0x00000000 - - - AES1EN - AES1 accelerator clock enable - 16 - 1 - - - ADCEN - ADC clock enable - 13 - 1 - - - GPIOHEN - IO port H clock enable - 7 - 1 - - - GPIOEEN - IO port E clock enable - 4 - 1 - - - GPIODEN - IO port D clock enable - 3 - 1 - - - GPIOCEN - IO port C clock enable - 2 - 1 - - - GPIOBEN - IO port B clock enable - 1 - 1 - - - GPIOAEN - IO port A clock enable - 0 - 1 - - - - - AHB3ENR - AHB3ENR - AHB3 peripheral clock enable register - 0x50 - 0x20 - read-write - 0x02080000 - - - FLASHEN - FLASHEN - 25 - 1 - - - IPCCEN - IPCCEN - 20 - 1 - - - HSEMEN - HSEMEN - 19 - 1 - - - RNGEN - RNGEN - 18 - 1 - - - AES2EN - AES2EN - 17 - 1 - - - PKAEN - PKAEN - 16 - 1 - - - QSPIEN - QSPIEN - 8 - 1 - - - - - APB1ENR1 - APB1ENR1 - APB1ENR1 - 0x58 - 0x20 - read-write - 0x00000400 - - - LPTIM1EN - CPU1 Low power timer 1 clock enable - 31 - 1 - - - USBEN - CPU1 USB clock enable - 26 - 1 - - - CRSEN - CPU1 CRS clock enable - 24 - 1 - - - I2C3EN - CPU1 I2C3 clock enable - 23 - 1 - - - I2C1EN - CPU1 I2C1 clock enable - 21 - 1 - - - SPI2EN - CPU1 SPI2 clock enable - 14 - 1 - - - WWDGEN - CPU1 Window watchdog clock enable - 11 - 1 - - - RTCAPBEN - CPU1 RTC APB clock enable - 10 - 1 - - - LCDEN - CPU1 LCD clock enable - 9 - 1 - - - TIM2EN - CPU1 TIM2 timer clock enable - 0 - 1 - - - - - APB1ENR2 - APB1ENR2 - APB1 peripheral clock enable register 2 - 0x5C - 0x20 - read-write - 0x00000000 - - - LPTIM2EN - CPU1 LPTIM2EN - 5 - 1 - - - LPUART1EN - CPU1 Low power UART 1 clock enable - 0 - 1 - - - - - APB2ENR - APB2ENR - APB2ENR - 0x60 - 0x20 - read-write - 0x00000000 - - - SAI1EN - CPU1 SAI1 clock enable - 21 - 1 - - - TIM17EN - CPU1 TIM17 timer clock enable - 18 - 1 - - - TIM16EN - CPU1 TIM16 timer clock enable - 17 - 1 - - - USART1EN - CPU1 USART1clock enable - 14 - 1 - - - SPI1EN - CPU1 SPI1 clock enable - 12 - 1 - - - TIM1EN - CPU1 TIM1 timer clock enable - 11 - 1 - - - - - AHB1SMENR - AHB1SMENR - AHB1 peripheral clocks enable in Sleep and Stop modes register - 0x68 - 0x20 - read-write - 0x00011207 - - - TSCSMEN - CPU1 Touch Sensing Controller clocks enable during Sleep and Stop modes - 16 - 1 - - - CRCSMEN - CPU1 CRCSMEN - 12 - 1 - - - SRAM1SMEN - CPU1 SRAM1 interface clocks enable during Sleep and Stop modes - 9 - 1 - - - DMAMUXSMEN - CPU1 DMAMUX clocks enable during Sleep and Stop modes - 2 - 1 - - - DMA2SMEN - CPU1 DMA2 clocks enable during Sleep and Stop modes - 1 - 1 - - - DMA1SMEN - CPU1 DMA1 clocks enable during Sleep and Stop modes - 0 - 1 - - - - - AHB2SMENR - AHB2SMENR - AHB2 peripheral clocks enable in Sleep and Stop modes register - 0x6C - 0x20 - read-write - 0x0001209F - - - AES1SMEN - CPU1 AES1 accelerator clocks enable during Sleep and Stop modes - 16 - 1 - - - ADCFSSMEN - CPU1 ADC clocks enable during Sleep and Stop modes - 13 - 1 - - - GPIOHSMEN - CPU1 IO port H clocks enable during Sleep and Stop modes - 7 - 1 - - - GPIOESMEN - CPU1 IO port E clocks enable during Sleep and Stop modes - 4 - 1 - - - GPIODSMEN - CPU1 IO port D clocks enable during Sleep and Stop modes - 3 - 1 - - - GPIOCSMEN - CPU1 IO port C clocks enable during Sleep and Stop modes - 2 - 1 - - - GPIOBSMEN - CPU1 IO port B clocks enable during Sleep and Stop modes - 1 - 1 - - - GPIOASMEN - CPU1 IO port A clocks enable during Sleep and Stop modes - 0 - 1 - - - - - AHB3SMENR - AHB3SMENR - AHB3 peripheral clocks enable in Sleep and Stop modes register - 0x70 - 0x20 - read-write - 0x03070100 - - - FLASHSMEN - Flash interface clocks enable during CPU1 sleep mode - 25 - 1 - - - SRAM2SMEN - SRAM2a and SRAM2b memory interface clocks enable during CPU1 sleep mode - 24 - 1 - - - RNGSMEN - True RNG clocks enable during CPU1 sleep mode - 18 - 1 - - - AES2SMEN - AES2 accelerator clocks enable during CPU1 sleep mode - 17 - 1 - - - PKASMEN - PKA accelerator clocks enable during CPU1 sleep mode - 16 - 1 - - - QSPISMEN - QSPISMEN - 8 - 1 - - - - - APB1SMENR1 - APB1SMENR1 - APB1SMENR1 - 0x78 - 0x20 - read-write - 0x85A04E01 - - - LPTIM1SMEN - Low power timer 1 clocks enable during CPU1 Sleep mode - 31 - 1 - - - USBSMEN - USB FS clocks enable during CPU1 Sleep mode - 26 - 1 - - - CRSMEN - CRS clocks enable during CPU1 Sleep mode - 24 - 1 - - - I2C3SMEN - I2C3 clocks enable during CPU1 Sleep mode - 23 - 1 - - - I2C1SMEN - I2C1 clocks enable during CPU1 Sleep mode - 21 - 1 - - - SPI2SMEN - SPI2 clocks enable during CPU1 Sleep mode - 14 - 1 - - - WWDGSMEN - Window watchdog clocks enable during CPU1 Sleep mode - 11 - 1 - - - RTCAPBSMEN - RTC APB clocks enable during CPU1 Sleep mode - 10 - 1 - - - LCDSMEN - LCD clocks enable during CPU1 Sleep mode - 9 - 1 - - - TIM2SMEN - TIM2 timer clocks enable during CPU1 Sleep mode - 0 - 1 - - - - - APB1SMENR2 - APB1SMENR2 - APB1 peripheral clocks enable in Sleep and Stop modes register 2 - 0x7C - 0x20 - read-write - 0x000000021 - - - LPTIM2SMEN - Low power timer 2 clocks enable during CPU1 Sleep mode - 5 - 1 - - - LPUART1SMEN - Low power UART 1 clocks enable during CPU1 Sleep mode - 0 - 1 - - - - - APB2SMENR - APB2SMENR - APB2SMENR - 0x80 - 0x20 - read-write - 0x00265800 - - - SAI1SMEN - SAI1 clocks enable during CPU1 Sleep mode - 21 - 1 - - - TIM17SMEN - TIM17 timer clocks enable during CPU1 Sleep mode - 18 - 1 - - - TIM16SMEN - TIM16 timer clocks enable during CPU1 Sleep mode - 17 - 1 - - - USART1SMEN - USART1clocks enable during CPU1 Sleep mode - 14 - 1 - - - SPI1SMEN - SPI1 clocks enable during CPU1 Sleep mode - 12 - 1 - - - TIM1SMEN - TIM1 timer clocks enable during CPU1 Sleep mode - 11 - 1 - - - - - CCIPR - CCIPR - CCIPR - 0x88 - 0x20 - read-write - 0x00000000 - - - RNGSEL - RNG clock source selection - 30 - 2 - - - ADCSEL - ADCs clock source selection - 28 - 2 - - - CLK48SEL - 48 MHz clock source selection - 26 - 2 - - - SAI1SEL - SAI1 clock source selection - 22 - 2 - - - LPTIM2SEL - Low power timer 2 clock source selection - 20 - 2 - - - LPTIM1SEL - Low power timer 1 clock source selection - 18 - 2 - - - I2C3SEL - I2C3 clock source selection - 16 - 2 - - - I2C1SEL - I2C1 clock source selection - 12 - 2 - - - LPUART1SEL - LPUART1 clock source selection - 10 - 2 - - - USART1SEL - USART1 clock source selection - 0 - 2 - - - - - BDCR - BDCR - BDCR - 0x90 - 0x20 - 0x00000000 - - - LSCOSEL - Low speed clock output selection - 25 - 1 - read-write - - - LSCOEN - Low speed clock output enable - 24 - 1 - read-write - - - BDRST - Backup domain software reset - 16 - 1 - read-write - - - RTCEN - RTC clock enable - 15 - 1 - read-write - - - RTCSEL - RTC clock source selection - 8 - 2 - read-write - - - LSECSSD_ - CSS on LSE failure detection - 6 - 1 - read-only - - - LSECSSON - LSECSSON - 5 - 1 - read-write - - - LSEDRV - SE oscillator drive capability - 3 - 2 - read-write - - - LSEBYP - LSE oscillator bypass - 2 - 1 - read-write - - - LSERDY - LSE oscillator ready - 1 - 1 - read-only - - - LSEON - LSE oscillator enable - 0 - 1 - read-write - - - - - CSR - CSR - CSR - 0x94 - 0x20 - 0x0C000000 - - - LPWRRSTF - Low-power reset flag - 31 - 1 - read-only - - - WWDGRSTF - Window watchdog reset flag - 30 - 1 - read-only - - - IWDGRSTF - Independent window watchdog reset flag - 29 - 1 - read-only - - - SFTRSTF - Software reset flag - 28 - 1 - read-only - - - BORRSTF - BOR flag - 27 - 1 - read-only - - - PINRSTF - Pin reset flag - 26 - 1 - read-only - - - OBLRSTF - Option byte loader reset flag - 25 - 1 - read-only - - - RMVF - Remove reset flag - 23 - 1 - read-write - - - RFWKPSEL - RF system wakeup clock source selection - 14 - 2 - read-write - - - LSI2BW - LSI2 oscillator bias configuration - 8 - 4 - read-write - - - LSI2TRIMOK - LSI2 oscillator trim OK - 5 - 1 - read-only - - - LSI2TRIMEN - LSI2 oscillator trimming enable - 4 - 1 - read-write - - - LSI2RDY - LSI2 oscillator ready - 3 - 1 - read-only - - - LSI2ON - LSI2 oscillator enabled - 2 - 1 - read-write - - - LSI1RDY - LSI1 oscillator ready - 1 - 1 - read-only - - - LSI1ON - LSI1 oscillator enabled - 0 - 1 - read-write - - - RFRSTS - Radio system BLE and 802.15.4 reset status - 16 - 1 - read-only - - - - - CRRCR - CRRCR - Clock recovery RC register - 0x98 - 0x20 - 0x00000000 - - - HSI48CAL - HSI48 clock calibration - 7 - 9 - read-only - - - HSI48RDY - HSI48 clock ready - 1 - 1 - read-only - - - HSI48ON - HSI48 oscillator enabled - 0 - 1 - read-write - - - - - HSECR - HSECR - Clock HSE register - 0x9C - 0x20 - 0x00000030 - - - HSETUNE - HSE capacitor tuning - 8 - 6 - read-only - - - HSEGMC - HSE current control - 4 - 3 - read-write - - - HSES - HSE Sense amplifier threshold - 3 - 1 - read-write - - - UNLOCKED - Register lock system - 0 - 1 - read-write - - - - - EXTCFGR - EXTCFGR - Extended clock recovery register - 0x108 - 0x20 - 0x00030000 - - - RFCSS - RF clock source selected - 20 - 1 - read-only - - - C2HPREF - CPU2 AHB prescaler flag - 17 - 1 - read-only - - - SHDHPREF - Shared AHB prescaler flag - 16 - 1 - read-only - - - C2HPRE - CPU2 AHB prescaler - 4 - 4 - read-write - - - SHDHPRE - Shared AHB prescaler - 0 - 4 - read-write - - - - - C2AHB1ENR - C2AHB1ENR - CPU2 AHB1 peripheral clock enable register - 0x148 - 0x20 - read-write - 0x00000000 - - - TSCEN - CPU2 Touch Sensing Controller clock enable - 16 - 1 - - - CRCEN - CPU2 CRC clock enable - 12 - 1 - - - SRAM1EN - CPU2 SRAM1 clock enable - 9 - 1 - - - DMAMUXEN - CPU2 DMAMUX clock enable - 2 - 1 - - - DMA2EN - CPU2 DMA2 clock enable - 1 - 1 - - - DMA1EN - CPU2 DMA1 clock enable - 0 - 1 - - - - - C2AHB2ENR - C2AHB2ENR - CPU2 AHB2 peripheral clock enable register - 0x14C - 0x20 - read-write - 0x00000000 - - - AES1EN - CPU2 AES1 accelerator clock enable - 16 - 1 - - - ADCEN - CPU2 ADC clock enable - 13 - 1 - - - GPIOHEN - CPU2 IO port H clock enable - 7 - 1 - - - GPIOEEN - CPU2 IO port E clock enable - 4 - 1 - - - GPIODEN - CPU2 IO port D clock enable - 3 - 1 - - - GPIOCEN - CPU2 IO port C clock enable - 2 - 1 - - - GPIOBEN - CPU2 IO port B clock enable - 1 - 1 - - - GPIOAEN - CPU2 IO port A clock enable - 0 - 1 - - - - - C2AHB3ENR - C2AHB3ENR - CPU2 AHB3 peripheral clock enable register - 0x150 - 0x20 - read-write - 0x02080000 - - - FLASHEN - CPU2 FLASHEN - 25 - 1 - - - IPCCEN - CPU2 IPCCEN - 20 - 1 - - - HSEMEN - CPU2 HSEMEN - 19 - 1 - - - RNGEN - CPU2 RNGEN - 18 - 1 - - - AES2EN - CPU2 AES2EN - 17 - 1 - - - PKAEN - CPU2 PKAEN - 16 - 1 - - - - - C2APB1ENR1 - C2APB1ENR1 - CPU2 APB1ENR1 - 0x158 - 0x20 - read-write - 0x00000400 - - - LPTIM1EN - CPU2 Low power timer 1 clock enable - 31 - 1 - - - USBEN - CPU2 USB clock enable - 26 - 1 - - - CRSEN - CPU2 CRS clock enable - 24 - 1 - - - I2C3EN - CPU2 I2C3 clock enable - 23 - 1 - - - I2C1EN - CPU2 I2C1 clock enable - 21 - 1 - - - SPI2EN - CPU2 SPI2 clock enable - 14 - 1 - - - RTCAPBEN - CPU2 RTC APB clock enable - 10 - 1 - - - LCDEN - CPU2 LCD clock enable - 9 - 1 - - - TIM2EN - CPU2 TIM2 timer clock enable - 0 - 1 - - - - - C2APB1ENR2 - C2APB1ENR2 - CPU2 APB1 peripheral clock enable register 2 - 0x15C - 0x20 - read-write - 0x00000000 - - - LPTIM2EN - CPU2 LPTIM2EN - 5 - 1 - - - LPUART1EN - CPU2 Low power UART 1 clock enable - 0 - 1 - - - - - C2APB2ENR - C2APB2ENR - CPU2 APB2ENR - 0x160 - 0x20 - read-write - 0x00000000 - - - SAI1EN - CPU2 SAI1 clock enable - 21 - 1 - - - TIM17EN - CPU2 TIM17 timer clock enable - 18 - 1 - - - TIM16EN - CPU2 TIM16 timer clock enable - 17 - 1 - - - USART1EN - CPU2 USART1clock enable - 14 - 1 - - - SPI1EN - CPU2 SPI1 clock enable - 12 - 1 - - - TIM1EN - CPU2 TIM1 timer clock enable - 11 - 1 - - - - - C2APB3ENR - C2APB3ENR - CPU2 APB3ENR - 0x164 - 0x20 - read-write - 0x00000000 - - - EN802 - CPU2 802.15.4 interface clock enable - 1 - 1 - - - BLEEN - CPU2 BLE interface clock enable - 0 - 1 - - - - - C2AHB1SMENR - C2AHB1SMENR - CPU2 AHB1 peripheral clocks enable in Sleep and Stop modes register - 0x168 - 0x20 - read-write - 0x00011207 - - - TSCSMEN - CPU2 Touch Sensing Controller clocks enable during Sleep and Stop modes - 16 - 1 - - - CRCSMEN - CPU2 CRCSMEN - 12 - 1 - - - SRAM1SMEN - SRAM1 interface clock enable during CPU1 CSleep mode - 9 - 1 - - - DMAMUXSMEN - CPU2 DMAMUX clocks enable during Sleep and Stop modes - 2 - 1 - - - DMA2SMEN - CPU2 DMA2 clocks enable during Sleep and Stop modes - 1 - 1 - - - DMA1SMEN - CPU2 DMA1 clocks enable during Sleep and Stop modes - 0 - 1 - - - - - C2AHB2SMENR - C2AHB2SMENR - CPU2 AHB2 peripheral clocks enable in Sleep and Stop modes register - 0x16C - 0x20 - read-write - 0x0001209F - - - AES1SMEN - CPU2 AES1 accelerator clocks enable during Sleep and Stop modes - 16 - 1 - - - ADCFSSMEN - CPU2 ADC clocks enable during Sleep and Stop modes - 13 - 1 - - - GPIOHSMEN - CPU2 IO port H clocks enable during Sleep and Stop modes - 7 - 1 - - - GPIOESMEN - CPU2 IO port E clocks enable during Sleep and Stop modes - 4 - 1 - - - GPIODSMEN - CPU2 IO port D clocks enable during Sleep and Stop modes - 3 - 1 - - - GPIOCSMEN - CPU2 IO port C clocks enable during Sleep and Stop modes - 2 - 1 - - - GPIOBSMEN - CPU2 IO port B clocks enable during Sleep and Stop modes - 1 - 1 - - - GPIOASMEN - CPU2 IO port A clocks enable during Sleep and Stop modes - 0 - 1 - - - - - C2AHB3SMENR - C2AHB3SMENR - CPU2 AHB3 peripheral clocks enable in Sleep and Stop modes register - 0x170 - 0x20 - read-write - 0x03070000 - - - FLASHSMEN - Flash interface clocks enable during CPU2 sleep modes - 25 - 1 - - - SRAM2SMEN - SRAM2a and SRAM2b memory interface clocks enable during CPU2 sleep modes - 24 - 1 - - - RNGSMEN - True RNG clocks enable during CPU2 sleep modes - 18 - 1 - - - AES2SMEN - AES2 accelerator clocks enable during CPU2 sleep modes - 17 - 1 - - - PKASMEN - PKA accelerator clocks enable during CPU2 sleep modes - 16 - 1 - - - - - C2APB1SMENR1 - C2APB1SMENR1 - CPU2 APB1SMENR1 - 0x178 - 0x20 - read-write - 0x85A04601 - - - LPTIM1SMEN - Low power timer 1 clocks enable during CPU2 Sleep mode - 31 - 1 - - - USBSMEN - USB FS clocks enable during CPU2 Sleep mode - 26 - 1 - - - CRSMEN - CRS clocks enable during CPU2 Sleep mode - 24 - 1 - - - I2C3SMEN - I2C3 clocks enable during CPU2 Sleep mode - 23 - 1 - - - I2C1SMEN - I2C1 clocks enable during CPU2 Sleep mode - 21 - 1 - - - SPI2SMEN - SPI2 clocks enable during CPU2 Sleep mode - 14 - 1 - - - RTCAPBSMEN - RTC APB clocks enable during CPU2 Sleep mode - 10 - 1 - - - LCDSMEN - LCD clocks enable during CPU2 Sleep mode - 9 - 1 - - - TIM2SMEN - TIM2 timer clocks enable during CPU2 Sleep mode - 0 - 1 - - - - - C2APB1SMENR2 - C2APB1SMENR2 - CPU2 APB1 peripheral clocks enable in Sleep and Stop modes register 2 - 0x17C - 0x20 - read-write - 0x000000021 - - - LPTIM2SMEN - Low power timer 2 clocks enable during CPU2 Sleep mode - 5 - 1 - - - LPUART1SMEN - Low power UART 1 clocks enable during CPU2 Sleep mode - 0 - 1 - - - - - C2APB2SMENR - C2APB2SMENR - CPU2 APB2SMENR - 0x180 - 0x20 - read-write - 0x00265800 - - - SAI1SMEN - SAI1 clocks enable during CPU2 Sleep mode - 21 - 1 - - - TIM17SMEN - TIM17 timer clocks enable during CPU2 Sleep mode - 18 - 1 - - - TIM16SMEN - TIM16 timer clocks enable during CPU2 Sleep mode - 17 - 1 - - - USART1SMEN - USART1clocks enable during CPU2 Sleep mode - 14 - 1 - - - SPI1SMEN - SPI1 clocks enable during CPU2 Sleep mode - 12 - 1 - - - TIM1SMEN - TIM1 timer clocks enable during CPU2 Sleep mode - 11 - 1 - - - - - C2APB3SMENR - C2APB3SMENR - CPU2 APB3SMENR - 0x184 - 0x20 - read-write - 0x0000003 - - - SMEN802 - 802.15.4 interface clocks enable during CPU2 Sleep modes - 1 - 1 - - - BLESMEN - BLE interface clocks enable during CPU2 Sleep mode - 0 - 1 - - - - - - - PWR - Power control - PWR - 0x58000400 - - 0x0 - 0x400 - registers - - - PWR_SOTF - PWR switching on the fly - interrupt - 43 - - - - CR1 - CR1 - Power control register 1 - 0x0 - 0x20 - read-write - 0x00000200 - - - LPR - Low-power run - 14 - 1 - - - VOS - Voltage scaling range selection - 9 - 2 - - - DBP - Disable backup domain write protection - 8 - 1 - - - FPDS - Flash power down mode during LPsSleep for CPU1 - 5 - 1 - - - FPDR - Flash power down mode during LPRun for CPU1 - 4 - 1 - - - LPMS - Low-power mode selection for CPU1 - 0 - 3 - - - - - CR2 - CR2 - Power control register 2 - 0x4 - 0x20 - read-write - 0x00000000 - - - USV - VDDUSB USB supply valid - 10 - 1 - - - PVME3 - Peripheral voltage monitoring 3 enable: VDDA vs. 1.62V - 6 - 1 - - - PVME1 - Peripheral voltage monitoring 1 enable: VDDUSB vs. 1.2V - 4 - 1 - - - PLS - Power voltage detector level selection - 1 - 3 - - - PVDE - Power voltage detector enable - 0 - 1 - - - - - CR3 - CR3 - Power control register 3 - 0x8 - 0x20 - read-write - 0x00008000 - - - EIWUL - Enable internal wakeup line for CPU1 - 15 - 1 - - - EC2H - Enable CPU2 Hold interrupt for CPU1 - 14 - 1 - - - E802A - Enable end of activity interrupt for CPU1 - 13 - 1 - - - EBLEA - Enable BLE end of activity interrupt for CPU1 - 11 - 1 - - - ECRPE - Enable critical radio phase end of activity interrupt for CPU1 - 12 - 1 - - - APC - Apply pull-up and pull-down configuration - 10 - 1 - - - RRS - SRAM2a retention in Standby mode - 9 - 1 - - - EBORHSDFB - Enable BORH and Step Down counverter forced in Bypass interrups for CPU1 - 8 - 1 - - - EWUP5 - Enable Wakeup pin WKUP5 - 4 - 1 - - - EWUP4 - Enable Wakeup pin WKUP4 - 3 - 1 - - - EWUP3 - Enable Wakeup pin WKUP3 - 2 - 1 - - - EWUP2 - Enable Wakeup pin WKUP2 - 1 - 1 - - - EWUP1 - Enable Wakeup pin WKUP1 - 0 - 1 - - - - - CR4 - CR4 - Power control register 4 - 0xC - 0x20 - read-write - 0x00000000 - - - C2BOOT - BOOT CPU2 after reset or wakeup from Stop or Standby modes - 15 - 1 - - - VBRS - VBAT battery charging resistor selection - 9 - 1 - - - VBE - VBAT battery charging enable - 8 - 1 - - - WP5 - Wakeup pin WKUP5 polarity - 4 - 1 - - - WP4 - Wakeup pin WKUP4 polarity - 3 - 1 - - - WP3 - Wakeup pin WKUP3 polarity - 2 - 1 - - - WP2 - Wakeup pin WKUP2 polarity - 1 - 1 - - - WP1 - Wakeup pin WKUP1 polarity - 0 - 1 - - - - - SR1 - SR1 - Power status register 1 - 0x10 - 0x20 - read-only - 0x00000000 - - - WUFI - Internal Wakeup interrupt flag - 15 - 1 - - - C2HF - CPU2 Hold interrupt flag - 14 - 1 - - - AF802 - 802.15.4 end of activity interrupt flag - 13 - 1 - - - BLEAF - BLE end of activity interrupt flag - 12 - 1 - - - CRPEF - Enable critical radio phase end of activity interrupt flag - 11 - 1 - - - WUF802 - 802.15.4 wakeup interrupt flag - 10 - 1 - - - BLEWUF - BLE wakeup interrupt flag - 9 - 1 - - - BORHF - BORH interrupt flag - 8 - 1 - - - SDFBF - Step Down converter forced in Bypass interrupt flag - 7 - 1 - - - CWUF5 - Wakeup flag 5 - 4 - 1 - - - CWUF4 - Wakeup flag 4 - 3 - 1 - - - CWUF3 - Wakeup flag 3 - 2 - 1 - - - CWUF2 - Wakeup flag 2 - 1 - 1 - - - CWUF1 - Wakeup flag 1 - 0 - 1 - - - - - SR2 - SR2 - Power status register 2 - 0x14 - 0x20 - read-only - 0x00000002 - - - PVMO3 - Peripheral voltage monitoring output: VDDA vs. 1.62 V - 14 - 1 - - - PVMO1 - Peripheral voltage monitoring output: VDDUSB vs. 1.2 V - 12 - 1 - - - PVDO - Power voltage detector output - 11 - 1 - - - VOSF - Voltage scaling flag - 10 - 1 - - - REGLPF - Low-power regulator flag - 9 - 1 - - - REGLPS - Low-power regulator started - 8 - 1 - - - SDSMPSF - Step Down converter SMPS mode flag - 1 - 1 - - - SDBF - Step Down converter Bypass mode flag - 0 - 1 - - - - - SCR - SCR - Power status clear register - 0x18 - 0x20 - write-only - 0x00000000 - - - CC2HF - Clear CPU2 Hold interrupt flag - 14 - 1 - - - C802AF - Clear 802.15.4 end of activity interrupt flag - 13 - 1 - - - CBLEAF - Clear BLE end of activity interrupt flag - 12 - 1 - - - CCRPEF - Clear critical radio phase end of activity interrupt flag - 11 - 1 - - - C802WUF - Clear 802.15.4 wakeup interrupt flag - 10 - 1 - - - CBLEWUF - Clear BLE wakeup interrupt flag - 9 - 1 - - - CBORHF - Clear BORH interrupt flag - 8 - 1 - - - CSMPSFBF - Clear SMPS Step Down converter forced in Bypass interrupt flag - 7 - 1 - - - CWUF5 - Clear wakeup flag 5 - 4 - 1 - - - CWUF4 - Clear wakeup flag 4 - 3 - 1 - - - CWUF3 - Clear wakeup flag 3 - 2 - 1 - - - CWUF2 - Clear wakeup flag 2 - 1 - 1 - - - CWUF1 - Clear wakeup flag 1 - 0 - 1 - - - - - CR5 - CR5 - Power control register 5 - 0x1C - 0x20 - read-write - 0x00004270 - - - SDEB - Enable Step Down converter SMPS mode enabled - 15 - 1 - - - SDBEN - Enable Step Down converter Bypass mode enabled - 14 - 1 - - - SMPSCFG - VOS configuration selection (non user) - 9 - 1 - - - BORHC - BORH configuration selection - 8 - 1 - - - SDSC - Step Down converter supplt startup current selection - 4 - 3 - - - SDVOS - Step Down converter voltage output scaling - 0 - 4 - - - - - PUCRA - PUCRA - Power Port A pull-up control register - 0x20 - 0x20 - read-write - 0x00000000 - - - PU15 - Port A pull-up bit y (y=0..15) - 15 - 1 - - - PU13 - Port A pull-up bit y (y=0..15) - 13 - 1 - - - PU12 - Port A pull-up bit y (y=0..15) - 12 - 1 - - - PU11 - Port A pull-up bit y (y=0..15) - 11 - 1 - - - PU10 - Port A pull-up bit y (y=0..15) - 10 - 1 - - - PU9 - Port A pull-up bit y (y=0..15) - 9 - 1 - - - PU8 - Port A pull-up bit y (y=0..15) - 8 - 1 - - - PU7 - Port A pull-up bit y (y=0..15) - 7 - 1 - - - PU6 - Port A pull-up bit y (y=0..15) - 6 - 1 - - - PU5 - Port A pull-up bit y (y=0..15) - 5 - 1 - - - PU4 - Port A pull-up bit y (y=0..15) - 4 - 1 - - - PU3 - Port A pull-up bit y (y=0..15) - 3 - 1 - - - PU2 - Port A pull-up bit y (y=0..15) - 2 - 1 - - - PU1 - Port A pull-up bit y (y=0..15) - 1 - 1 - - - PU0 - Port A pull-up bit y (y=0..15) - 0 - 1 - - - - - PDCRA - PDCRA - Power Port A pull-down control register - 0x24 - 0x20 - read-write - 0x00000000 - - - PD14 - Port A pull-down bit y (y=0..15) - 14 - 1 - - - PD12 - Port A pull-down bit y (y=0..15) - 12 - 1 - - - PD11 - Port A pull-down bit y (y=0..15) - 11 - 1 - - - PD10 - Port A pull-down bit y (y=0..15) - 10 - 1 - - - PD9 - Port A pull-down bit y (y=0..15) - 9 - 1 - - - PD8 - Port A pull-down bit y (y=0..15) - 8 - 1 - - - PD7 - Port A pull-down bit y (y=0..15) - 7 - 1 - - - PD6 - Port A pull-down bit y (y=0..15) - 6 - 1 - - - PD5 - Port A pull-down bit y (y=0..15) - 5 - 1 - - - PD4 - Port A pull-down bit y (y=0..15) - 4 - 1 - - - PD3 - Port A pull-down bit y (y=0..15) - 3 - 1 - - - PD2 - Port A pull-down bit y (y=0..15) - 2 - 1 - - - PD1 - Port A pull-down bit y (y=0..15) - 1 - 1 - - - PD0 - Port A pull-down bit y (y=0..15) - 0 - 1 - - - - - PUCRB - PUCRB - Power Port B pull-up control register - 0x28 - 0x20 - read-write - 0x00000000 - - - PU15 - Port B pull-up bit y (y=0..15) - 15 - 1 - - - PU14 - Port B pull-up bit y (y=0..15) - 14 - 1 - - - PU13 - Port B pull-up bit y (y=0..15) - 13 - 1 - - - PU12 - Port B pull-up bit y (y=0..15) - 12 - 1 - - - PU11 - Port B pull-up bit y (y=0..15) - 11 - 1 - - - PU10 - Port B pull-up bit y (y=0..15) - 10 - 1 - - - PU9 - Port B pull-up bit y (y=0..15) - 9 - 1 - - - PU8 - Port B pull-up bit y (y=0..15) - 8 - 1 - - - PU7 - Port B pull-up bit y (y=0..15) - 7 - 1 - - - PU6 - Port B pull-up bit y (y=0..15) - 6 - 1 - - - PU5 - Port B pull-up bit y (y=0..15) - 5 - 1 - - - PU4 - Port B pull-up bit y (y=0..15) - 4 - 1 - - - PU3 - Port B pull-up bit y (y=0..15) - 3 - 1 - - - PU2 - Port B pull-up bit y (y=0..15) - 2 - 1 - - - PU1 - Port B pull-up bit y (y=0..15) - 1 - 1 - - - PU0 - Port B pull-up bit y (y=0..15) - 0 - 1 - - - - - PDCRB - PDCRB - Power Port B pull-down control register - 0x2C - 0x20 - read-write - 0x00000000 - - - PD15 - Port B pull-down bit y (y=0..15) - 15 - 1 - - - PD14 - Port B pull-down bit y (y=0..15) - 14 - 1 - - - PD13 - Port B pull-down bit y (y=0..15) - 13 - 1 - - - PD12 - Port B pull-down bit y (y=0..15) - 12 - 1 - - - PD11 - Port B pull-down bit y (y=0..15) - 11 - 1 - - - PD10 - Port B pull-down bit y (y=0..15) - 10 - 1 - - - PD9 - Port B pull-down bit y (y=0..15) - 9 - 1 - - - PD8 - Port B pull-down bit y (y=0..15) - 8 - 1 - - - PD7 - Port B pull-down bit y (y=0..15) - 7 - 1 - - - PD6 - Port B pull-down bit y (y=0..15) - 6 - 1 - - - PD5 - Port B pull-down bit y (y=0..15) - 5 - 1 - - - PD3 - Port B pull-down bit y (y=0..15) - 3 - 1 - - - PD2 - Port B pull-down bit y (y=0..15) - 2 - 1 - - - PD1 - Port B pull-down bit y (y=0..15) - 1 - 1 - - - PD0 - Port B pull-down bit y (y=0..15) - 0 - 1 - - - - - PUCRC - PUCRC - Power Port C pull-up control register - 0x30 - 0x20 - read-write - 0x00000000 - - - PU15 - Port C pull-up bit y (y=0..15) - 15 - 1 - - - PU14 - Port C pull-up bit y (y=0..15) - 14 - 1 - - - PU13 - Port C pull-up bit y (y=0..15) - 13 - 1 - - - PU12 - Port C pull-up bit y (y=0..15) - 12 - 1 - - - PU11 - Port C pull-up bit y (y=0..15) - 11 - 1 - - - PU10 - Port C pull-up bit y (y=0..15) - 10 - 1 - - - PU9 - Port C pull-up bit y (y=0..15) - 9 - 1 - - - PU8 - Port C pull-up bit y (y=0..15) - 8 - 1 - - - PU7 - Port C pull-up bit y (y=0..15) - 7 - 1 - - - PU6 - Port C pull-up bit y (y=0..15) - 6 - 1 - - - PU5 - Port C pull-up bit y (y=0..15) - 5 - 1 - - - PU4 - Port C pull-up bit y (y=0..15) - 4 - 1 - - - PU3 - Port C pull-up bit y (y=0..15) - 3 - 1 - - - PU2 - Port C pull-up bit y (y=0..15) - 2 - 1 - - - PU1 - Port C pull-up bit y (y=0..15) - 1 - 1 - - - PU0 - Port C pull-up bit y (y=0..15) - 0 - 1 - - - - - PDCRC - PDCRC - Power Port C pull-down control register - 0x34 - 0x20 - read-write - 0x00000000 - - - PD15 - Port C pull-down bit y (y=0..15) - 15 - 1 - - - PD14 - Port C pull-down bit y (y=0..15) - 14 - 1 - - - PD13 - Port C pull-down bit y (y=0..15) - 13 - 1 - - - PD12 - Port C pull-down bit y (y=0..15) - 12 - 1 - - - PD11 - Port C pull-down bit y (y=0..15) - 11 - 1 - - - PD10 - Port C pull-down bit y (y=0..15) - 10 - 1 - - - PD9 - Port C pull-down bit y (y=0..15) - 9 - 1 - - - PD8 - Port C pull-down bit y (y=0..15) - 8 - 1 - - - PD7 - Port C pull-down bit y (y=0..15) - 7 - 1 - - - PD6 - Port C pull-down bit y (y=0..15) - 6 - 1 - - - PD5 - Port C pull-down bit y (y=0..15) - 5 - 1 - - - PD4 - Port C pull-down bit y (y=0..15) - 4 - 1 - - - PD3 - Port C pull-down bit y (y=0..15) - 3 - 1 - - - PD2 - Port C pull-down bit y (y=0..15) - 2 - 1 - - - PD1 - Port C pull-down bit y (y=0..15) - 1 - 1 - - - PD0 - Port C pull-down bit y (y=0..15) - 0 - 1 - - - - - PUCRD - PUCRD - Power Port D pull-up control register - 0x38 - 0x20 - read-write - 0x00000000 - - - PU15 - Port D pull-up bit y (y=0..15) - 15 - 1 - - - PU14 - Port D pull-up bit y (y=0..15) - 14 - 1 - - - PU13 - Port D pull-up bit y (y=0..15) - 13 - 1 - - - PU12 - Port D pull-up bit y (y=0..15) - 12 - 1 - - - PU11 - Port D pull-up bit y (y=0..15) - 11 - 1 - - - PU10 - Port D pull-up bit y (y=0..15) - 10 - 1 - - - PU9 - Port D pull-up bit y (y=0..15) - 9 - 1 - - - PU8 - Port D pull-up bit y (y=0..15) - 8 - 1 - - - PU7 - Port D pull-up bit y (y=0..15) - 7 - 1 - - - PU6 - Port D pull-up bit y (y=0..15) - 6 - 1 - - - PU5 - Port D pull-up bit y (y=0..15) - 5 - 1 - - - PU4 - Port D pull-up bit y (y=0..15) - 4 - 1 - - - PU3 - Port D pull-up bit y (y=0..15) - 3 - 1 - - - PU2 - Port D pull-up bit y (y=0..15) - 2 - 1 - - - PU1 - Port D pull-up bit y (y=0..15) - 1 - 1 - - - PU0 - Port D pull-up bit y (y=0..15) - 0 - 1 - - - - - PDCRD - PDCRD - Power Port D pull-down control register - 0x3C - 0x20 - read-write - 0x00000000 - - - PD15 - Port D pull-down bit y (y=0..15) - 15 - 1 - - - PD14 - Port D pull-down bit y (y=0..15) - 14 - 1 - - - PD13 - Port D pull-down bit y (y=0..15) - 13 - 1 - - - PD12 - Port D pull-down bit y (y=0..15) - 12 - 1 - - - PD11 - Port D pull-down bit y (y=0..15) - 11 - 1 - - - PD10 - Port D pull-down bit y (y=0..15) - 10 - 1 - - - PD9 - Port D pull-down bit y (y=0..15) - 9 - 1 - - - PD8 - Port D pull-down bit y (y=0..15) - 8 - 1 - - - PD7 - Port D pull-down bit y (y=0..15) - 7 - 1 - - - PD6 - Port D pull-down bit y (y=0..15) - 6 - 1 - - - PD5 - Port D pull-down bit y (y=0..15) - 5 - 1 - - - PD4 - Port D pull-down bit y (y=0..15) - 4 - 1 - - - PD3 - Port D pull-down bit y (y=0..15) - 3 - 1 - - - PD2 - Port D pull-down bit y (y=0..15) - 2 - 1 - - - PD1 - Port D pull-down bit y (y=0..15) - 1 - 1 - - - PD0 - Port D pull-down bit y (y=0..15) - 0 - 1 - - - - - PUCRE - PUCRE - Power Port E pull-up control register - 0x40 - 0x20 - read-write - 0x00000000 - - - PU4 - Port E pull-up bit y (y=0..15) - 4 - 1 - - - PU3 - Port E pull-up bit y (y=0..15) - 3 - 1 - - - PU2 - Port E pull-up bit y (y=0..15) - 2 - 1 - - - PU1 - Port E pull-up bit y (y=0..15) - 1 - 1 - - - PU0 - Port E pull-up bit y (y=0..15) - 0 - 1 - - - - - PDCRE - PDCRE - Power Port E pull-down control register - 0x44 - 0x20 - read-write - 0x00000000 - - - PD4 - Port E pull-down bit y (y=0..15) - 4 - 1 - - - PD3 - Port E pull-down bit y (y=0..15) - 3 - 1 - - - PD2 - Port E pull-down bit y (y=0..15) - 2 - 1 - - - PD1 - Port E pull-down bit y (y=0..15) - 1 - 1 - - - PD0 - Port E pull-down bit y (y=0..15) - 0 - 1 - - - - - PUCRH - PUCRH - Power Port H pull-up control register - 0x58 - 0x20 - read-write - 0x00000000 - - - PU3 - Port H pull-up bit y (y=0..1) - 3 - 1 - - - PU1 - Port H pull-up bit y (y=0..1) - 1 - 1 - - - PU0 - Port H pull-up bit y (y=0..1) - 0 - 1 - - - - - PDCRH - PDCRH - Power Port H pull-down control register - 0x5C - 0x20 - read-write - 0x00000000 - - - PD3 - Port H pull-down bit y (y=0..1) - 3 - 1 - - - PD1 - Port H pull-down bit y (y=0..1) - 1 - 1 - - - PD0 - Port H pull-down bit y (y=0..1) - 0 - 1 - - - - - C2CR1 - C2CR1 - CPU2 Power control register 1 - 0x80 - 0x20 - read-write - 0x00000000 - - - EWKUP802 - 802.15.4 external wakeup signal - 15 - 1 - - - BLEEWKUP - BLE external wakeup signal - 14 - 1 - - - FPDS - Flash power down mode during LPSleep for CPU2 - 5 - 1 - - - FPDR - Flash power down mode during LPRun for CPU2 - 4 - 1 - - - LPMS - Low-power mode selection for CPU2 - 0 - 3 - - - - - C2CR3 - C2CR3 - CPU2 Power control register 3 - 0x84 - 0x20 - read-write - 0X00008000 - - - EIWUL - Enable internal wakeup line for CPU2 - 15 - 1 - - - APC - Apply pull-up and pull-down configuration for CPU2 - 12 - 1 - - - E802WUP - Enable 802.15.4 host wakeup interrupt for CPU2 - 10 - 1 - - - EBLEWUP - Enable BLE host wakeup interrupt for CPU2 - 9 - 1 - - - EWUP5 - Enable Wakeup pin WKUP5 for CPU2 - 4 - 1 - - - EWUP4 - Enable Wakeup pin WKUP4 for CPU2 - 3 - 1 - - - EWUP3 - Enable Wakeup pin WKUP3 for CPU2 - 2 - 1 - - - EWUP2 - Enable Wakeup pin WKUP2 for CPU2 - 1 - 1 - - - EWUP1 - Enable Wakeup pin WKUP1 for CPU2 - 0 - 1 - - - - - EXTSCR - EXTSCR - Power status clear register - 0x88 - 0x20 - 0x00000000 - - - C2DS - CPU2 deepsleep mode - 15 - 1 - read-only - - - C1DS - CPU1 deepsleep mode - 14 - 1 - read-only - - - CRPF - Critical Radio system phase - 13 - 1 - read-only - - - C2STOPF - System Stop flag for CPU2 - 11 - 1 - read-only - - - C2SBF - System Standby flag for CPU2 - 10 - 1 - read-only - - - C1STOPF - System Stop flag for CPU1 - 9 - 1 - read-only - - - C1SBF - System Standby flag for CPU1 - 8 - 1 - read-only - - - CCRPF - Clear Critical Radio system phase - 2 - 1 - write-only - - - C2CSSF - Clear CPU2 Stop Standby flags - 1 - 1 - write-only - - - C1CSSF - Clear CPU1 Stop Standby flags - 0 - 1 - write-only - - - - - - - SYSCFG_VREFBUF - SYSCFG_VREFBUF - SYSCFG_VREFBUF - 0x40010000 - - 0x0 - 0x200 - registers - - - - SYSCFG_MEMRMP - SYSCFG_MEMRMP - memory remap register - 0x0 - 0x20 - read-write - 0x00000000 - - - MEM_MODE - Memory mapping selection - 0 - 3 - - - - - SYSCFG_CFGR1 - SYSCFG_CFGR1 - configuration register 1 - 0x4 - 0x20 - read-write - 0x7C000001 - - - FPU_IE - Floating Point Unit interrupts enable bits - 26 - 6 - - - I2C3_FMP - I2C3 Fast-mode Plus driving capability activation - 22 - 1 - - - I2C1_FMP - I2C1 Fast-mode Plus driving capability activation - 20 - 1 - - - I2C_PB9_FMP - Fast-mode Plus (Fm+) driving capability activation on PB9 - 19 - 1 - - - I2C_PB8_FMP - Fast-mode Plus (Fm+) driving capability activation on PB8 - 18 - 1 - - - I2C_PB7_FMP - Fast-mode Plus (Fm+) driving capability activation on PB7 - 17 - 1 - - - I2C_PB6_FMP - Fast-mode Plus (Fm+) driving capability activation on PB6 - 16 - 1 - - - BOOSTEN - I/O analog switch voltage booster enable - 8 - 1 - - - - - SYSCFG_EXTICR1 - SYSCFG_EXTICR1 - external interrupt configuration register 1 - 0x8 - 0x20 - read-write - 0x00000000 - - - EXTI3 - EXTI 3 configuration bits - 12 - 3 - - - EXTI2 - EXTI 2 configuration bits - 8 - 3 - - - EXTI1 - EXTI 1 configuration bits - 4 - 3 - - - EXTI0 - EXTI 0 configuration bits - 0 - 3 - - - - - SYSCFG_EXTICR2 - SYSCFG_EXTICR2 - external interrupt configuration register 2 - 0xC - 0x20 - read-write - 0x00000000 - - - EXTI7 - EXTI 7 configuration bits - 12 - 3 - - - EXTI6 - EXTI 6 configuration bits - 8 - 3 - - - EXTI5 - EXTI 5 configuration bits - 4 - 3 - - - EXTI4 - EXTI 4 configuration bits - 0 - 3 - - - - - SYSCFG_EXTICR3 - SYSCFG_EXTICR3 - external interrupt configuration register 3 - 0x10 - 0x20 - read-write - 0x00000000 - - - EXTI11 - EXTI 11 configuration bits - 12 - 3 - - - EXTI10 - EXTI 10 configuration bits - 8 - 3 - - - EXTI9 - EXTI 9 configuration bits - 4 - 3 - - - EXTI8 - EXTI 8 configuration bits - 0 - 3 - - - - - SYSCFG_EXTICR4 - SYSCFG_EXTICR4 - external interrupt configuration register 4 - 0x14 - 0x20 - read-write - 0x00000000 - - - EXTI15 - EXTI15 configuration bits - 12 - 3 - - - EXTI14 - EXTI14 configuration bits - 8 - 3 - - - EXTI13 - EXTI13 configuration bits - 4 - 3 - - - EXTI12 - EXTI12 configuration bits - 0 - 3 - - - - - SYSCFG_SCSR - SYSCFG_SCSR - SCSR - 0x18 - 0x20 - 0x00000000 - - - SRAM2BSY - SRAM2 busy by erase operation - 1 - 1 - read-only - - - SRAM2ER - SRAM2 Erase - 0 - 1 - read-write - - - C2RFD - CPU2 SRAM fetch (execution) disable. - 31 - 1 - read-write - - - - - SYSCFG_CFGR2 - SYSCFG_CFGR2 - CFGR2 - 0x1C - 0x20 - 0x00000000 - - - SPF - SRAM2 parity error flag - 8 - 1 - read-write - - - ECCL - ECC Lock - 3 - 1 - write-only - - - PVDL - PVD lock enable bit - 2 - 1 - write-only - - - SPL - SRAM2 parity lock bit - 1 - 1 - write-only - - - CLL - Cortex-M4 LOCKUP (Hardfault) output enable bit - 0 - 1 - write-only - - - - - SYSCFG_SWPR - SYSCFG_SWPR - SRAM2 write protection register - 0x20 - 0x20 - write-only - 0x00000000 - - - P31WP - SRAM2 page 31 write protection - 31 - 1 - - - P30WP - P30WP - 30 - 1 - - - P29WP - P29WP - 29 - 1 - - - P28WP - P28WP - 28 - 1 - - - P27WP - P27WP - 27 - 1 - - - P26WP - P26WP - 26 - 1 - - - P25WP - P25WP - 25 - 1 - - - P24WP - P24WP - 24 - 1 - - - P23WP - P23WP - 23 - 1 - - - P22WP - P22WP - 22 - 1 - - - P21WP - P21WP - 21 - 1 - - - P20WP - P20WP - 20 - 1 - - - P19WP - P19WP - 19 - 1 - - - P18WP - P18WP - 18 - 1 - - - P17WP - P17WP - 17 - 1 - - - P16WP - P16WP - 16 - 1 - - - P15WP - P15WP - 15 - 1 - - - P14WP - P14WP - 14 - 1 - - - P13WP - P13WP - 13 - 1 - - - P12WP - P12WP - 12 - 1 - - - P11WP - P11WP - 11 - 1 - - - P10WP - P10WP - 10 - 1 - - - P9WP - P9WP - 9 - 1 - - - P8WP - P8WP - 8 - 1 - - - P7WP - P7WP - 7 - 1 - - - P6WP - P6WP - 6 - 1 - - - P5WP - P5WP - 5 - 1 - - - P4WP - P4WP - 4 - 1 - - - P3WP - P3WP - 3 - 1 - - - P2WP - P2WP - 2 - 1 - - - P1WP - P1WP - 1 - 1 - - - P0WP - P0WP - 0 - 1 - - - - - SYSCFG_SKR - SYSCFG_SKR - SKR - 0x24 - 0x20 - write-only - 0x00000000 - - - KEY - SRAM2 write protection key for software erase - 0 - 8 - - - - - SYSCFG_SWPR2 - SYSCFG_SWPR2 - SRAM2 write protection register 2 - 0x28 - 0x20 - write-only - 0x00000000 - - - P63WP - SRAM2 page 63 write protection - 31 - 1 - - - P62WP - P62WP - 30 - 1 - - - P61WP - P61WP - 29 - 1 - - - P60WP - P60WP - 28 - 1 - - - P59WP - P59WP - 27 - 1 - - - P58WP - P58WP - 26 - 1 - - - P57WP - P57WP - 25 - 1 - - - P56WP - P56WP - 24 - 1 - - - P55WP - P55WP - 23 - 1 - - - P54WP - P54WP - 22 - 1 - - - P53WP - P53WP - 21 - 1 - - - P52WP - P52WP - 20 - 1 - - - P51WP - P51WP - 19 - 1 - - - P50WP - P50WP - 18 - 1 - - - P49WP - P49WP - 17 - 1 - - - P48WP - P48WP - 16 - 1 - - - P47WP - P47WP - 15 - 1 - - - P46WP - P46WP - 14 - 1 - - - P45WP - P45WP - 13 - 1 - - - P44WP - P44WP - 12 - 1 - - - P43WP - P43WP - 11 - 1 - - - P42WP - P42WP - 10 - 1 - - - P41WP - P41WP - 9 - 1 - - - P40WP - P40WP - 8 - 1 - - - P39WP - P39WP - 7 - 1 - - - P38WP - P38WP - 6 - 1 - - - P37WP - P37WP - 5 - 1 - - - P36WP - P36WP - 4 - 1 - - - P35WP - P35WP - 3 - 1 - - - P34WP - P34WP - 2 - 1 - - - P33WP - P33WP - 1 - 1 - - - P32WP - P32WP - 0 - 1 - - - - - VREFBUF_CSR - VREFBUF_CSR - VREF control and status register - 0x30 - 0x20 - 0x00000002 - - - ENVR - Voltage reference buffer enable - 0 - 1 - read-write - - - HIZ - High impedance mode - 1 - 1 - read-write - - - VRS - Voltage reference scale - 2 - 1 - read-write - - - VRR - Voltage reference buffer ready - 3 - 1 - read-only - - - - - VREFBUF_CCR - VREFBUF_CCR - calibration control register - 0x34 - 0x20 - read-write - 0x00000000 - - - TRIM - Trimming code - 0 - 6 - - - - - SYSCFG_IMR1 - SYSCFG_IMR1 - CPU1 interrupt mask register 1 - 0x100 - 0x20 - read-write - 0x00000000 - - - TIM1IM - Peripheral TIM1 interrupt mask to CPU1 - 13 - 1 - - - TIM16IM - Peripheral TIM16 interrupt mask to CPU1 - 14 - 1 - - - TIM17IM - Peripheral TIM17 interrupt mask to CPU1 - 15 - 1 - - - EXIT5IM - Peripheral EXIT5 interrupt mask to CPU1 - 21 - 1 - - - EXIT6IM - Peripheral EXIT6 interrupt mask to CPU1 - 22 - 1 - - - EXIT7IM - Peripheral EXIT7 interrupt mask to CPU1 - 23 - 1 - - - EXIT8IM - Peripheral EXIT8 interrupt mask to CPU1 - 24 - 1 - - - EXIT9IM - Peripheral EXIT9 interrupt mask to CPU1 - 25 - 1 - - - EXIT10IM - Peripheral EXIT10 interrupt mask to CPU1 - 26 - 1 - - - EXIT11IM - Peripheral EXIT11 interrupt mask to CPU1 - 27 - 1 - - - EXIT12IM - Peripheral EXIT12 interrupt mask to CPU1 - 28 - 1 - - - EXIT13IM - Peripheral EXIT13 interrupt mask to CPU1 - 29 - 1 - - - EXIT14IM - Peripheral EXIT14 interrupt mask to CPU1 - 30 - 1 - - - EXIT15IM - Peripheral EXIT15 interrupt mask to CPU1 - 31 - 1 - - - - - SYSCFG_IMR2 - SYSCFG_IMR2 - CPU1 interrupt mask register 2 - 0x104 - 0x20 - read-write - 0x00000000 - - - PVM3IM - Peripheral PVM3 interrupt mask to CPU1 - 18 - 1 - - - PVM1IM - Peripheral PVM1 interrupt mask to CPU1 - 16 - 1 - - - PVDIM - Peripheral PVD interrupt mask to CPU1 - 20 - 1 - - - - - SYSCFG_C2IMR1 - SYSCFG_C2IMR1 - CPU2 interrupt mask register 1 - 0x108 - 0x20 - read-write - 0x00000000 - - - RTCSTAMP - Peripheral RTCSTAMP interrupt mask to CPU2 - 0 - 1 - - - RTCWKUP - Peripheral RTCWKUP interrupt mask to CPU2 - 3 - 1 - - - RTCALARM - Peripheral RTCALARM interrupt mask to CPU2 - 4 - 1 - - - RCC - Peripheral RCC interrupt mask to CPU2 - 5 - 1 - - - FLASH - Peripheral FLASH interrupt mask to CPU2 - 6 - 1 - - - PKA - Peripheral PKA interrupt mask to CPU2 - 8 - 1 - - - RNG - Peripheral RNG interrupt mask to CPU2 - 9 - 1 - - - AES1 - Peripheral AES1 interrupt mask to CPU2 - 10 - 1 - - - COMP - Peripheral COMP interrupt mask to CPU2 - 11 - 1 - - - ADC - Peripheral ADC interrupt mask to CPU2 - 12 - 1 - - - - - SYSCFG_C2IMR2 - SYSCFG_C2IMR2 - CPU2 interrupt mask register 1 - 0x10C - 0x20 - read-write - 0x00000000 - - - DMA1_CH1_IM - Peripheral DMA1 CH1 interrupt mask to CPU2 - 0 - 1 - - - DMA1_CH2_IM - Peripheral DMA1 CH2 interrupt mask to CPU2 - 1 - 1 - - - DMA1_CH3_IM - Peripheral DMA1 CH3 interrupt mask to CPU2 - 2 - 1 - - - DMA1_CH4_IM - Peripheral DMA1 CH4 interrupt mask to CPU2 - 3 - 1 - - - DMA1_CH5_IM - Peripheral DMA1 CH5 interrupt mask to CPU2 - 4 - 1 - - - DMA1_CH6_IM - Peripheral DMA1 CH6 interrupt mask to CPU2 - 5 - 1 - - - DMA1_CH7_IM - Peripheral DMA1 CH7 interrupt mask to CPU2 - 6 - 1 - - - DMA2_CH1_IM - Peripheral DMA2 CH1 interrupt mask to CPU1 - 8 - 1 - - - DMA2_CH2_IM - Peripheral DMA2 CH2 interrupt mask to CPU1 - 9 - 1 - - - DMA2_CH3_IM - Peripheral DMA2 CH3 interrupt mask to CPU1 - 10 - 1 - - - DMA2_CH4_IM - Peripheral DMA2 CH4 interrupt mask to CPU1 - 11 - 1 - - - DMA2_CH5_IM - Peripheral DMA2 CH5 interrupt mask to CPU1 - 12 - 1 - - - DMA2_CH6_IM - Peripheral DMA2 CH6 interrupt mask to CPU1 - 13 - 1 - - - DMA2_CH7_IM - Peripheral DMA2 CH7 interrupt mask to CPU1 - 14 - 1 - - - DMAM_UX1_IM - Peripheral DMAM UX1 interrupt mask to CPU1 - 15 - 1 - - - PVM1IM - Peripheral PVM1IM interrupt mask to CPU1 - 16 - 1 - - - PVM3IM - Peripheral PVM3IM interrupt mask to CPU1 - 18 - 1 - - - PVDIM - Peripheral PVDIM interrupt mask to CPU1 - 20 - 1 - - - TSCIM - Peripheral TSCIM interrupt mask to CPU1 - 21 - 1 - - - LCDIM - Peripheral LCDIM interrupt mask to CPU1 - 22 - 1 - - - - - SYSCFG_SIPCR - SYSCFG_SIPCR - secure IP control register - 0x110 - 0x20 - read-write - 0x00000000 - - - SAES1 - Enable AES1 KEY[7:0] security. - 0 - 1 - - - SAES2 - Enable AES2 security. - 1 - 1 - - - SPKA - Enable PKA security - 2 - 1 - - - SRNG - Enable True RNG security - 3 - 1 - - - - - - - COMP - Comparator instance 1 - COMP - 0x40010200 - - 0x0 - 0x9 - registers - - - COMP - COMP2 & COMP1 interrupt through - AIEC[21:20] - 22 - - - - COMP1_CSR - COMP1_CSR - Comparator control and status register - 0x0 - 0x20 - 0x00000000 - - - COMP1_EN - Comparator enable - 0 - 1 - read-write - - - COMP1_PWRMODE - Comparator power mode - 2 - 2 - read-write - - - COMP1_INMSEL - Comparator input minus selection - 4 - 3 - read-write - - - COMP1_INPSEL - Comparator input plus selection - 7 - 2 - read-write - - - COMP1_POLARITY - Comparator output polarity - 15 - 1 - read-write - - - COMP1_HYST - Comparator hysteresis - 16 - 2 - read-write - - - COMP1_BLANKING - Comparator blanking source - 18 - 3 - read-write - - - COMP1_BRGEN - Comparator voltage scaler enable - 22 - 1 - read-write - - - COMP1_SCALEN - Comparator scaler bridge enable - 23 - 1 - read-write - - - COMP1_INMESEL - Comparator input minus extended selection - 25 - 2 - read-write - - - COMP1_VALUE - Comparator output level - 30 - 1 - read-only - - - COMP1_LOCK - Comparator lock - 31 - 1 - read-write - - - - - COMP2_CSR - COMP2_CSR - Comparator 2 control and status register - 0x4 - 0x20 - 0x00000000 - - - COMP2_EN - Comparator 2 enable bit - 0 - 1 - read-write - - - COMP2_PWRMODE - Power Mode of the comparator 2 - 2 - 2 - read-write - - - COMP2_INMSEL - Comparator 2 input minus selection bits - 4 - 2 - read-write - - - COMP2_INPSEL - Comparator 1 input plus selection bit - 7 - 2 - read-write - - - COMP2_WINMODE - Windows mode selection bit - 9 - 1 - read-write - - - COMP2_POLARITY - Comparator 2 polarity selection bit - 15 - 1 - read-write - - - COMP2_HYST - Comparator 2 hysteresis selection bits - 16 - 2 - read-write - - - COMP2_BLANKING - Comparator 2 blanking source selection bits - 18 - 3 - read-write - - - COMP2_BRGEN - Scaler bridge enable - 22 - 1 - read-write - - - COMP2_SCALEN - Voltage scaler enable bit - 23 - 1 - read-write - - - COMP2_INMESEL - comparator 2 input minus extended selection bits. - 25 - 2 - read-write - - - COMP2_VALUE - Comparator 2 output status bit - 30 - 1 - read-only - - - COMP2_LOCK - CSR register lock bit - 31 - 1 - read-write - - - - - - - RNG - Random number generator - RNG - 0x58001000 - - 0x0 - 0x400 - registers - - - True_RNG - True random number generator - interrupt - 53 - - - - CR - CR - control register - 0x0 - 0x20 - read-write - 0x00000000 - - - RNGEN - Random number generator enable - 2 - 1 - - - IE - Interrupt enable - 3 - 1 - - - BYP - Bypass mode enable - 6 - 1 - - - - - SR - SR - status register - 0x4 - 0x20 - 0x00000000 - - - SEIS - Seed error interrupt status - 6 - 1 - read-write - - - CEIS - Clock error interrupt status - 5 - 1 - read-write - - - SECS - Seed error current status - 2 - 1 - read-only - - - CECS - Clock error current status - 1 - 1 - read-only - - - DRDY - Data ready - 0 - 1 - read-only - - - - - DR - DR - data register - 0x8 - 0x20 - read-only - 0x00000000 - - - RNDATA - Random data - 0 - 32 - - - - - - - AES1 - Advanced encryption standard hardware accelerator 1 - AES1 - 0x50060000 - - 0x0 - 0x400 - registers - - - AES1 - AES1 global interrupt - 51 - - - - CR - CR - control register - 0x0 - 0x20 - read-write - 0x00000000 - - - NPBLB - Number of padding bytes in last block of payload - 20 - 4 - - - KEYSIZE - Key size selection - 18 - 1 - - - CHMOD2 - AES chaining mode Bit2 - 16 - 1 - - - GCMPH - Used only for GCM, CCM and GMAC algorithms and has no effect when other algorithms are selected - 13 - 2 - - - DMAOUTEN - Enable DMA management of data output phase - 12 - 1 - - - DMAINEN - Enable DMA management of data input phase - 11 - 1 - - - ERRIE - Error interrupt enable - 10 - 1 - - - CCFIE - CCF flag interrupt enable - 9 - 1 - - - ERRC - Error clear - 8 - 1 - - - CCFC - Computation Complete Flag Clear - 7 - 1 - - - CHMOD10 - AES chaining mode Bit1 Bit0 - 5 - 2 - - - MODE - AES operating mode - 3 - 2 - - - DATATYPE - Data type selection (for data in and data out to/from the cryptographic block) - 1 - 2 - - - EN - AES enable - 0 - 1 - - - - - SR - SR - status register - 0x4 - 0x20 - read-only - 0x00000000 - - - BUSY - Busy flag - 3 - 1 - - - WRERR - Write error flag - 2 - 1 - - - RDERR - Read error flag - 1 - 1 - - - CCF - Computation complete flag - 0 - 1 - - - - - DINR - DINR - data input register - 0x8 - 0x20 - read-write - 0x00000000 - - - AES_DINR - Data Input Register - 0 - 32 - - - - - DOUTR - DOUTR - data output register - 0xC - 0x20 - read-only - 0x00000000 - - - AES_DOUTR - Data output register - 0 - 32 - - - - - KEYR0 - KEYR0 - key register 0 - 0x10 - 0x20 - read-write - 0x00000000 - - - AES_KEYR0 - Data Output Register (LSB key [31:0]) - 0 - 32 - - - - - KEYR1 - KEYR1 - key register 1 - 0x14 - 0x20 - read-write - 0x00000000 - - - AES_KEYR1 - AES key register (key [63:32]) - 0 - 32 - - - - - KEYR2 - KEYR2 - key register 2 - 0x18 - 0x20 - read-write - 0x00000000 - - - AES_KEYR2 - AES key register (key [95:64]) - 0 - 32 - - - - - KEYR3 - KEYR3 - key register 3 - 0x1C - 0x20 - read-write - 0x00000000 - - - AES_KEYR3 - AES key register (MSB key [127:96]) - 0 - 32 - - - - - IVR0 - IVR0 - initialization vector register 0 - 0x20 - 0x20 - read-write - 0x00000000 - - - AES_IVR0 - initialization vector register (LSB IVR [31:0]) - 0 - 32 - - - - - IVR1 - IVR1 - initialization vector register 1 - 0x24 - 0x20 - read-write - 0x00000000 - - - AES_IVR1 - Initialization Vector Register (IVR [63:32]) - 0 - 32 - - - - - IVR2 - IVR2 - initialization vector register 2 - 0x28 - 0x20 - read-write - 0x00000000 - - - AES_IVR2 - Initialization Vector Register (IVR [95:64]) - 0 - 32 - - - - - IVR3 - IVR3 - initialization vector register 3 - 0x2C - 0x20 - read-write - 0x00000000 - - - AES_IVR3 - Initialization Vector Register (MSB IVR [127:96]) - 0 - 32 - - - - - KEYR4 - KEYR4 - key register 4 - 0x30 - 0x20 - read-write - 0x00000000 - - - AES_KEYR4 - AES key register (MSB key [159:128]) - 0 - 32 - - - - - KEYR5 - KEYR5 - key register 5 - 0x34 - 0x20 - read-write - 0x00000000 - - - AES_KEYR5 - AES key register (MSB key [191:160]) - 0 - 32 - - - - - KEYR6 - KEYR6 - key register 6 - 0x38 - 0x20 - read-write - 0x00000000 - - - AES_KEYR6 - AES key register (MSB key [223:192]) - 0 - 32 - - - - - KEYR7 - KEYR7 - key register 7 - 0x3C - 0x20 - read-write - 0x00000000 - - - AES_KEYR7 - AES key register (MSB key [255:224]) - 0 - 32 - - - - - SUSP0R - SUSP0R - AES suspend register 0 - 0x40 - 0x20 - read-write - 0x00000000 - - - AES_SUSP0R - AES suspend register 0 - 0 - 32 - - - - - SUSP1R - SUSP1R - AES suspend register 1 - 0x44 - 0x20 - read-write - 0x00000000 - - - AES_SUSP1R - AES suspend register 1 - 0 - 32 - - - - - SUSP2R - SUSP2R - AES suspend register 2 - 0x48 - 0x20 - read-write - 0x00000000 - - - AES_SUSP2R - AES suspend register 2 - 0 - 32 - - - - - SUSP3R - SUSP3R - AES suspend register 3 - 0x4C - 0x20 - read-write - 0x00000000 - - - AES_SUSP3R - AES suspend register 3 - 0 - 32 - - - - - SUSP4R - SUSP4R - AES suspend register 4 - 0x50 - 0x20 - read-write - 0x00000000 - - - AES_SUSP4R - AES suspend register 4 - 0 - 32 - - - - - SUSP5R - SUSP5R - AES suspend register 5 - 0x54 - 0x20 - read-write - 0x00000000 - - - AES_SUSP5R - AES suspend register 5 - 0 - 32 - - - - - SUSP6R - SUSP6R - AES suspend register 6 - 0x58 - 0x20 - read-write - 0x00000000 - - - AES_SUSP6R - AES suspend register 6 - 0 - 32 - - - - - SUSP7R - SUSP7R - AES suspend register 7 - 0x5C - 0x20 - read-write - 0x00000000 - - - AES_SUSP7R - AES suspend register 7 - 0 - 32 - - - - - HWCFR - HWCFR - AES hardware configuration register - 0x3F0 - 0x20 - read-only - 0x00000002 - - - CFG4 - HW Generic 4 - 12 - 4 - - - CFG3 - HW Generic 3 - 8 - 4 - - - CFG2 - HW Generic 2 - 4 - 4 - - - CFG1 - HW Generic 1 - 0 - 4 - - - - - VERR - VERR - AES version register - 0x3F4 - 0x20 - read-only - 0x00000010 - - - MAJREV - Major revision - 4 - 4 - - - MINREV - Minor revision - 0 - 4 - - - - - IPIDR - IPIDR - AES identification register - 0x3F8 - 0x20 - read-only - 0x00170023 - - - ID - Identification code - 0 - 32 - - - - - SIDR - SIDR - AES size ID register - 0x3FC - 0x20 - read-only - 0xA3C5DD01 - - - ID - Size Identification code - 0 - 32 - - - - - - - AES2 - Advanced encryption standard hardware accelerator 1 - AES1 - 0x58001800 - - 0x0 - 0x400 - registers - - - AES2 - AES2 global interrupt - 52 - - - - CR - CR - control register - 0x0 - 0x20 - read-write - 0x00000000 - - - NPBLB - Number of padding bytes in last block of payload - 20 - 4 - - - KEYSIZE - Key size selection - 18 - 1 - - - CHMOD2 - AES chaining mode Bit2 - 16 - 1 - - - GCMPH - Used only for GCM, CCM and GMAC algorithms and has no effect when other algorithms are selected - 13 - 2 - - - DMAOUTEN - Enable DMA management of data output phase - 12 - 1 - - - DMAINEN - Enable DMA management of data input phase - 11 - 1 - - - ERRIE - Error interrupt enable - 10 - 1 - - - CCFIE - CCF flag interrupt enable - 9 - 1 - - - ERRC - Error clear - 8 - 1 - - - CCFC - Computation Complete Flag Clear - 7 - 1 - - - CHMOD10 - AES chaining mode Bit1 Bit0 - 5 - 2 - - - MODE - AES operating mode - 3 - 2 - - - DATATYPE - Data type selection (for data in and data out to/from the cryptographic block) - 1 - 2 - - - EN - AES enable - 0 - 1 - - - - - SR - SR - status register - 0x4 - 0x20 - read-only - 0x00000000 - - - BUSY - Busy flag - 3 - 1 - - - WRERR - Write error flag - 2 - 1 - - - RDERR - Read error flag - 1 - 1 - - - CCF - Computation complete flag - 0 - 1 - - - - - DINR - DINR - data input register - 0x8 - 0x20 - read-write - 0x00000000 - - - AES_DINR - Data Input Register - 0 - 32 - - - - - DOUTR - DOUTR - data output register - 0xC - 0x20 - read-only - 0x00000000 - - - AES_DOUTR - Data output register - 0 - 32 - - - - - KEYR0 - KEYR0 - key register 0 - 0x10 - 0x20 - read-write - 0x00000000 - - - AES_KEYR0 - Data Output Register (LSB key [31:0]) - 0 - 32 - - - - - KEYR1 - KEYR1 - key register 1 - 0x14 - 0x20 - read-write - 0x00000000 - - - AES_KEYR1 - AES key register (key [63:32]) - 0 - 32 - - - - - KEYR2 - KEYR2 - key register 2 - 0x18 - 0x20 - read-write - 0x00000000 - - - AES_KEYR2 - AES key register (key [95:64]) - 0 - 32 - - - - - KEYR3 - KEYR3 - key register 3 - 0x1C - 0x20 - read-write - 0x00000000 - - - AES_KEYR3 - AES key register (MSB key [127:96]) - 0 - 32 - - - - - IVR0 - IVR0 - initialization vector register 0 - 0x20 - 0x20 - read-write - 0x00000000 - - - AES_IVR0 - initialization vector register (LSB IVR [31:0]) - 0 - 32 - - - - - IVR1 - IVR1 - initialization vector register 1 - 0x24 - 0x20 - read-write - 0x00000000 - - - AES_IVR1 - Initialization Vector Register (IVR [63:32]) - 0 - 32 - - - - - IVR2 - IVR2 - initialization vector register 2 - 0x28 - 0x20 - read-write - 0x00000000 - - - AES_IVR2 - Initialization Vector Register (IVR [95:64]) - 0 - 32 - - - - - IVR3 - IVR3 - initialization vector register 3 - 0x2C - 0x20 - read-write - 0x00000000 - - - AES_IVR3 - Initialization Vector Register (MSB IVR [127:96]) - 0 - 32 - - - - - KEYR4 - KEYR4 - key register 4 - 0x30 - 0x20 - read-write - 0x00000000 - - - AES_KEYR4 - AES key register (MSB key [159:128]) - 0 - 32 - - - - - KEYR5 - KEYR5 - key register 5 - 0x34 - 0x20 - read-write - 0x00000000 - - - AES_KEYR5 - AES key register (MSB key [191:160]) - 0 - 32 - - - - - KEYR6 - KEYR6 - key register 6 - 0x38 - 0x20 - read-write - 0x00000000 - - - AES_KEYR6 - AES key register (MSB key [223:192]) - 0 - 32 - - - - - KEYR7 - KEYR7 - key register 7 - 0x3C - 0x20 - read-write - 0x00000000 - - - AES_KEYR7 - AES key register (MSB key [255:224]) - 0 - 32 - - - - - SUSP0R - SUSP0R - AES suspend register 0 - 0x40 - 0x20 - read-write - 0x00000000 - - - AES_SUSP0R - AES suspend register 0 - 0 - 32 - - - - - SUSP1R - SUSP1R - AES suspend register 1 - 0x44 - 0x20 - read-write - 0x00000000 - - - AES_SUSP1R - AES suspend register 1 - 0 - 32 - - - - - SUSP2R - SUSP2R - AES suspend register 2 - 0x48 - 0x20 - read-write - 0x00000000 - - - AES_SUSP2R - AES suspend register 2 - 0 - 32 - - - - - SUSP3R - SUSP3R - AES suspend register 3 - 0x4C - 0x20 - read-write - 0x00000000 - - - AES_SUSP3R - AES suspend register 3 - 0 - 32 - - - - - SUSP4R - SUSP4R - AES suspend register 4 - 0x50 - 0x20 - read-write - 0x00000000 - - - AES_SUSP4R - AES suspend register 4 - 0 - 32 - - - - - SUSP5R - SUSP5R - AES suspend register 5 - 0x54 - 0x20 - read-write - 0x00000000 - - - AES_SUSP5R - AES suspend register 5 - 0 - 32 - - - - - SUSP6R - SUSP6R - AES suspend register 6 - 0x58 - 0x20 - read-write - 0x00000000 - - - AES_SUSP6R - AES suspend register 6 - 0 - 32 - - - - - SUSP7R - SUSP7R - AES suspend register 7 - 0x5C - 0x20 - read-write - 0x00000000 - - - AES_SUSP7R - AES suspend register 7 - 0 - 32 - - - - - HWCFR - HWCFR - AES hardware configuration register - 0x60 - 0x20 - read-only - 0x00000002 - - - CFG4 - HW Generic 4 - 12 - 4 - - - CFG3 - HW Generic 3 - 8 - 4 - - - CFG2 - HW Generic 2 - 4 - 4 - - - CFG1 - HW Generic 1 - 0 - 4 - - - - - VERR - VERR - AES version register - 0x64 - 0x20 - read-only - 0x00000010 - - - MAJREV - Major revision - 4 - 4 - - - MINREV - Minor revision - 0 - 4 - - - - - IPIDR - IPIDR - AES identification register - 0x68 - 0x20 - read-only - 0x00170023 - - - ID - Identification code - 0 - 32 - - - - - SIDR - SIDR - AES size ID register - 0x6C - 0x20 - read-only - 0x00170023 - - - ID - Size Identification code - 0 - 32 - - - - - - - HSEM - HSEM - Hardware_Semaphore - 0x58001400 - - 0x0 - 0x400 - registers - - - HSEM - Semaphore interrupt 0 to CPU1 - 46 - - - - R0 - R0 - Semaphore 0 register - 0x0 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R1 - R1 - Semaphore 1 register - 0x4 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R2 - R2 - Semaphore 2 register - 0x8 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R3 - R3 - Semaphore 3 register - 0xC - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R4 - R4 - Semaphore 4 register - 0x10 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R5 - R5 - Semaphore 5 register - 0x14 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R6 - R6 - Semaphore 6 register - 0x18 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R7 - R7 - Semaphore 7 register - 0x1C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R8 - R8 - Semaphore 8 register - 0x20 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R9 - R9 - Semaphore 9 register - 0x24 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R10 - R10 - Semaphore 10 register - 0x28 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R11 - R11 - Semaphore 11 register - 0x2C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R12 - R12 - Semaphore 12 register - 0x30 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R13 - R13 - Semaphore 13 register - 0x34 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R14 - R14 - Semaphore 14 register - 0x38 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R15 - R15 - Semaphore 15 register - 0x3C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R16 - R16 - Semaphore 16 register - 0x40 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R17 - R17 - Semaphore 17 register - 0x44 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R18 - R18 - Semaphore 18 register - 0x48 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R19 - R19 - Semaphore 19 register - 0x4C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R20 - R20 - Semaphore 20 register - 0x50 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R21 - R21 - Semaphore 21 register - 0x54 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R22 - R22 - Semaphore 22 register - 0x58 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R23 - R23 - Semaphore 23 register - 0x5C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R24 - R24 - Semaphore 24 register - 0x60 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R25 - R25 - Semaphore 25 register - 0x64 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R26 - R26 - Semaphore 26 register - 0x68 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R27 - R27 - Semaphore 27 register - 0x6C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R28 - R28 - Semaphore 28 register - 0x70 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R29 - R29 - Semaphore 29 register - 0x74 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R30 - R30 - Semaphore 30 register - 0x78 - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - R31 - R31 - Semaphore 31 register - 0x7C - 0x20 - read-write - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR0 - RLR0 - Semaphore 0 read lock register - 0x80 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR1 - RLR1 - Semaphore 1 read lock register - 0x84 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR2 - RLR2 - Semaphore 2 read lock register - 0x88 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR3 - RLR3 - Semaphore 3 read lock register - 0x8C - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR4 - RLR4 - Semaphore 4 read lock read lock register - 0x90 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR5 - RLR5 - Semaphore 5 read lock register - 0x94 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR6 - RLR6 - Semaphore 6 read lock register - 0x98 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR7 - RLR7 - Semaphore 7 read lock register - 0x9C - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR8 - RLR8 - Semaphore 8 read lock register - 0xA0 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR9 - RLR9 - Semaphore 9 read lock register - 0xA4 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR10 - RLR10 - Semaphore 10 read lock register - 0xA8 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR11 - RLR11 - Semaphore 11 read lock register - 0xAC - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR12 - RLR12 - Semaphore 12 read lock register - 0xB0 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR13 - RLR13 - Semaphore 13 read lock register - 0xB4 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR14 - RLR14 - Semaphore 14 read lock register - 0xB8 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR15 - RLR15 - Semaphore 15 read lock register - 0xBC - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR16 - RLR16 - Semaphore 16 read lock register - 0xC0 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR17 - RLR17 - Semaphore 17 read lock register - 0xC4 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR18 - RLR18 - Semaphore 18 read lock register - 0xC8 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR19 - RLR19 - Semaphore 19 read lock register - 0xCC - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR20 - RLR20 - Semaphore 20 read lock register - 0xD0 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR21 - RLR21 - Semaphore 21 read lock register - 0xD4 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR22 - RLR22 - Semaphore 22 read lock register - 0xD8 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR23 - RLR23 - Semaphore 23 read lock register - 0xDC - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR24 - RLR24 - Semaphore 24 read lock register - 0xE0 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR25 - RLR25 - Semaphore 25 read lock register - 0xE4 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR26 - RLR26 - Semaphore 26 read lock register - 0xE8 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR27 - RLR27 - Semaphore 27 read lock register - 0xEC - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR28 - RLR28 - Semaphore 28 read lock register - 0xF0 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR29 - RLR29 - Semaphore 29 read lock register - 0xF4 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR30 - RLR30 - Semaphore 30 read lock register - 0xF8 - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - RLR31 - RLR31 - Semaphore 31 read lock register - 0xFC - 0x20 - read-only - 0x00000000 - - - LOCK - lock indication - 31 - 1 - - - COREID - Semaphore CoreID - 8 - 4 - - - PROCID - Semaphore ProcessID - 0 - 8 - - - - - CR - CR - Semaphore Clear register - 0x140 - 0x20 - read-write - 0x00000000 - - - KEY - Semaphore clear Key - 16 - 16 - - - COREID - CoreID of semaphore to be cleared - 8 - 4 - - - - - KEYR - KEYR - Interrupt clear register - 0x144 - 0x20 - read-write - 0x00000000 - - - KEY - Semaphore Clear Key - 16 - 16 - - - - - HWCFGR2 - HWCFGR2 - Semaphore hardware configuration register 2 - 0x3EC - 0x20 - read-only - 0x00000084 - - - MASTERID4 - Hardware Configuration valid bus masters ID4 - 12 - 4 - - - MASTERID3 - Hardware Configuration valid bus masters ID3 - 8 - 4 - - - MASTERID2 - Hardware Configuration valid bus masters ID2 - 4 - 4 - - - MASTERID1 - Hardware Configuration valid bus masters ID1 - 0 - 4 - - - - - HWCFGR1 - HWCFGR1 - Semaphore hardware configuration register 1 - 0x3F0 - 0x20 - read-only - 0x00000220 - - - NBINT - Hardware Configuration number of interrupts supported number of master IDs - 8 - 4 - - - NBSEM - Hardware Configuration number of semaphores - 0 - 8 - - - - - VERR - VERR - HSEM version register - 0x3F4 - 0x20 - read-only - 0x00000020 - - - MAJREV - Major Revision - 4 - 4 - - - MINREV - Minor Revision - 0 - 4 - - - - - IPIDR - IPIDR - HSEM indentification register - 0x3F8 - 0x20 - read-only - 0x00100072 - - - ID - Identification Code - 0 - 32 - - - - - SIDR - SIDR - HSEM size indentification register - 0x3FC - 0x20 - read-only - 0xA3C5DD01 - - - SID - Size Identification Code - 0 - 32 - - - - - C1IER0 - C1IER0 - HSEM Interrupt enable register - 0x100 - 0x20 - read-write - 0x00000000 - - - ISEm - CPU(n) semaphore m enable bit - 0 - 32 - - - - - C1ICR - C1ICR - HSEM Interrupt clear register - 0x104 - 0x20 - read-write - 0x00000000 - - - ISCm - CPU(n) semaphore m clear bit - 0 - 32 - - - - - C1ISR - C1ISR - HSEM Interrupt status register - 0x108 - 0x20 - read-only - 0x00000000 - - - ISFm - CPU(n) semaphore m status bit before enable (mask) - 0 - 32 - - - - - C1MISR - C1MISR - HSEM Masked interrupt status register - 0x10C - 0x20 - read-only - 0x00000000 - - - MISFm - masked CPU(n) semaphore m status bit after enable (mask). - 0 - 32 - - - - - C2IER0 - C2IER0 - HSEM Interrupt enable register - 0x110 - 0x20 - read-write - 0x00000000 - - - ISEm - CPU(2) semaphore m enable bit. - 0 - 32 - - - - - C2ICR - C2ICR - HSEM Interrupt clear register - 0x114 - 0x20 - read-write - 0x00000000 - - - ISCm - CPU(2) semaphore m clear bit - 0 - 32 - - - - - C2ISR - C2ISR - HSEM Interrupt status register - 0x118 - 0x20 - read-only - 0x00000000 - - - ISFm - CPU(2) semaphore m status bit before enable (mask). - 0 - 32 - - - - - C2MISR - C2MISR - HSEM Masked interrupt status register - 0x11C - 0x20 - read-only - 0x00000000 - - - MISFm - masked CPU(2) semaphore m status bit after enable (mask). - 0 - 32 - - - - - - - ADC - Analog to Digital Converter instance 1 - ADC - 0x50040000 - - 0x0 - 0x400 - registers - - - ADC1 - ADC1 global interrupt - 18 - - - - ISR - ISR - ADC interrupt and status register - 0x0 - 0x20 - read-write - 0x00000000 - - - JQOVF - ADC group injected contexts queue overflow flag - 10 - 1 - - - AWD3 - ADC analog watchdog 3 flag - 9 - 1 - - - AWD2 - ADC analog watchdog 2 flag - 8 - 1 - - - AWD1 - ADC analog watchdog 1 flag - 7 - 1 - - - JEOS - ADC group injected end of sequence conversions flag - 6 - 1 - - - JEOC - ADC group injected end of unitary conversion flag - 5 - 1 - - - OVR - ADC group regular overrun flag - 4 - 1 - - - EOS - ADC group regular end of sequence conversions flag - 3 - 1 - - - EOC - ADC group regular end of unitary conversion flag - 2 - 1 - - - EOSMP - ADC group regular end of sampling flag - 1 - 1 - - - ADRDY - ADC ready flag - 0 - 1 - - - - - IER - IER - ADC interrupt enable register - 0x4 - 0x20 - read-write - 0x00000000 - - - JQOVFIE - ADC group injected contexts queue overflow interrupt - 10 - 1 - - - AWD3IE - ADC analog watchdog 3 interrupt - 9 - 1 - - - AWD2IE - ADC analog watchdog 2 interrupt - 8 - 1 - - - AWD1IE - ADC analog watchdog 1 interrupt - 7 - 1 - - - JEOSIE - ADC group injected end of sequence conversions interrupt - 6 - 1 - - - JEOCIE - ADC group injected end of unitary conversion interrupt - 5 - 1 - - - OVRIE - ADC group regular overrun interrupt - 4 - 1 - - - EOSIE - ADC group regular end of sequence conversions interrupt - 3 - 1 - - - EOCIE - ADC group regular end of unitary conversion interrupt - 2 - 1 - - - EOSMPIE - ADC group regular end of sampling interrupt - 1 - 1 - - - ADRDYIE - ADC ready interrupt - 0 - 1 - - - - - CR - CR - ADC control register - 0x8 - 0x20 - read-write - 0x00000000 - - - ADCAL - ADC calibration - 31 - 1 - - - ADCALDIF - ADC differential mode for calibration - 30 - 1 - - - DEEPPWD - ADC deep power down enable - 29 - 1 - - - ADVREGEN - ADC voltage regulator enable - 28 - 1 - - - JADSTP - ADC group injected conversion stop - 5 - 1 - - - ADSTP - ADC group regular conversion stop - 4 - 1 - - - JADSTART - ADC group injected conversion start - 3 - 1 - - - ADSTART - ADC group regular conversion start - 2 - 1 - - - ADDIS - ADC disable - 1 - 1 - - - ADEN - ADC enable - 0 - 1 - - - - - CFGR - CFGR - ADC configuration register 1 - 0xC - 0x20 - read-write - 0x80000000 - - - JQDIS - ADC group injected contexts queue disable - 31 - 1 - - - AWDCH1CH - ADC analog watchdog 1 monitored channel selection - 26 - 5 - - - JAUTO - ADC group injected automatic trigger mode - 25 - 1 - - - JAWD1EN - ADC analog watchdog 1 enable on scope ADC group injected - 24 - 1 - - - AWD1EN - ADC analog watchdog 1 enable on scope ADC group regular - 23 - 1 - - - AWD1SGL - ADC analog watchdog 1 monitoring a single channel or all channels - 22 - 1 - - - JQM - ADC group injected contexts queue mode - 21 - 1 - - - JDISCEN - ADC group injected sequencer discontinuous mode - 20 - 1 - - - DISCNUM - ADC group regular sequencer discontinuous number of ranks - 17 - 3 - - - DISCEN - ADC group regular sequencer discontinuous mode - 16 - 1 - - - AUTDLY - ADC low power auto wait - 14 - 1 - - - CONT - ADC group regular continuous conversion mode - 13 - 1 - - - OVRMOD - ADC group regular overrun configuration - 12 - 1 - - - EXTEN - ADC group regular external trigger polarity - 10 - 2 - - - EXTSEL - ADC group regular external trigger source - 6 - 4 - - - ALIGN - ADC data alignement - 5 - 1 - - - RES - ADC data resolution - 3 - 2 - - - DMACFG - ADC DMA transfer configuration - 1 - 1 - - - DMAEN - ADC DMA transfer enable - 0 - 1 - - - - - CFGR2 - CFGR2 - ADC configuration register 2 - 0x10 - 0x20 - read-write - 0x00000000 - - - ROVSM - ADC oversampling mode managing interlaced conversions of ADC group regular and group injected - 10 - 1 - - - TOVS - ADC oversampling discontinuous mode (triggered mode) for ADC group regular - 9 - 1 - - - OVSS - ADC oversampling shift - 5 - 4 - - - OVSR - ADC oversampling ratio - 2 - 3 - - - JOVSE - ADC oversampler enable on scope ADC group injected - 1 - 1 - - - ROVSE - ADC oversampler enable on scope ADC group regular - 0 - 1 - - - - - SMPR1 - SMPR1 - ADC sampling time register 1 - 0x14 - 0x20 - read-write - 0x00000000 - - - SMP9 - ADC channel 9 sampling time selection - 27 - 3 - - - SMP8 - ADC channel 8 sampling time selection - 24 - 3 - - - SMP7 - ADC channel 7 sampling time selection - 21 - 3 - - - SMP6 - ADC channel 6 sampling time selection - 18 - 3 - - - SMP5 - ADC channel 5 sampling time selection - 15 - 3 - - - SMP4 - ADC channel 4 sampling time selection - 12 - 3 - - - SMP3 - ADC channel 3 sampling time selection - 9 - 3 - - - SMP2 - ADC channel 2 sampling time selection - 6 - 3 - - - SMP1 - ADC channel 1 sampling time selection - 3 - 3 - - - - - SMPR2 - SMPR2 - ADC sampling time register 2 - 0x18 - 0x20 - read-write - 0x00000000 - - - SMP18 - ADC channel 18 sampling time selection - 24 - 3 - - - SMP17 - ADC channel 17 sampling time selection - 21 - 3 - - - SMP16 - ADC channel 16 sampling time selection - 18 - 3 - - - SMP15 - ADC channel 15 sampling time selection - 15 - 3 - - - SMP14 - ADC channel 14 sampling time selection - 12 - 3 - - - SMP13 - ADC channel 13 sampling time selection - 9 - 3 - - - SMP12 - ADC channel 12 sampling time selection - 6 - 3 - - - SMP11 - ADC channel 11 sampling time selection - 3 - 3 - - - SMP10 - ADC channel 10 sampling time selection - 0 - 3 - - - - - TR1 - TR1 - ADC analog watchdog 1 threshold register - 0x20 - 0x20 - read-write - 0x0FFF0000 - - - HT1 - ADC analog watchdog 1 threshold high - 16 - 12 - - - LT1 - ADC analog watchdog 1 threshold low - 0 - 12 - - - - - TR2 - TR2 - ADC analog watchdog 2 threshold register - 0x24 - 0x20 - read-write - 0x0FFF0000 - - - HT2 - ADC analog watchdog 2 threshold high - 16 - 8 - - - LT2 - ADC analog watchdog 2 threshold low - 0 - 8 - - - - - TR3 - TR3 - ADC analog watchdog 3 threshold register - 0x28 - 0x20 - read-write - 0x0FFF0000 - - - HT3 - ADC analog watchdog 3 threshold high - 16 - 8 - - - LT3 - ADC analog watchdog 3 threshold low - 0 - 8 - - - - - SQR1 - SQR1 - ADC group regular sequencer ranks register 1 - 0x30 - 0x20 - read-write - 0x00000000 - - - SQ4 - ADC group regular sequencer rank 4 - 24 - 5 - - - SQ3 - ADC group regular sequencer rank 3 - 18 - 5 - - - SQ2 - ADC group regular sequencer rank 2 - 12 - 5 - - - SQ1 - ADC group regular sequencer rank 1 - 6 - 5 - - - L3 - L3 - 0 - 4 - - - - - SQR2 - SQR2 - ADC group regular sequencer ranks register 2 - 0x34 - 0x20 - read-write - 0x00000000 - - - SQ9 - ADC group regular sequencer rank 9 - 24 - 5 - - - SQ8 - ADC group regular sequencer rank 8 - 18 - 5 - - - SQ7 - ADC group regular sequencer rank 7 - 12 - 5 - - - SQ6 - ADC group regular sequencer rank 6 - 6 - 5 - - - SQ5 - ADC group regular sequencer rank 5 - 0 - 5 - - - - - SQR3 - SQR3 - ADC group regular sequencer ranks register 3 - 0x38 - 0x20 - read-write - 0x00000000 - - - SQ14 - ADC group regular sequencer rank 14 - 24 - 5 - - - SQ13 - ADC group regular sequencer rank 13 - 18 - 5 - - - SQ12 - ADC group regular sequencer rank 12 - 12 - 5 - - - SQ11 - ADC group regular sequencer rank 11 - 6 - 5 - - - SQ10 - ADC group regular sequencer rank 10 - 0 - 5 - - - - - SQR4 - SQR4 - ADC group regular sequencer ranks register 4 - 0x3C - 0x20 - read-write - 0x00000000 - - - SQ16 - ADC group regular sequencer rank 16 - 6 - 5 - - - SQ15 - ADC group regular sequencer rank 15 - 0 - 5 - - - - - DR - DR - ADC group regular conversion data register - 0x40 - 0x20 - 0x00000000 - - - RDATA_0_6 - Regular Data converted 0_6 - 0 - 6 - read-write - - - RDATA_7_15 - 15 - 7 - 9 - read-only - - - - - JSQR - JSQR - ADC group injected sequencer register - 0x4C - 0x20 - read-write - 0x00000000 - - - JSQ4 - ADC group injected sequencer rank 4 - 26 - 5 - - - JSQ3 - ADC group injected sequencer rank 3 - 20 - 5 - - - JSQ2 - ADC group injected sequencer rank 2 - 14 - 5 - - - JSQ1 - ADC group injected sequencer rank 1 - 8 - 5 - - - JEXTEN - ADC group injected external trigger polarity - 6 - 2 - - - JEXTSEL - ADC group injected external trigger source - 2 - 4 - - - JL - ADC group injected sequencer scan length - 0 - 2 - - - - - OFR1 - OFR1 - ADC offset number 1 register - 0x60 - 0x20 - read-write - 0x00000000 - - - OFFSET1_EN - ADC offset number 1 enable - 31 - 1 - - - OFFSET1_CH - ADC offset number 1 channel selection - 26 - 5 - - - OFFSET1 - ADC offset number 1 offset level - 0 - 12 - - - - - OFR2 - OFR2 - ADC offset number 2 register - 0x64 - 0x20 - read-write - 0x00000000 - - - OFFSET2_EN - ADC offset number 2 enable - 31 - 1 - - - OFFSET2_CH - ADC offset number 2 channel selection - 26 - 5 - - - OFFSET2 - ADC offset number 2 offset level - 0 - 12 - - - - - OFR3 - OFR3 - ADC offset number 3 register - 0x68 - 0x20 - read-write - 0x00000000 - - - OFFSET3_EN - ADC offset number 3 enable - 31 - 1 - - - OFFSET3_CH - ADC offset number 3 channel selection - 26 - 5 - - - OFFSET3 - ADC offset number 3 offset level - 0 - 12 - - - - - OFR4 - OFR4 - ADC offset number 4 register - 0x6C - 0x20 - read-write - 0x00000000 - - - OFFSET4_EN - ADC offset number 4 enable - 31 - 1 - - - OFFSET4_CH - ADC offset number 4 channel selection - 26 - 5 - - - OFFSET4 - ADC offset number 4 offset level - 0 - 12 - - - - - JDR1 - JDR1 - ADC group injected sequencer rank 1 register - 0x80 - 0x20 - read-only - 0x00000000 - - - JDATA1 - ADC group injected sequencer rank 1 conversion data - 0 - 16 - - - - - JDR2 - JDR2 - ADC group injected sequencer rank 2 register - 0x84 - 0x20 - read-only - 0x00000000 - - - JDATA2 - ADC group injected sequencer rank 2 conversion data - 0 - 16 - - - - - JDR3 - JDR3 - ADC group injected sequencer rank 3 register - 0x88 - 0x20 - read-only - 0x00000000 - - - JDATA3 - ADC group injected sequencer rank 3 conversion data - 0 - 16 - - - - - JDR4 - JDR4 - ADC group injected sequencer rank 4 register - 0x8C - 0x20 - read-only - 0x00000000 - - - JDATA4 - ADC group injected sequencer rank 4 conversion data - 0 - 16 - - - - - AWD2CR - AWD2CR - ADC analog watchdog 2 configuration register - 0xA0 - 0x20 - read-write - 0x00000000 - - - AWD2CH - ADC analog watchdog 2 monitored channel selection - 0 - 19 - - - - - AWD3CR - AWD3CR - ADC analog watchdog 3 configuration register - 0xA4 - 0x20 - read-write - 0x00000000 - - - AWD3CH - ADC analog watchdog 3 monitored channel selection - 0 - 19 - - - - - DIFSEL - DIFSEL - ADC channel differential or single-ended mode selection register - 0xB0 - 0x20 - 0x00000000 - - - DIFSEL_0 - ADC channel differential or single-ended mode for channel 0 - 0 - 1 - read-only - - - DIFSEL_1_15 - ADC channel differential or single-ended mode for channels 1 to 15 - 1 - 15 - read-write - - - DIFSEL_16_18 - ADC channel differential or single-ended mode for channels 18 to 16 - 16 - 3 - read-only - - - - - CALFACT - CALFACT - ADC calibration factors register - 0xB4 - 0x20 - read-write - 0x00000000 - - - CALFACT_D - ADC calibration factor in differential mode - 16 - 7 - - - CALFACT_S - ADC calibration factor in single-ended mode - 0 - 7 - - - - - CCR - CCR - ADC common control register - 0x308 - 0x20 - read-write - 0x00000000 - - - VBATEN - VBAT enable - 24 - 1 - - - TSEN - Temperature sensor enable - 23 - 1 - - - VREFEN - VREFEN - 22 - 1 - - - PRESC - ADC prescaler - 18 - 4 - - - CKMODE - ADC clock mode - 16 - 2 - - - - - - - GPIOA - General-purpose I/Os - GPIO - 0x48000000 - - 0x0 - 0x400 - registers - - - - MODER - MODER - GPIO port mode register - 0x0 - 0x20 - read-write - 0xABFFFFFF - - - MODER15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - MODER14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - MODER13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - MODER12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - MODER11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - MODER10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - MODER9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - MODER8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - MODER7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - MODER6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - MODER5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - MODER4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - MODER3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - MODER2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - MODER1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - MODER0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - OTYPER - OTYPER - GPIO port output type register - 0x4 - 0x20 - read-write - 0x00000000 - - - OT15 - Port x configuration bits (y = 0..15) - 15 - 1 - - - OT14 - Port x configuration bits (y = 0..15) - 14 - 1 - - - OT13 - Port x configuration bits (y = 0..15) - 13 - 1 - - - OT12 - Port x configuration bits (y = 0..15) - 12 - 1 - - - OT11 - Port x configuration bits (y = 0..15) - 11 - 1 - - - OT10 - Port x configuration bits (y = 0..15) - 10 - 1 - - - OT9 - Port x configuration bits (y = 0..15) - 9 - 1 - - - OT8 - Port x configuration bits (y = 0..15) - 8 - 1 - - - OT7 - Port x configuration bits (y = 0..15) - 7 - 1 - - - OT6 - Port x configuration bits (y = 0..15) - 6 - 1 - - - OT5 - Port x configuration bits (y = 0..15) - 5 - 1 - - - OT4 - Port x configuration bits (y = 0..15) - 4 - 1 - - - OT3 - Port x configuration bits (y = 0..15) - 3 - 1 - - - OT2 - Port x configuration bits (y = 0..15) - 2 - 1 - - - OT1 - Port x configuration bits (y = 0..15) - 1 - 1 - - - OT0 - Port x configuration bits (y = 0..15) - 0 - 1 - - - - - OSPEEDR - OSPEEDR - GPIO port output speed register - 0x8 - 0x20 - read-write - 0x0C000000 - - - OSPEEDR15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - OSPEEDR14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - OSPEEDR13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - OSPEEDR12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - OSPEEDR11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - OSPEEDR10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - OSPEEDR9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - OSPEEDR8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - OSPEEDR7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - OSPEEDR6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - OSPEEDR5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - OSPEEDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - OSPEEDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - OSPEEDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - OSPEEDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - OSPEEDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - PUPDR - PUPDR - GPIO port pull-up/pull-down register - 0xC - 0x20 - read-write - 0x64000000 - - - PUPDR15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - PUPDR14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - PUPDR13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - PUPDR12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - PUPDR11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - PUPDR10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - PUPDR9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - PUPDR8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - PUPDR7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - PUPDR6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - PUPDR5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - PUPDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - PUPDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - PUPDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - PUPDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - PUPDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - IDR - IDR - GPIO port input data register - 0x10 - 0x20 - read-only - 0x00000000 - - - IDR15 - Port input data (y = 0..15) - 15 - 1 - - - IDR14 - Port input data (y = 0..15) - 14 - 1 - - - IDR13 - Port input data (y = 0..15) - 13 - 1 - - - IDR12 - Port input data (y = 0..15) - 12 - 1 - - - IDR11 - Port input data (y = 0..15) - 11 - 1 - - - IDR10 - Port input data (y = 0..15) - 10 - 1 - - - IDR9 - Port input data (y = 0..15) - 9 - 1 - - - IDR8 - Port input data (y = 0..15) - 8 - 1 - - - IDR7 - Port input data (y = 0..15) - 7 - 1 - - - IDR6 - Port input data (y = 0..15) - 6 - 1 - - - IDR5 - Port input data (y = 0..15) - 5 - 1 - - - IDR4 - Port input data (y = 0..15) - 4 - 1 - - - IDR3 - Port input data (y = 0..15) - 3 - 1 - - - IDR2 - Port input data (y = 0..15) - 2 - 1 - - - IDR1 - Port input data (y = 0..15) - 1 - 1 - - - IDR0 - Port input data (y = 0..15) - 0 - 1 - - - - - ODR - ODR - GPIO port output data register - 0x14 - 0x20 - read-write - 0x00000000 - - - ODR15 - Port output data (y = 0..15) - 15 - 1 - - - ODR14 - Port output data (y = 0..15) - 14 - 1 - - - ODR13 - Port output data (y = 0..15) - 13 - 1 - - - ODR12 - Port output data (y = 0..15) - 12 - 1 - - - ODR11 - Port output data (y = 0..15) - 11 - 1 - - - ODR10 - Port output data (y = 0..15) - 10 - 1 - - - ODR9 - Port output data (y = 0..15) - 9 - 1 - - - ODR8 - Port output data (y = 0..15) - 8 - 1 - - - ODR7 - Port output data (y = 0..15) - 7 - 1 - - - ODR6 - Port output data (y = 0..15) - 6 - 1 - - - ODR5 - Port output data (y = 0..15) - 5 - 1 - - - ODR4 - Port output data (y = 0..15) - 4 - 1 - - - ODR3 - Port output data (y = 0..15) - 3 - 1 - - - ODR2 - Port output data (y = 0..15) - 2 - 1 - - - ODR1 - Port output data (y = 0..15) - 1 - 1 - - - ODR0 - Port output data (y = 0..15) - 0 - 1 - - - - - BSRR - BSRR - GPIO port bit set/reset register - 0x18 - 0x20 - write-only - 0x00000000 - - - BR15 - Port x reset bit y (y = 0..15) - 31 - 1 - - - BR14 - Port x reset bit y (y = 0..15) - 30 - 1 - - - BR13 - Port x reset bit y (y = 0..15) - 29 - 1 - - - BR12 - Port x reset bit y (y = 0..15) - 28 - 1 - - - BR11 - Port x reset bit y (y = 0..15) - 27 - 1 - - - BR10 - Port x reset bit y (y = 0..15) - 26 - 1 - - - BR9 - Port x reset bit y (y = 0..15) - 25 - 1 - - - BR8 - Port x reset bit y (y = 0..15) - 24 - 1 - - - BR7 - Port x reset bit y (y = 0..15) - 23 - 1 - - - BR6 - Port x reset bit y (y = 0..15) - 22 - 1 - - - BR5 - Port x reset bit y (y = 0..15) - 21 - 1 - - - BR4 - Port x reset bit y (y = 0..15) - 20 - 1 - - - BR3 - Port x reset bit y (y = 0..15) - 19 - 1 - - - BR2 - Port x reset bit y (y = 0..15) - 18 - 1 - - - BR1 - Port x reset bit y (y = 0..15) - 17 - 1 - - - BR0 - Port x set bit y (y= 0..15) - 16 - 1 - - - BS15 - Port x set bit y (y= 0..15) - 15 - 1 - - - BS14 - Port x set bit y (y= 0..15) - 14 - 1 - - - BS13 - Port x set bit y (y= 0..15) - 13 - 1 - - - BS12 - Port x set bit y (y= 0..15) - 12 - 1 - - - BS11 - Port x set bit y (y= 0..15) - 11 - 1 - - - BS10 - Port x set bit y (y= 0..15) - 10 - 1 - - - BS9 - Port x set bit y (y= 0..15) - 9 - 1 - - - BS8 - Port x set bit y (y= 0..15) - 8 - 1 - - - BS7 - Port x set bit y (y= 0..15) - 7 - 1 - - - BS6 - Port x set bit y (y= 0..15) - 6 - 1 - - - BS5 - Port x set bit y (y= 0..15) - 5 - 1 - - - BS4 - Port x set bit y (y= 0..15) - 4 - 1 - - - BS3 - Port x set bit y (y= 0..15) - 3 - 1 - - - BS2 - Port x set bit y (y= 0..15) - 2 - 1 - - - BS1 - Port x set bit y (y= 0..15) - 1 - 1 - - - BS0 - Port x set bit y (y= 0..15) - 0 - 1 - - - - - LCKR - LCKR - GPIO port configuration lock register - 0x1C - 0x20 - read-write - 0x00000000 - - - LCKK - Port x lock bit y (y= 0..15) - 16 - 1 - - - LCK15 - Port x lock bit y (y= 0..15) - 15 - 1 - - - LCK14 - Port x lock bit y (y= 0..15) - 14 - 1 - - - LCK13 - Port x lock bit y (y= 0..15) - 13 - 1 - - - LCK12 - Port x lock bit y (y= 0..15) - 12 - 1 - - - LCK11 - Port x lock bit y (y= 0..15) - 11 - 1 - - - LCK10 - Port x lock bit y (y= 0..15) - 10 - 1 - - - LCK9 - Port x lock bit y (y= 0..15) - 9 - 1 - - - LCK8 - Port x lock bit y (y= 0..15) - 8 - 1 - - - LCK7 - Port x lock bit y (y= 0..15) - 7 - 1 - - - LCK6 - Port x lock bit y (y= 0..15) - 6 - 1 - - - LCK5 - Port x lock bit y (y= 0..15) - 5 - 1 - - - LCK4 - Port x lock bit y (y= 0..15) - 4 - 1 - - - LCK3 - Port x lock bit y (y= 0..15) - 3 - 1 - - - LCK2 - Port x lock bit y (y= 0..15) - 2 - 1 - - - LCK1 - Port x lock bit y (y= 0..15) - 1 - 1 - - - LCK0 - Port x lock bit y (y= 0..15) - 0 - 1 - - - - - AFRL - AFRL - GPIO alternate function low register - 0x20 - 0x20 - read-write - 0x00000000 - - - AFSEL7 - Alternate function selection for port x bit y (y = 0..7) - 28 - 4 - - - AFSEL6 - Alternate function selection for port x bit y (y = 0..7) - 24 - 4 - - - AFSEL5 - Alternate function selection for port x bit y (y = 0..7) - 20 - 4 - - - AFSEL4 - Alternate function selection for port x bit y (y = 0..7) - 16 - 4 - - - AFSEL3 - Alternate function selection for port x bit y (y = 0..7) - 12 - 4 - - - AFSEL2 - Alternate function selection for port x bit y (y = 0..7) - 8 - 4 - - - AFSEL1 - Alternate function selection for port x bit y (y = 0..7) - 4 - 4 - - - AFSEL0 - Alternate function selection for port x bit y (y = 0..7) - 0 - 4 - - - - - AFRH - AFRH - GPIO alternate function high register - 0x24 - 0x20 - read-write - 0x00000000 - - - AFSEL15 - Alternate function selection for port x bit y (y = 8..15) - 28 - 4 - - - AFSEL14 - Alternate function selection for port x bit y (y = 8..15) - 24 - 4 - - - AFSEL13 - Alternate function selection for port x bit y (y = 8..15) - 20 - 4 - - - AFSEL12 - Alternate function selection for port x bit y (y = 8..15) - 16 - 4 - - - AFSEL11 - Alternate function selection for port x bit y (y = 8..15) - 12 - 4 - - - AFSEL10 - Alternate function selection for port x bit y (y = 8..15) - 8 - 4 - - - AFSEL9 - Alternate function selection for port x bit y (y = 8..15) - 4 - 4 - - - AFSEL8 - Alternate function selection for port x bit y (y = 8..15) - 0 - 4 - - - - - BRR - BRR - port bit reset register - 0x28 - 0x20 - write-only - 0x00000000 - - - BR0 - Port Reset bit - 0 - 1 - - - BR1 - Port Reset bit - 1 - 1 - - - BR2 - Port Reset bit - 2 - 1 - - - BR3 - Port Reset bit - 3 - 1 - - - BR4 - Port Reset bit - 4 - 1 - - - BR5 - Port Reset bit - 5 - 1 - - - BR6 - Port Reset bit - 6 - 1 - - - BR7 - Port Reset bit - 7 - 1 - - - BR8 - Port Reset bit - 8 - 1 - - - BR9 - Port Reset bit - 9 - 1 - - - BR10 - Port Reset bit - 10 - 1 - - - BR11 - Port Reset bit - 11 - 1 - - - BR12 - Port Reset bit - 12 - 1 - - - BR13 - Port Reset bit - 13 - 1 - - - BR14 - Port Reset bit - 14 - 1 - - - BR15 - Port Reset bit - 15 - 1 - - - - - - - GPIOB - General-purpose I/Os - GPIO - 0x48000400 - - 0x0 - 0x400 - registers - - - - MODER - MODER - GPIO port mode register - 0x0 - 0x20 - read-write - 0xFFFFFEBF - - - MODER15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - MODER14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - MODER13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - MODER12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - MODER11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - MODER10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - MODER9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - MODER8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - MODER7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - MODER6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - MODER5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - MODER4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - MODER3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - MODER2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - MODER1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - MODER0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - OTYPER - OTYPER - GPIO port output type register - 0x4 - 0x20 - read-write - 0x00000000 - - - OT15 - Port x configuration bits (y = 0..15) - 15 - 1 - - - OT14 - Port x configuration bits (y = 0..15) - 14 - 1 - - - OT13 - Port x configuration bits (y = 0..15) - 13 - 1 - - - OT12 - Port x configuration bits (y = 0..15) - 12 - 1 - - - OT11 - Port x configuration bits (y = 0..15) - 11 - 1 - - - OT10 - Port x configuration bits (y = 0..15) - 10 - 1 - - - OT9 - Port x configuration bits (y = 0..15) - 9 - 1 - - - OT8 - Port x configuration bits (y = 0..15) - 8 - 1 - - - OT7 - Port x configuration bits (y = 0..15) - 7 - 1 - - - OT6 - Port x configuration bits (y = 0..15) - 6 - 1 - - - OT5 - Port x configuration bits (y = 0..15) - 5 - 1 - - - OT4 - Port x configuration bits (y = 0..15) - 4 - 1 - - - OT3 - Port x configuration bits (y = 0..15) - 3 - 1 - - - OT2 - Port x configuration bits (y = 0..15) - 2 - 1 - - - OT1 - Port x configuration bits (y = 0..15) - 1 - 1 - - - OT0 - Port x configuration bits (y = 0..15) - 0 - 1 - - - - - OSPEEDR - OSPEEDR - GPIO port output speed register - 0x8 - 0x20 - read-write - 0x000000C0 - - - OSPEEDR15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - OSPEEDR14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - OSPEEDR13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - OSPEEDR12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - OSPEEDR11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - OSPEEDR10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - OSPEEDR9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - OSPEEDR8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - OSPEEDR7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - OSPEEDR6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - OSPEEDR5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - OSPEEDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - OSPEEDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - OSPEEDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - OSPEEDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - OSPEEDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - PUPDR - PUPDR - GPIO port pull-up/pull-down register - 0xC - 0x20 - read-write - 0x00000100 - - - PUPDR15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - PUPDR14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - PUPDR13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - PUPDR12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - PUPDR11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - PUPDR10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - PUPDR9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - PUPDR8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - PUPDR7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - PUPDR6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - PUPDR5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - PUPDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - PUPDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - PUPDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - PUPDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - PUPDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - IDR - IDR - GPIO port input data register - 0x10 - 0x20 - read-only - 0x00000000 - - - IDR15 - Port input data (y = 0..15) - 15 - 1 - - - IDR14 - Port input data (y = 0..15) - 14 - 1 - - - IDR13 - Port input data (y = 0..15) - 13 - 1 - - - IDR12 - Port input data (y = 0..15) - 12 - 1 - - - IDR11 - Port input data (y = 0..15) - 11 - 1 - - - IDR10 - Port input data (y = 0..15) - 10 - 1 - - - IDR9 - Port input data (y = 0..15) - 9 - 1 - - - IDR8 - Port input data (y = 0..15) - 8 - 1 - - - IDR7 - Port input data (y = 0..15) - 7 - 1 - - - IDR6 - Port input data (y = 0..15) - 6 - 1 - - - IDR5 - Port input data (y = 0..15) - 5 - 1 - - - IDR4 - Port input data (y = 0..15) - 4 - 1 - - - IDR3 - Port input data (y = 0..15) - 3 - 1 - - - IDR2 - Port input data (y = 0..15) - 2 - 1 - - - IDR1 - Port input data (y = 0..15) - 1 - 1 - - - IDR0 - Port input data (y = 0..15) - 0 - 1 - - - - - ODR - ODR - GPIO port output data register - 0x14 - 0x20 - read-write - 0x00000000 - - - ODR15 - Port output data (y = 0..15) - 15 - 1 - - - ODR14 - Port output data (y = 0..15) - 14 - 1 - - - ODR13 - Port output data (y = 0..15) - 13 - 1 - - - ODR12 - Port output data (y = 0..15) - 12 - 1 - - - ODR11 - Port output data (y = 0..15) - 11 - 1 - - - ODR10 - Port output data (y = 0..15) - 10 - 1 - - - ODR9 - Port output data (y = 0..15) - 9 - 1 - - - ODR8 - Port output data (y = 0..15) - 8 - 1 - - - ODR7 - Port output data (y = 0..15) - 7 - 1 - - - ODR6 - Port output data (y = 0..15) - 6 - 1 - - - ODR5 - Port output data (y = 0..15) - 5 - 1 - - - ODR4 - Port output data (y = 0..15) - 4 - 1 - - - ODR3 - Port output data (y = 0..15) - 3 - 1 - - - ODR2 - Port output data (y = 0..15) - 2 - 1 - - - ODR1 - Port output data (y = 0..15) - 1 - 1 - - - ODR0 - Port output data (y = 0..15) - 0 - 1 - - - - - BSRR - BSRR - GPIO port bit set/reset register - 0x18 - 0x20 - write-only - 0x00000000 - - - BR15 - Port x reset bit y (y = 0..15) - 31 - 1 - - - BR14 - Port x reset bit y (y = 0..15) - 30 - 1 - - - BR13 - Port x reset bit y (y = 0..15) - 29 - 1 - - - BR12 - Port x reset bit y (y = 0..15) - 28 - 1 - - - BR11 - Port x reset bit y (y = 0..15) - 27 - 1 - - - BR10 - Port x reset bit y (y = 0..15) - 26 - 1 - - - BR9 - Port x reset bit y (y = 0..15) - 25 - 1 - - - BR8 - Port x reset bit y (y = 0..15) - 24 - 1 - - - BR7 - Port x reset bit y (y = 0..15) - 23 - 1 - - - BR6 - Port x reset bit y (y = 0..15) - 22 - 1 - - - BR5 - Port x reset bit y (y = 0..15) - 21 - 1 - - - BR4 - Port x reset bit y (y = 0..15) - 20 - 1 - - - BR3 - Port x reset bit y (y = 0..15) - 19 - 1 - - - BR2 - Port x reset bit y (y = 0..15) - 18 - 1 - - - BR1 - Port x reset bit y (y = 0..15) - 17 - 1 - - - BR0 - Port x set bit y (y= 0..15) - 16 - 1 - - - BS15 - Port x set bit y (y= 0..15) - 15 - 1 - - - BS14 - Port x set bit y (y= 0..15) - 14 - 1 - - - BS13 - Port x set bit y (y= 0..15) - 13 - 1 - - - BS12 - Port x set bit y (y= 0..15) - 12 - 1 - - - BS11 - Port x set bit y (y= 0..15) - 11 - 1 - - - BS10 - Port x set bit y (y= 0..15) - 10 - 1 - - - BS9 - Port x set bit y (y= 0..15) - 9 - 1 - - - BS8 - Port x set bit y (y= 0..15) - 8 - 1 - - - BS7 - Port x set bit y (y= 0..15) - 7 - 1 - - - BS6 - Port x set bit y (y= 0..15) - 6 - 1 - - - BS5 - Port x set bit y (y= 0..15) - 5 - 1 - - - BS4 - Port x set bit y (y= 0..15) - 4 - 1 - - - BS3 - Port x set bit y (y= 0..15) - 3 - 1 - - - BS2 - Port x set bit y (y= 0..15) - 2 - 1 - - - BS1 - Port x set bit y (y= 0..15) - 1 - 1 - - - BS0 - Port x set bit y (y= 0..15) - 0 - 1 - - - - - LCKR - LCKR - GPIO port configuration lock register - 0x1C - 0x20 - read-write - 0x00000000 - - - LCKK - Port x lock bit y (y= 0..15) - 16 - 1 - - - LCK15 - Port x lock bit y (y= 0..15) - 15 - 1 - - - LCK14 - Port x lock bit y (y= 0..15) - 14 - 1 - - - LCK13 - Port x lock bit y (y= 0..15) - 13 - 1 - - - LCK12 - Port x lock bit y (y= 0..15) - 12 - 1 - - - LCK11 - Port x lock bit y (y= 0..15) - 11 - 1 - - - LCK10 - Port x lock bit y (y= 0..15) - 10 - 1 - - - LCK9 - Port x lock bit y (y= 0..15) - 9 - 1 - - - LCK8 - Port x lock bit y (y= 0..15) - 8 - 1 - - - LCK7 - Port x lock bit y (y= 0..15) - 7 - 1 - - - LCK6 - Port x lock bit y (y= 0..15) - 6 - 1 - - - LCK5 - Port x lock bit y (y= 0..15) - 5 - 1 - - - LCK4 - Port x lock bit y (y= 0..15) - 4 - 1 - - - LCK3 - Port x lock bit y (y= 0..15) - 3 - 1 - - - LCK2 - Port x lock bit y (y= 0..15) - 2 - 1 - - - LCK1 - Port x lock bit y (y= 0..15) - 1 - 1 - - - LCK0 - Port x lock bit y (y= 0..15) - 0 - 1 - - - - - AFRL - AFRL - GPIO alternate function low register - 0x20 - 0x20 - read-write - 0x00000000 - - - AFSEL7 - Alternate function selection for port x bit y (y = 0..7) - 28 - 4 - - - AFSEL6 - Alternate function selection for port x bit y (y = 0..7) - 24 - 4 - - - AFSEL5 - Alternate function selection for port x bit y (y = 0..7) - 20 - 4 - - - AFSEL4 - Alternate function selection for port x bit y (y = 0..7) - 16 - 4 - - - AFSEL3 - Alternate function selection for port x bit y (y = 0..7) - 12 - 4 - - - AFSEL2 - Alternate function selection for port x bit y (y = 0..7) - 8 - 4 - - - AFSEL1 - Alternate function selection for port x bit y (y = 0..7) - 4 - 4 - - - AFSEL0 - Alternate function selection for port x bit y (y = 0..7) - 0 - 4 - - - - - AFRH - AFRH - GPIO alternate function high register - 0x24 - 0x20 - read-write - 0x00000000 - - - AFSEL15 - Alternate function selection for port x bit y (y = 8..15) - 28 - 4 - - - AFSEL14 - Alternate function selection for port x bit y (y = 8..15) - 24 - 4 - - - AFSEL13 - Alternate function selection for port x bit y (y = 8..15) - 20 - 4 - - - AFSEL12 - Alternate function selection for port x bit y (y = 8..15) - 16 - 4 - - - AFSEL11 - Alternate function selection for port x bit y (y = 8..15) - 12 - 4 - - - AFSEL10 - Alternate function selection for port x bit y (y = 8..15) - 8 - 4 - - - AFSEL9 - Alternate function selection for port x bit y (y = 8..15) - 4 - 4 - - - AFSEL8 - Alternate function selection for port x bit y (y = 8..15) - 0 - 4 - - - - - BRR - BRR - port bit reset register - 0x28 - 0x20 - write-only - 0x00000000 - - - BR0 - Port Reset bit - 0 - 1 - - - BR1 - Port Reset bit - 1 - 1 - - - BR2 - Port Reset bit - 2 - 1 - - - BR3 - Port Reset bit - 3 - 1 - - - BR4 - Port Reset bit - 4 - 1 - - - BR5 - Port Reset bit - 5 - 1 - - - BR6 - Port Reset bit - 6 - 1 - - - BR7 - Port Reset bit - 7 - 1 - - - BR8 - Port Reset bit - 8 - 1 - - - BR9 - Port Reset bit - 9 - 1 - - - BR10 - Port Reset bit - 10 - 1 - - - BR11 - Port Reset bit - 11 - 1 - - - BR12 - Port Reset bit - 12 - 1 - - - BR13 - Port Reset bit - 13 - 1 - - - BR14 - Port Reset bit - 14 - 1 - - - BR15 - Port Reset bit - 15 - 1 - - - - - - - GPIOC - General-purpose I/Os - GPIO - 0x48000800 - - 0x0 - 0x400 - registers - - - - MODER - MODER - GPIO port mode register - 0x0 - 0x20 - read-write - 0xFFFFFFFF - - - MODER15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - MODER14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - MODER13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - MODER12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - MODER11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - MODER10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - MODER9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - MODER8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - MODER7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - MODER6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - MODER5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - MODER4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - MODER3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - MODER2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - MODER1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - MODER0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - OTYPER - OTYPER - GPIO port output type register - 0x4 - 0x20 - read-write - 0x00000000 - - - OT15 - Port x configuration bits (y = 0..15) - 15 - 1 - - - OT14 - Port x configuration bits (y = 0..15) - 14 - 1 - - - OT13 - Port x configuration bits (y = 0..15) - 13 - 1 - - - OT12 - Port x configuration bits (y = 0..15) - 12 - 1 - - - OT11 - Port x configuration bits (y = 0..15) - 11 - 1 - - - OT10 - Port x configuration bits (y = 0..15) - 10 - 1 - - - OT9 - Port x configuration bits (y = 0..15) - 9 - 1 - - - OT8 - Port x configuration bits (y = 0..15) - 8 - 1 - - - OT7 - Port x configuration bits (y = 0..15) - 7 - 1 - - - OT6 - Port x configuration bits (y = 0..15) - 6 - 1 - - - OT5 - Port x configuration bits (y = 0..15) - 5 - 1 - - - OT4 - Port x configuration bits (y = 0..15) - 4 - 1 - - - OT3 - Port x configuration bits (y = 0..15) - 3 - 1 - - - OT2 - Port x configuration bits (y = 0..15) - 2 - 1 - - - OT1 - Port x configuration bits (y = 0..15) - 1 - 1 - - - OT0 - Port x configuration bits (y = 0..15) - 0 - 1 - - - - - OSPEEDR - OSPEEDR - GPIO port output speed register - 0x8 - 0x20 - read-write - 0x000000C0 - - - OSPEEDR15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - OSPEEDR14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - OSPEEDR13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - OSPEEDR12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - OSPEEDR11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - OSPEEDR10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - OSPEEDR9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - OSPEEDR8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - OSPEEDR7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - OSPEEDR6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - OSPEEDR5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - OSPEEDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - OSPEEDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - OSPEEDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - OSPEEDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - OSPEEDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - PUPDR - PUPDR - GPIO port pull-up/pull-down register - 0xC - 0x20 - read-write - 0x00000100 - - - PUPDR15 - Port x configuration bits (y = 0..15) - 30 - 2 - - - PUPDR14 - Port x configuration bits (y = 0..15) - 28 - 2 - - - PUPDR13 - Port x configuration bits (y = 0..15) - 26 - 2 - - - PUPDR12 - Port x configuration bits (y = 0..15) - 24 - 2 - - - PUPDR11 - Port x configuration bits (y = 0..15) - 22 - 2 - - - PUPDR10 - Port x configuration bits (y = 0..15) - 20 - 2 - - - PUPDR9 - Port x configuration bits (y = 0..15) - 18 - 2 - - - PUPDR8 - Port x configuration bits (y = 0..15) - 16 - 2 - - - PUPDR7 - Port x configuration bits (y = 0..15) - 14 - 2 - - - PUPDR6 - Port x configuration bits (y = 0..15) - 12 - 2 - - - PUPDR5 - Port x configuration bits (y = 0..15) - 10 - 2 - - - PUPDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - PUPDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - PUPDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - PUPDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - PUPDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - IDR - IDR - GPIO port input data register - 0x10 - 0x20 - read-only - 0x00000000 - - - IDR15 - Port input data (y = 0..15) - 15 - 1 - - - IDR14 - Port input data (y = 0..15) - 14 - 1 - - - IDR13 - Port input data (y = 0..15) - 13 - 1 - - - IDR12 - Port input data (y = 0..15) - 12 - 1 - - - IDR11 - Port input data (y = 0..15) - 11 - 1 - - - IDR10 - Port input data (y = 0..15) - 10 - 1 - - - IDR9 - Port input data (y = 0..15) - 9 - 1 - - - IDR8 - Port input data (y = 0..15) - 8 - 1 - - - IDR7 - Port input data (y = 0..15) - 7 - 1 - - - IDR6 - Port input data (y = 0..15) - 6 - 1 - - - IDR5 - Port input data (y = 0..15) - 5 - 1 - - - IDR4 - Port input data (y = 0..15) - 4 - 1 - - - IDR3 - Port input data (y = 0..15) - 3 - 1 - - - IDR2 - Port input data (y = 0..15) - 2 - 1 - - - IDR1 - Port input data (y = 0..15) - 1 - 1 - - - IDR0 - Port input data (y = 0..15) - 0 - 1 - - - - - ODR - ODR - GPIO port output data register - 0x14 - 0x20 - read-write - 0x00000000 - - - ODR15 - Port output data (y = 0..15) - 15 - 1 - - - ODR14 - Port output data (y = 0..15) - 14 - 1 - - - ODR13 - Port output data (y = 0..15) - 13 - 1 - - - ODR12 - Port output data (y = 0..15) - 12 - 1 - - - ODR11 - Port output data (y = 0..15) - 11 - 1 - - - ODR10 - Port output data (y = 0..15) - 10 - 1 - - - ODR9 - Port output data (y = 0..15) - 9 - 1 - - - ODR8 - Port output data (y = 0..15) - 8 - 1 - - - ODR7 - Port output data (y = 0..15) - 7 - 1 - - - ODR6 - Port output data (y = 0..15) - 6 - 1 - - - ODR5 - Port output data (y = 0..15) - 5 - 1 - - - ODR4 - Port output data (y = 0..15) - 4 - 1 - - - ODR3 - Port output data (y = 0..15) - 3 - 1 - - - ODR2 - Port output data (y = 0..15) - 2 - 1 - - - ODR1 - Port output data (y = 0..15) - 1 - 1 - - - ODR0 - Port output data (y = 0..15) - 0 - 1 - - - - - BSRR - BSRR - GPIO port bit set/reset register - 0x18 - 0x20 - write-only - 0x00000000 - - - BR15 - Port x reset bit y (y = 0..15) - 31 - 1 - - - BR14 - Port x reset bit y (y = 0..15) - 30 - 1 - - - BR13 - Port x reset bit y (y = 0..15) - 29 - 1 - - - BR12 - Port x reset bit y (y = 0..15) - 28 - 1 - - - BR11 - Port x reset bit y (y = 0..15) - 27 - 1 - - - BR10 - Port x reset bit y (y = 0..15) - 26 - 1 - - - BR9 - Port x reset bit y (y = 0..15) - 25 - 1 - - - BR8 - Port x reset bit y (y = 0..15) - 24 - 1 - - - BR7 - Port x reset bit y (y = 0..15) - 23 - 1 - - - BR6 - Port x reset bit y (y = 0..15) - 22 - 1 - - - BR5 - Port x reset bit y (y = 0..15) - 21 - 1 - - - BR4 - Port x reset bit y (y = 0..15) - 20 - 1 - - - BR3 - Port x reset bit y (y = 0..15) - 19 - 1 - - - BR2 - Port x reset bit y (y = 0..15) - 18 - 1 - - - BR1 - Port x reset bit y (y = 0..15) - 17 - 1 - - - BR0 - Port x set bit y (y= 0..15) - 16 - 1 - - - BS15 - Port x set bit y (y= 0..15) - 15 - 1 - - - BS14 - Port x set bit y (y= 0..15) - 14 - 1 - - - BS13 - Port x set bit y (y= 0..15) - 13 - 1 - - - BS12 - Port x set bit y (y= 0..15) - 12 - 1 - - - BS11 - Port x set bit y (y= 0..15) - 11 - 1 - - - BS10 - Port x set bit y (y= 0..15) - 10 - 1 - - - BS9 - Port x set bit y (y= 0..15) - 9 - 1 - - - BS8 - Port x set bit y (y= 0..15) - 8 - 1 - - - BS7 - Port x set bit y (y= 0..15) - 7 - 1 - - - BS6 - Port x set bit y (y= 0..15) - 6 - 1 - - - BS5 - Port x set bit y (y= 0..15) - 5 - 1 - - - BS4 - Port x set bit y (y= 0..15) - 4 - 1 - - - BS3 - Port x set bit y (y= 0..15) - 3 - 1 - - - BS2 - Port x set bit y (y= 0..15) - 2 - 1 - - - BS1 - Port x set bit y (y= 0..15) - 1 - 1 - - - BS0 - Port x set bit y (y= 0..15) - 0 - 1 - - - - - LCKR - LCKR - GPIO port configuration lock register - 0x1C - 0x20 - read-write - 0x00000000 - - - LCKK - Port x lock bit y (y= 0..15) - 16 - 1 - - - LCK15 - Port x lock bit y (y= 0..15) - 15 - 1 - - - LCK14 - Port x lock bit y (y= 0..15) - 14 - 1 - - - LCK13 - Port x lock bit y (y= 0..15) - 13 - 1 - - - LCK12 - Port x lock bit y (y= 0..15) - 12 - 1 - - - LCK11 - Port x lock bit y (y= 0..15) - 11 - 1 - - - LCK10 - Port x lock bit y (y= 0..15) - 10 - 1 - - - LCK9 - Port x lock bit y (y= 0..15) - 9 - 1 - - - LCK8 - Port x lock bit y (y= 0..15) - 8 - 1 - - - LCK7 - Port x lock bit y (y= 0..15) - 7 - 1 - - - LCK6 - Port x lock bit y (y= 0..15) - 6 - 1 - - - LCK5 - Port x lock bit y (y= 0..15) - 5 - 1 - - - LCK4 - Port x lock bit y (y= 0..15) - 4 - 1 - - - LCK3 - Port x lock bit y (y= 0..15) - 3 - 1 - - - LCK2 - Port x lock bit y (y= 0..15) - 2 - 1 - - - LCK1 - Port x lock bit y (y= 0..15) - 1 - 1 - - - LCK0 - Port x lock bit y (y= 0..15) - 0 - 1 - - - - - AFRL - AFRL - GPIO alternate function low register - 0x20 - 0x20 - read-write - 0x00000000 - - - AFSEL7 - Alternate function selection for port x bit y (y = 0..7) - 28 - 4 - - - AFSEL6 - Alternate function selection for port x bit y (y = 0..7) - 24 - 4 - - - AFSEL5 - Alternate function selection for port x bit y (y = 0..7) - 20 - 4 - - - AFSEL4 - Alternate function selection for port x bit y (y = 0..7) - 16 - 4 - - - AFSEL3 - Alternate function selection for port x bit y (y = 0..7) - 12 - 4 - - - AFSEL2 - Alternate function selection for port x bit y (y = 0..7) - 8 - 4 - - - AFSEL1 - Alternate function selection for port x bit y (y = 0..7) - 4 - 4 - - - AFSEL0 - Alternate function selection for port x bit y (y = 0..7) - 0 - 4 - - - - - AFRH - AFRH - GPIO alternate function high register - 0x24 - 0x20 - read-write - 0x00000000 - - - AFSEL15 - Alternate function selection for port x bit y (y = 8..15) - 28 - 4 - - - AFSEL14 - Alternate function selection for port x bit y (y = 8..15) - 24 - 4 - - - AFSEL13 - Alternate function selection for port x bit y (y = 8..15) - 20 - 4 - - - AFSEL12 - Alternate function selection for port x bit y (y = 8..15) - 16 - 4 - - - AFSEL11 - Alternate function selection for port x bit y (y = 8..15) - 12 - 4 - - - AFSEL10 - Alternate function selection for port x bit y (y = 8..15) - 8 - 4 - - - AFSEL9 - Alternate function selection for port x bit y (y = 8..15) - 4 - 4 - - - AFSEL8 - Alternate function selection for port x bit y (y = 8..15) - 0 - 4 - - - - - BRR - BRR - port bit reset register - 0x28 - 0x20 - write-only - 0x00000000 - - - BR0 - Port Reset bit - 0 - 1 - - - BR1 - Port Reset bit - 1 - 1 - - - BR2 - Port Reset bit - 2 - 1 - - - BR3 - Port Reset bit - 3 - 1 - - - BR4 - Port Reset bit - 4 - 1 - - - BR5 - Port Reset bit - 5 - 1 - - - BR6 - Port Reset bit - 6 - 1 - - - BR7 - Port Reset bit - 7 - 1 - - - BR8 - Port Reset bit - 8 - 1 - - - BR9 - Port Reset bit - 9 - 1 - - - BR10 - Port Reset bit - 10 - 1 - - - BR11 - Port Reset bit - 11 - 1 - - - BR12 - Port Reset bit - 12 - 1 - - - BR13 - Port Reset bit - 13 - 1 - - - BR14 - Port Reset bit - 14 - 1 - - - BR15 - Port Reset bit - 15 - 1 - - - - - - - GPIOD - 0x48000C00 - - - GPIOE - General-purpose I/Os - GPIO - 0x48001000 - - 0x0 - 0x400 - registers - - - - MODER - MODER - GPIO port mode register - 0x0 - 0x20 - read-write - 0x000003FF - - - MODER4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - MODER3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - MODER2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - MODER1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - MODER0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - OTYPER - OTYPER - GPIO port output type register - 0x4 - 0x20 - read-write - 0x00000000 - - - OT4 - Port x configuration bits (y = 0..15) - 4 - 1 - - - OT3 - Port x configuration bits (y = 0..15) - 3 - 1 - - - OT2 - Port x configuration bits (y = 0..15) - 2 - 1 - - - OT1 - Port x configuration bits (y = 0..15) - 1 - 1 - - - OT0 - Port x configuration bits (y = 0..15) - 0 - 1 - - - - - OSPEEDR - OSPEEDR - GPIO port output speed register - 0x8 - 0x20 - read-write - 0x000000C0 - - - OSPEEDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - OSPEEDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - OSPEEDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - OSPEEDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - OSPEEDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - PUPDR - PUPDR - GPIO port pull-up/pull-down register - 0xC - 0x20 - read-write - 0x00000000 - - - PUPDR4 - Port x configuration bits (y = 0..15) - 8 - 2 - - - PUPDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - PUPDR2 - Port x configuration bits (y = 0..15) - 4 - 2 - - - PUPDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - PUPDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - IDR - IDR - GPIO port input data register - 0x10 - 0x20 - read-only - 0x00000000 - - - IDR4 - Port input data (y = 0..15) - 4 - 1 - - - IDR3 - Port input data (y = 0..15) - 3 - 1 - - - IDR2 - Port input data (y = 0..15) - 2 - 1 - - - IDR1 - Port input data (y = 0..15) - 1 - 1 - - - IDR0 - Port input data (y = 0..15) - 0 - 1 - - - - - ODR - ODR - GPIO port output data register - 0x14 - 0x20 - read-write - 0x00000000 - - - ODR4 - Port output data (y = 0..15) - 4 - 1 - - - ODR3 - Port output data (y = 0..15) - 3 - 1 - - - ODR2 - Port output data (y = 0..15) - 2 - 1 - - - ODR1 - Port output data (y = 0..15) - 1 - 1 - - - ODR0 - Port output data (y = 0..15) - 0 - 1 - - - - - BSRR - BSRR - GPIO port bit set/reset register - 0x18 - 0x20 - write-only - 0x00000000 - - - BR4 - Port x reset bit y (y = 0..15) - 20 - 1 - - - BR3 - Port x reset bit y (y = 0..15) - 19 - 1 - - - BR2 - Port x reset bit y (y = 0..15) - 18 - 1 - - - BR1 - Port x reset bit y (y = 0..15) - 17 - 1 - - - BR0 - Port x set bit y (y= 0..15) - 16 - 1 - - - BS4 - Port x set bit y (y= 0..15) - 4 - 1 - - - BS3 - Port x set bit y (y= 0..15) - 3 - 1 - - - BS2 - Port x set bit y (y= 0..15) - 2 - 1 - - - BS1 - Port x set bit y (y= 0..15) - 1 - 1 - - - BS0 - Port x set bit y (y= 0..15) - 0 - 1 - - - - - LCKR - LCKR - GPIO port configuration lock register - 0x1C - 0x20 - read-write - 0x00000000 - - - LCKK - Port x lock bit y (y= 0..15) - 16 - 1 - - - LCK4 - Port x lock bit y (y= 0..15) - 4 - 1 - - - LCK3 - Port x lock bit y (y= 0..15) - 3 - 1 - - - LCK2 - Port x lock bit y (y= 0..15) - 2 - 1 - - - LCK1 - Port x lock bit y (y= 0..15) - 1 - 1 - - - LCK0 - Port x lock bit y (y= 0..15) - 0 - 1 - - - - - AFRL - AFRL - GPIO alternate function low register - 0x20 - 0x20 - read-write - 0x00000000 - - - AFSEL4 - Alternate function selection for port x bit y (y = 0..7) - 16 - 4 - - - AFSEL3 - Alternate function selection for port x bit y (y = 0..7) - 12 - 4 - - - AFSEL2 - Alternate function selection for port x bit y (y = 0..7) - 8 - 4 - - - AFSEL1 - Alternate function selection for port x bit y (y = 0..7) - 4 - 4 - - - AFSEL0 - Alternate function selection for port x bit y (y = 0..7) - 0 - 4 - - - - - AFRH - AFRH - GPIO alternate function high register - 0x24 - 0x20 - read-write - 0x00000000 - - - AFSEL15 - Alternate function selection for port x bit y (y = 8..15) - 28 - 4 - - - AFSEL14 - Alternate function selection for port x bit y (y = 8..15) - 24 - 4 - - - AFSEL13 - Alternate function selection for port x bit y (y = 8..15) - 20 - 4 - - - AFSEL12 - Alternate function selection for port x bit y (y = 8..15) - 16 - 4 - - - AFSEL11 - Alternate function selection for port x bit y (y = 8..15) - 12 - 4 - - - AFSEL10 - Alternate function selection for port x bit y (y = 8..15) - 8 - 4 - - - AFSEL9 - Alternate function selection for port x bit y (y = 8..15) - 4 - 4 - - - AFSEL8 - Alternate function selection for port x bit y (y = 8..15) - 0 - 4 - - - - - BRR - BRR - port bit reset register - 0x28 - 0x20 - write-only - 0x00000000 - - - BR0 - Port Reset bit - 0 - 1 - - - BR1 - Port Reset bit - 1 - 1 - - - BR2 - Port Reset bit - 2 - 1 - - - BR3 - Port Reset bit - 3 - 1 - - - BR4 - Port Reset bit - 4 - 1 - - - - - - - GPIOH - General-purpose I/Os - GPIO - 0x48001C00 - - 0x0 - 0x400 - registers - - - - MODER - MODER - GPIO port mode register - 0x0 - 0x20 - read-write - 0x000000CF - - - MODER3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - MODER1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - MODER0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - OTYPER - OTYPER - GPIO port output type register - 0x4 - 0x20 - read-write - 0x00000000 - - - OT3 - Port x configuration bits (y = 0..15) - 3 - 1 - - - OT1 - Port x configuration bits (y = 0..15) - 1 - 1 - - - OT0 - Port x configuration bits (y = 0..15) - 0 - 1 - - - - - OSPEEDR - OSPEEDR - GPIO port output speed register - 0x8 - 0x20 - read-write - 0x00000000 - - - OSPEEDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - OSPEEDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - OSPEEDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - PUPDR - PUPDR - GPIO port pull-up/pull-down register - 0xC - 0x20 - read-write - 0x00000000 - - - PUPDR3 - Port x configuration bits (y = 0..15) - 6 - 2 - - - PUPDR1 - Port x configuration bits (y = 0..15) - 2 - 2 - - - PUPDR0 - Port x configuration bits (y = 0..15) - 0 - 2 - - - - - IDR - IDR - GPIO port input data register - 0x10 - 0x20 - read-only - 0x00000000 - - - IDR3 - Port input data (y = 0..15) - 3 - 1 - - - IDR1 - Port input data (y = 0..15) - 1 - 1 - - - IDR0 - Port input data (y = 0..15) - 0 - 1 - - - - - ODR - ODR - GPIO port output data register - 0x14 - 0x20 - read-write - 0x00000000 - - - ODR3 - Port output data (y = 0..15) - 3 - 1 - - - ODR1 - Port output data (y = 0..15) - 1 - 1 - - - ODR0 - Port output data (y = 0..15) - 0 - 1 - - - - - BSRR - BSRR - GPIO port bit set/reset register - 0x18 - 0x20 - write-only - 0x00000000 - - - BR3 - Port x reset bit y (y = 0..15) - 19 - 1 - - - BR1 - Port x reset bit y (y = 0..15) - 17 - 1 - - - BR0 - Port x set bit y (y= 0..15) - 16 - 1 - - - BS3 - Port x set bit y (y= 0..15) - 3 - 1 - - - BS1 - Port x set bit y (y= 0..15) - 1 - 1 - - - BS0 - Port x set bit y (y= 0..15) - 0 - 1 - - - - - LCKR - LCKR - GPIO port configuration lock register - 0x1C - 0x20 - read-write - 0x00000000 - - - LCKK - Port x lock bit y (y= 0..15) - 16 - 1 - - - LCK3 - Port x lock bit y (y= 0..15) - 3 - 1 - - - LCK1 - Port x lock bit y (y= 0..15) - 1 - 1 - - - LCK0 - Port x lock bit y (y= 0..15) - 0 - 1 - - - - - AFRL - AFRL - GPIO alternate function low register - 0x20 - 0x20 - read-write - 0x00000000 - - - AFSEL3 - Alternate function selection for port x bit y (y = 0..7) - 12 - 4 - - - AFSEL1 - Alternate function selection for port x bit y (y = 0..7) - 4 - 4 - - - AFSEL0 - Alternate function selection for port x bit y (y = 0..7) - 0 - 4 - - - - - AFRH - AFRH - GPIO alternate function high register - 0x24 - 0x20 - read-write - 0x00000000 - - - AFSEL15 - Alternate function selection for port x bit y (y = 8..15) - 28 - 4 - - - AFSEL14 - Alternate function selection for port x bit y (y = 8..15) - 24 - 4 - - - AFSEL13 - Alternate function selection for port x bit y (y = 8..15) - 20 - 4 - - - AFSEL12 - Alternate function selection for port x bit y (y = 8..15) - 16 - 4 - - - AFSEL11 - Alternate function selection for port x bit y (y = 8..15) - 12 - 4 - - - AFSEL10 - Alternate function selection for port x bit y (y = 8..15) - 8 - 4 - - - AFSEL9 - Alternate function selection for port x bit y (y = 8..15) - 4 - 4 - - - AFSEL8 - Alternate function selection for port x bit y (y = 8..15) - 0 - 4 - - - - - BRR - BRR - port bit reset register - 0x28 - 0x20 - write-only - 0x00000000 - - - BR0 - Port Reset bit - 0 - 1 - - - BR1 - Port Reset bit - 1 - 1 - - - BR3 - Port Reset bit - 3 - 1 - - - - - - - SAI1 - Serial audio interface - SAI - 0x40015400 - - 0x0 - 0x400 - registers - - - SAI1 - SAI1 global interrupt - 38 - - - - GCR - GCR - Global configuration register - 0x0 - 0x20 - read-write - 0x00000000 - - - SYNCOUT - Synchronization outputs - 4 - 2 - - - SYNCIN - Synchronization inputs - 0 - 2 - - - - - BCR1 - BCR1 - BConfiguration register 1 - 0x24 - 0x20 - read-write - 0x00000040 - - - MCKEN - Master clock generation enable - 27 - 1 - - - OSR - Oversampling ratio for master clock - 26 - 1 - - - MCJDIV - Master clock divider - 20 - 6 - - - NODIV - No divider - 19 - 1 - - - DMAEN - DMA enable - 17 - 1 - - - SAIBEN - Audio block B enable - 16 - 1 - - - OutDri - Output drive - 13 - 1 - - - MONO - Mono mode - 12 - 1 - - - SYNCEN - Synchronization enable - 10 - 2 - - - CKSTR - Clock strobing edge - 9 - 1 - - - LSBFIRST - Least significant bit first - 8 - 1 - - - DS - Data size - 5 - 3 - - - PRTCFG - Protocol configuration - 2 - 2 - - - MODE - Audio block mode - 0 - 2 - - - - - BCR2 - BCR2 - BConfiguration register 2 - 0x28 - 0x20 - read-write - 0x00000000 - - - COMP - Companding mode - 14 - 2 - - - CPL - Complement bit - 13 - 1 - - - MUTECN - Mute counter - 7 - 6 - - - MUTEVAL - Mute value - 6 - 1 - - - MUTE - Mute - 5 - 1 - - - TRIS - Tristate management on data line - 4 - 1 - - - FFLUS - FIFO flush - 3 - 1 - - - FTH - FIFO threshold - 0 - 3 - - - - - BFRCR - BFRCR - BFRCR - 0x2C - 0x20 - read-write - 0x00000007 - - - FSOFF - Frame synchronization offset - 18 - 1 - - - FSPOL - Frame synchronization polarity - 17 - 1 - - - FSDEF - Frame synchronization definition - 16 - 1 - - - FSALL - Frame synchronization active level length - 8 - 7 - - - FRL - Frame length - 0 - 8 - - - - - BSLOTR - BSLOTR - BSlot register - 0x30 - 0x20 - read-write - 0x00000000 - - - SLOTEN - Slot enable - 16 - 16 - - - NBSLOT - Number of slots in an audio frame - 8 - 4 - - - SLOTSZ - Slot size - 6 - 2 - - - FBOFF - First bit offset - 0 - 5 - - - - - BIM - BIM - BInterrupt mask register2 - 0x34 - 0x20 - read-write - 0x00000000 - - - LFSDETIE - Late frame synchronization detection interrupt enable - 6 - 1 - - - AFSDETIE - Anticipated frame synchronization detection interrupt enable - 5 - 1 - - - CNRDYIE - Codec not ready interrupt enable - 4 - 1 - - - FREQIE - FIFO request interrupt enable - 3 - 1 - - - WCKCFG - Wrong clock configuration interrupt enable - 2 - 1 - - - MUTEDET - Mute detection interrupt enable - 1 - 1 - - - OVRUDRIE - Overrun/underrun interrupt enable - 0 - 1 - - - - - BSR - BSR - BStatus register - 0x38 - 0x20 - read-only - 0x00000008 - - - FLVL - FIFO level threshold - 16 - 3 - - - LFSDET - Late frame synchronization detection - 6 - 1 - - - AFSDET - Anticipated frame synchronization detection - 5 - 1 - - - CNRDY - Codec not ready - 4 - 1 - - - FREQ - FIFO request - 3 - 1 - - - WCKCFG - Wrong clock configuration flag - 2 - 1 - - - MUTEDET - Mute detection - 1 - 1 - - - OVRUDR - Overrun / underrun - 0 - 1 - - - - - BCLRFR - BCLRFR - BClear flag register - 0x3C - 0x20 - write-only - 0x00000000 - - - LFSDET - Clear late frame synchronization detection flag - 6 - 1 - - - CAFSDET - Clear anticipated frame synchronization detection flag - 5 - 1 - - - CNRDY - Clear codec not ready flag - 4 - 1 - - - WCKCFG - Clear wrong clock configuration flag - 2 - 1 - - - MUTEDET - Mute detection flag - 1 - 1 - - - OVRUDR - Clear overrun / underrun - 0 - 1 - - - - - BDR - BDR - BData register - 0x40 - 0x20 - read-write - 0x00000000 - - - DATA - Data - 0 - 32 - - - - - ACR1 - ACR1 - AConfiguration register 1 - 0x4 - 0x20 - read-write - 0x00000040 - - - MCKEN - Master clock generation enable - 27 - 1 - - - OSR - Oversampling ratio for master clock - 26 - 1 - - - MCJDIV - Master clock divider - 20 - 6 - - - NODIV - No divider - 19 - 1 - - - DMAEN - DMA enable - 17 - 1 - - - SAIBEN - Audio block B enable - 16 - 1 - - - OutDri - Output drive - 13 - 1 - - - MONO - Mono mode - 12 - 1 - - - SYNCEN - Synchronization enable - 10 - 2 - - - CKSTR - Clock strobing edge - 9 - 1 - - - LSBFIRST - Least significant bit first - 8 - 1 - - - DS - Data size - 5 - 3 - - - PRTCFG - Protocol configuration - 2 - 2 - - - MODE - Audio block mode - 0 - 2 - - - - - ACR2 - ACR2 - AConfiguration register 2 - 0x8 - 0x20 - read-write - 0x00000000 - - - COMP - Companding mode - 14 - 2 - - - CPL - Complement bit - 13 - 1 - - - MUTECN - Mute counter - 7 - 6 - - - MUTEVAL - Mute value - 6 - 1 - - - MUTE - Mute - 5 - 1 - - - TRIS - Tristate management on data line - 4 - 1 - - - FFLUS - FIFO flush - 3 - 1 - - - FTH - FIFO threshold - 0 - 3 - - - - - AFRCR - AFRCR - AFRCR - 0xC - 0x20 - read-write - 0x00000007 - - - FSOFF - Frame synchronization offset - 18 - 1 - - - FSPOL - Frame synchronization polarity - 17 - 1 - - - FSDEF - Frame synchronization definition - 16 - 1 - - - FSALL - Frame synchronization active level length - 8 - 7 - - - FRL - Frame length - 0 - 8 - - - - - ASLOTR - ASLOTR - ASlot register - 0x10 - 0x20 - read-write - 0x00000000 - - - SLOTEN - Slot enable - 16 - 16 - - - NBSLOT - Number of slots in an audio frame - 8 - 4 - - - SLOTSZ - Slot size - 6 - 2 - - - FBOFF - First bit offset - 0 - 5 - - - - - AIM - AIM - AInterrupt mask register2 - 0x14 - 0x20 - read-write - 0x00000000 - - - LFSDET - Late frame synchronization detection interrupt enable - 6 - 1 - - - AFSDETIE - Anticipated frame synchronization detection interrupt enable - 5 - 1 - - - CNRDYIE - Codec not ready interrupt enable - 4 - 1 - - - FREQIE - FIFO request interrupt enable - 3 - 1 - - - WCKCFG - Wrong clock configuration interrupt enable - 2 - 1 - - - MUTEDET - Mute detection interrupt enable - 1 - 1 - - - OVRUDRIE - Overrun/underrun interrupt enable - 0 - 1 - - - - - ASR - ASR - AStatus register - 0x18 - 0x20 - read-only - 0x00000008 - - - FLVL - FIFO level threshold - 16 - 3 - - - LFSDET - Late frame synchronization detection - 6 - 1 - - - AFSDET - Anticipated frame synchronization detection - 5 - 1 - - - CNRDY - Codec not ready - 4 - 1 - - - FREQ - FIFO request - 3 - 1 - - - WCKCFG - Wrong clock configuration flag. This bit is read only - 2 - 1 - - - MUTEDET - Mute detection - 1 - 1 - - - OVRUDR - Overrun / underrun - 0 - 1 - - - - - ACLRFR - ACLRFR - AClear flag register - 0x1C - 0x20 - write-only - 0x00000000 - - - LFSDET - Clear late frame synchronization detection flag - 6 - 1 - - - CAFSDET - Clear anticipated frame synchronization detection flag - 5 - 1 - - - CNRDY - Clear codec not ready flag - 4 - 1 - - - WCKCFG - Clear wrong clock configuration flag - 2 - 1 - - - MUTEDET - Mute detection flag - 1 - 1 - - - OVRUDR - Clear overrun / underrun - 0 - 1 - - - - - ADR - ADR - AData register - 0x20 - 0x20 - read-write - 0x00000000 - - - DATA - Data - 0 - 32 - - - - - PDMCR - PDMCR - PDM control register - 0x44 - 0x20 - read-write - 0x00000000 - - - CKEN4 - Clock enable of bitstream clock number 4 - 11 - 1 - - - CKEN3 - Clock enable of bitstream clock number 3 - 10 - 1 - - - CKEN2 - Clock enable of bitstream clock number 2 - 9 - 1 - - - CKEN1 - Clock enable of bitstream clock number 1 - 8 - 1 - - - MICNBR - Number of microphones - 4 - 2 - - - PDMEN - PDM enable - 0 - 1 - - - - - PDMDLY - PDMDLY - PDM delay register - 0x48 - 0x20 - read-write - 0x00000000 - - - DLYM4R - Delay line for second microphone of pair 4 - 28 - 3 - - - DLYM4L - Delay line for first microphone of pair 4 - 24 - 3 - - - DLYM3R - Delay line for second microphone of pair 3 - 20 - 3 - - - DLYM3L - Delay line for first microphone of pair 3 - 16 - 3 - - - DLYM2R - Delay line for second microphone of pair 2 - 12 - 3 - - - DLYM2L - Delay line for first microphone of pair 2 - 8 - 3 - - - DLYM1R - Delay line for second microphone of pair 1 - 4 - 3 - - - DLYM1L - Delay line for first microphone of pair 1 - 0 - 3 - - - - - - - TIM2 - General-purpose-timers - TIM - 0x40000000 - - 0x0 - 0x400 - registers - - - TIM2 - TIM2 global interrupt - 28 - - - - CR1 - CR1 - control register 1 - 0x0 - 0x20 - read-write - 0x0000 - - - UIFREMAP - UIF status bit remapping - 11 - 1 - - - CKD - Clock division - 8 - 2 - - - ARPE - Auto-reload preload enable - 7 - 1 - - - CMS - Center-aligned mode selection - 5 - 2 - - - DIR - Direction - 4 - 1 - - - OPM - One-pulse mode - 3 - 1 - - - URS - Update request source - 2 - 1 - - - UDIS - Update disable - 1 - 1 - - - CEN - Counter enable - 0 - 1 - - - - - CR2 - CR2 - control register 2 - 0x4 - 0x20 - read-write - 0x0000 - - - TI1S - TI1 selection - 7 - 1 - - - MMS - Master mode selection - 4 - 3 - - - CCDS - Capture/compare DMA selection - 3 - 1 - - - - - SMCR - SMCR - slave mode control register - 0x8 - 0x20 - read-write - 0x0000 - - - SMS_3 - Slave mode selection - bit 3 - 16 - 1 - - - ETP - External trigger polarity - 15 - 1 - - - ECE - External clock enable - 14 - 1 - - - ETPS - External trigger prescaler - 12 - 2 - - - ETF - External trigger filter - 8 - 4 - - - MSM - Master/Slave mode - 7 - 1 - - - TS - Trigger selection - 4 - 3 - - - OCCS - OCREF clear selection - 3 - 1 - - - SMS - Slave mode selection - 0 - 3 - - - - - DIER - DIER - DMA/Interrupt enable register - 0xC - 0x20 - read-write - 0x0000 - - - CC4DE - Capture/Compare 4 DMA request enable - 12 - 1 - - - CC3DE - Capture/Compare 3 DMA request enable - 11 - 1 - - - CC2DE - Capture/Compare 2 DMA request enable - 10 - 1 - - - CC1DE - Capture/Compare 1 DMA request enable - 9 - 1 - - - UDE - Update DMA request enable - 8 - 1 - - - TIE - Trigger interrupt enable - 6 - 1 - - - CC4IE - Capture/Compare 4 interrupt enable - 4 - 1 - - - CC3IE - Capture/Compare 3 interrupt enable - 3 - 1 - - - CC2IE - Capture/Compare 2 interrupt enable - 2 - 1 - - - CC1IE - Capture/Compare 1 interrupt enable - 1 - 1 - - - UIE - Update interrupt enable - 0 - 1 - - - - - SR - SR - status register - 0x10 - 0x20 - read-write - 0x0000 - - - CC4OF - Capture/Compare 4 overcapture flag - 12 - 1 - - - CC3OF - Capture/Compare 3 overcapture flag - 11 - 1 - - - CC2OF - Capture/compare 2 overcapture flag - 10 - 1 - - - CC1OF - Capture/Compare 1 overcapture flag - 9 - 1 - - - TIF - Trigger interrupt flag - 6 - 1 - - - CC4IF - Capture/Compare 4 interrupt flag - 4 - 1 - - - CC3IF - Capture/Compare 3 interrupt flag - 3 - 1 - - - CC2IF - Capture/Compare 2 interrupt flag - 2 - 1 - - - CC1IF - Capture/compare 1 interrupt flag - 1 - 1 - - - UIF - Update interrupt flag - 0 - 1 - - - - - EGR - EGR - event generation register - 0x14 - 0x20 - write-only - 0x0000 - - - TG - Trigger generation - 6 - 1 - - - CC4G - Capture/compare 4 generation - 4 - 1 - - - CC3G - Capture/compare 3 generation - 3 - 1 - - - CC2G - Capture/compare 2 generation - 2 - 1 - - - CC1G - Capture/compare 1 generation - 1 - 1 - - - UG - Update generation - 0 - 1 - - - - - CCMR1_Output - CCMR1_Output - capture/compare mode register 1 (output mode) - 0x18 - 0x20 - read-write - 0x00000000 - - - OC2M_3 - Output Compare 2 mode - bit 3 - 24 - 1 - - - OC1M_3 - Output Compare 1 mode - bit 3 - 16 - 1 - - - OC2CE - Output compare 2 clear enable - 15 - 1 - - - OC2M - Output compare 2 mode - 12 - 3 - - - OC2PE - Output compare 2 preload enable - 11 - 1 - - - OC2FE - Output compare 2 fast enable - 10 - 1 - - - CC2S - Capture/Compare 2 selection - 8 - 2 - - - OC1CE - Output compare 1 clear enable - 7 - 1 - - - OC1M - Output compare 1 mode - 4 - 3 - - - OC1PE - Output compare 1 preload enable - 3 - 1 - - - OC1FE - Output compare 1 fast enable - 2 - 1 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - - - CCMR1_Input - CCMR1_Input - capture/compare mode register 1 (input mode) - CCMR1_Output - 0x18 - 0x20 - read-write - 0x00000000 - - - IC2F - Input capture 2 filter - 12 - 4 - - - IC2PSC - Input capture 2 prescaler - 10 - 2 - - - CC2S - Capture/compare 2 selection - 8 - 2 - - - IC1F - Input capture 1 filter - 4 - 4 - - - IC1PSC - Input capture 1 prescaler - 2 - 2 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - - - CCMR2_Output - CCMR2_Output - capture/compare mode register 2 (output mode) - 0x1C - 0x20 - read-write - 0x00000000 - - - OC4M_3 - Output Compare 4 mode - bit 3 - 24 - 1 - - - OC3M_3 - Output Compare 3 mode - bit 3 - 16 - 1 - - - OC4CE - Output compare 4 clear enable - 15 - 1 - - - OC4M - Output compare 4 mode - 12 - 3 - - - OC4PE - Output compare 4 preload enable - 11 - 1 - - - OC4FE - Output compare 4 fast enable - 10 - 1 - - - CC4S - Capture/Compare 4 selection - 8 - 2 - - - OC3CE - Output compare 3 clear enable - 7 - 1 - - - OC3M - Output compare 3 mode - 4 - 3 - - - OC3PE - Output compare 3 preload enable - 3 - 1 - - - OC3FE - Output compare 3 fast enable - 2 - 1 - - - CC3S - Capture/Compare 3 selection - 0 - 2 - - - - - CCMR2_Input - CCMR2_Input - capture/compare mode register 2 (input mode) - CCMR2_Output - 0x1C - 0x20 - read-write - 0x00000000 - - - IC4F - Input capture 4 filter - 12 - 4 - - - IC4PSC - Input capture 4 prescaler - 10 - 2 - - - CC4S - Capture/Compare 4 selection - 8 - 2 - - - IC3F - Input capture 3 filter - 4 - 4 - - - IC3PSC - Input capture 3 prescaler - 2 - 2 - - - CC3S - Capture/Compare 3 selection - 0 - 2 - - - - - CCER - CCER - capture/compare enable register - 0x20 - 0x20 - read-write - 0x0000 - - - CC4NP - Capture/Compare 4 output Polarity - 15 - 1 - - - CC4P - Capture/Compare 3 output Polarity - 13 - 1 - - - CC4E - Capture/Compare 4 output enable - 12 - 1 - - - CC3NP - Capture/Compare 3 output Polarity - 11 - 1 - - - CC3P - Capture/Compare 3 output Polarity - 9 - 1 - - - CC3E - Capture/Compare 3 output enable - 8 - 1 - - - CC2NP - Capture/Compare 2 output Polarity - 7 - 1 - - - CC2P - Capture/Compare 2 output Polarity - 5 - 1 - - - CC2E - Capture/Compare 2 output enable - 4 - 1 - - - CC1NP - Capture/Compare 1 output Polarity - 3 - 1 - - - CC1P - Capture/Compare 1 output Polarity - 1 - 1 - - - CC1E - Capture/Compare 1 output enable - 0 - 1 - - - - - CNT - CNT - counter - 0x24 - 0x20 - 0x00000000 - - - CNT_H - High counter value (TIM2 only) - 16 - 15 - read-write - - - CNT_L - Low counter value - 0 - 16 - read-write - - - UIFCPY - Value depends on IUFREMAP in TIM2_CR1. - 31 - 1 - read-only - - - - - PSC - PSC - prescaler - 0x28 - 0x20 - read-write - 0x0000 - - - PSC - Prescaler value - 0 - 16 - - - - - ARR - ARR - auto-reload register - 0x2C - 0x20 - read-write - 0x00000000 - - - ARR_H - High Auto-reload value (TIM2 only) - 16 - 16 - - - ARR_L - Low Auto-reload value - 0 - 16 - - - - - CCR1 - CCR1 - capture/compare register 1 - 0x34 - 0x20 - read-write - 0x00000000 - - - CCR1_H - High Capture/Compare 1 value (TIM2 only) - 16 - 16 - - - CCR1_L - Low Capture/Compare 1 value - 0 - 16 - - - - - CCR2 - CCR2 - capture/compare register 2 - 0x38 - 0x20 - read-write - 0x00000000 - - - CCR2_H - High Capture/Compare 2 value (TIM2 only) - 16 - 16 - - - CCR2_L - Low Capture/Compare 2 value - 0 - 16 - - - - - CCR3 - CCR3 - capture/compare register 3 - 0x3C - 0x20 - read-write - 0x00000000 - - - CCR3_H - High Capture/Compare value (TIM2 only) - 16 - 16 - - - CCR3_L - Low Capture/Compare value - 0 - 16 - - - - - CCR4 - CCR4 - capture/compare register 4 - 0x40 - 0x20 - read-write - 0x00000000 - - - CCR4_H - High Capture/Compare value (TIM2 only) - 16 - 16 - - - CCR4_L - Low Capture/Compare value - 0 - 16 - - - - - DCR - DCR - DMA control register - 0x48 - 0x20 - read-write - 0x0000 - - - DBL - DMA burst length - 8 - 5 - - - DBA - DMA base address - 0 - 5 - - - - - DMAR - DMAR - DMA address for full transfer - 0x4C - 0x20 - read-write - 0x0000 - - - DMAB - DMA register for burst accesses - 0 - 16 - - - - - OR - OR - TIM2 option register - 0x50 - 0x20 - read-write - 0x0000 - - - TI4_RMP - Input capture 4 remap - 2 - 2 - - - ETR_RMP - External trigger remap - 1 - 1 - - - ITR_RMP - Internal trigger remap - 0 - 1 - - - - - AF - AF - TIM2 alternate function option register 1 - 0x60 - 0x20 - read-write - 0x0000 - - - ETRSEL - External trigger source selection - 14 - 3 - - - - - - - TIM16 - General purpose timers - TIM - 0x40014400 - - 0x0 - 0x400 - registers - - - - CR1 - CR1 - control register 1 - 0x0 - 0x20 - read-write - 0x0000 - - - CEN - Counter enable - 0 - 1 - - - UDIS - Update disable - 1 - 1 - - - URS - Update request source - 2 - 1 - - - OPM - One-pulse mode - 3 - 1 - - - ARPE - Auto-reload preload enable - 7 - 1 - - - CKD - Clock division - 8 - 2 - - - UIFREMAP - UIF status bit remapping - 11 - 1 - - - - - CR2 - CR2 - control register 2 - 0x4 - 0x20 - read-write - 0x0000 - - - OIS1N - Output Idle state 1 - 9 - 1 - - - OIS1 - Output Idle state 1 - 8 - 1 - - - CCDS - Capture/compare DMA selection - 3 - 1 - - - CCUS - Capture/compare control update selection - 2 - 1 - - - CCPC - Capture/compare preloaded control - 0 - 1 - - - - - DIER - DIER - DMA/Interrupt enable register - 0xC - 0x20 - read-write - 0x0000 - - - UIE - Update interrupt enable - 0 - 1 - - - CC1IE - Capture/Compare 1 interrupt enable - 1 - 1 - - - COMIE - COM interrupt enable - 5 - 1 - - - BIE - Break interrupt enable - 7 - 1 - - - UDE - Update DMA request enable - 8 - 1 - - - CC1DE - Capture/Compare 1 DMA request enable - 9 - 1 - - - - - SR - SR - status register - 0x10 - 0x20 - read-write - 0x0000 - - - CC1OF - Capture/Compare 1 overcapture flag - 9 - 1 - - - BIF - Break interrupt flag - 7 - 1 - - - COMIF - COM interrupt flag - 5 - 1 - - - CC1IF - Capture/compare 1 interrupt flag - 1 - 1 - - - UIF - Update interrupt flag - 0 - 1 - - - - - EGR - EGR - event generation register - 0x14 - 0x20 - write-only - 0x0000 - - - BG - Break generation - 7 - 1 - - - COMG - Capture/Compare control update generation - 5 - 1 - - - CC1G - Capture/compare 1 generation - 1 - 1 - - - UG - Update generation - 0 - 1 - - - - - CCMR1_Output - CCMR1_Output - capture/compare mode register (output mode) - 0x18 - 0x20 - read-write - 0x00000000 - - - OC1M_3 - Output Compare 1 mode - 16 - 1 - - - OC1M - Output Compare 1 mode - 4 - 3 - - - OC1PE - Output Compare 1 preload enable - 3 - 1 - - - OC1FE - Output Compare 1 fast enable - 2 - 1 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - - - CCMR1_Input - CCMR1_Input - capture/compare mode register 1 (input mode) - CCMR1_Output - 0x18 - 0x20 - read-write - 0x00000000 - - - IC1F - Input capture 1 filter - 4 - 4 - - - IC1PSC - Input capture 1 prescaler - 2 - 2 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - - - CCER - CCER - capture/compare enable register - 0x20 - 0x20 - read-write - 0x0000 - - - CC1NP - Capture/Compare 1 output Polarity - 3 - 1 - - - CC1NE - Capture/Compare 1 complementary output enable - 2 - 1 - - - CC1P - Capture/Compare 1 output Polarity - 1 - 1 - - - CC1E - Capture/Compare 1 output enable - 0 - 1 - - - - - CNT - CNT - counter - 0x24 - 0x20 - 0x00000000 - - - CNT - counter value - 0 - 16 - read-write - - - UIFCPY - UIF Copy - 31 - 1 - read-only - - - - - PSC - PSC - prescaler - 0x28 - 0x20 - read-write - 0x0000 - - - PSC - Prescaler value - 0 - 16 - - - - - ARR - ARR - auto-reload register - 0x2C - 0x20 - read-write - 0xFFFF - - - ARR - Auto-reload value - 0 - 16 - - - - - RCR - RCR - repetition counter register - 0x30 - 0x20 - read-write - 0x0000 - - - REP - Repetition counter value - 0 - 8 - - - - - CCR1 - CCR1 - capture/compare register 1 - 0x34 - 0x20 - read-write - 0x00000000 - - - CCR1 - Capture/Compare 1 value - 0 - 16 - - - - - BDTR - BDTR - break and dead-time register - 0x44 - 0x20 - read-write - 0x0000 - - - DTG - Dead-time generator setup - 0 - 8 - - - LOCK - Lock configuration - 8 - 2 - - - OSSI - Off-state selection for Idle mode - 10 - 1 - - - OSSR - Off-state selection for Run mode - 11 - 1 - - - BKE - Break enable - 12 - 1 - - - BKP - Break polarity - 13 - 1 - - - AOE - Automatic output enable - 14 - 1 - - - MOE - Main output enable - 15 - 1 - - - BKDSRM - Break Disarm - 26 - 1 - - - BKBID - Break Bidirectional - 28 - 1 - - - - - DCR - DCR - DMA control register - 0x48 - 0x20 - read-write - 0x0000 - - - DBL - DMA burst length - 8 - 5 - - - DBA - DMA base address - 0 - 5 - - - - - DMAR - DMAR - DMA address for full transfer - 0x4C - 0x20 - read-write - 0x0000 - - - DMAB - DMA register for burst accesses - 0 - 16 - - - - - OR1 - OR1 - TIM option register 1 - 0x50 - 0x20 - read-write - 0x0000 - - - TI1_RMP - Input capture 1 remap - 0 - 2 - - - - - AF1 - AF1 - alternate function register 1 - 0x60 - 0x20 - read-write - 0x00000001 - - - BKINE - BRK BKIN input enable - 0 - 1 - - - BKCMP1E - BRK COMP1 enable - 1 - 1 - - - BKCMP2E - BRK COMP2 enable - 2 - 1 - - - BKINP - BRK BKIN input polarity - 9 - 1 - - - BKCMP1P - BRK COMP1 input polarity - 10 - 1 - - - BKCMP2P - BRK COMP2 input polarit - 11 - 1 - - - - - TISEL - TISEL - input selection register - 0x68 - 0x20 - read-write - 0x00000000 - - - TI1SEL - selects TI1[0] to TI1[15] input - 0 - 4 - - - - - - - TIM17 - General purpose timers - TIM - 0x40014800 - - 0x0 - 0x400 - registers - - - - CR1 - CR1 - control register 1 - 0x0 - 0x20 - read-write - 0x0000 - - - CEN - Counter enable - 0 - 1 - - - UDIS - Update disable - 1 - 1 - - - URS - Update request source - 2 - 1 - - - OPM - One-pulse mode - 3 - 1 - - - ARPE - Auto-reload preload enable - 7 - 1 - - - CKD - Clock division - 8 - 2 - - - UIFREMAP - UIF status bit remapping - 11 - 1 - - - - - CR2 - CR2 - control register 2 - 0x4 - 0x20 - read-write - 0x0000 - - - OIS1N - Output Idle state 1 - 9 - 1 - - - OIS1 - Output Idle state 1 - 8 - 1 - - - CCDS - Capture/compare DMA selection - 3 - 1 - - - CCUS - Capture/compare control update selection - 2 - 1 - - - CCPC - Capture/compare preloaded control - 0 - 1 - - - - - DIER - DIER - DMA/Interrupt enable register - 0xC - 0x20 - read-write - 0x0000 - - - UIE - Update interrupt enable - 0 - 1 - - - CC1IE - Capture/Compare 1 interrupt enable - 1 - 1 - - - COMIE - COM interrupt enable - 5 - 1 - - - BIE - Break interrupt enable - 7 - 1 - - - UDE - Update DMA request enable - 8 - 1 - - - CC1DE - Capture/Compare 1 DMA request enable - 9 - 1 - - - - - SR - SR - status register - 0x10 - 0x20 - read-write - 0x0000 - - - CC1OF - Capture/Compare 1 overcapture flag - 9 - 1 - - - BIF - Break interrupt flag - 7 - 1 - - - COMIF - COM interrupt flag - 5 - 1 - - - CC1IF - Capture/compare 1 interrupt flag - 1 - 1 - - - UIF - Update interrupt flag - 0 - 1 - - - - - EGR - EGR - event generation register - 0x14 - 0x20 - write-only - 0x0000 - - - BG - Break generation - 7 - 1 - - - COMG - Capture/Compare control update generation - 5 - 1 - - - CC1G - Capture/compare 1 generation - 1 - 1 - - - UG - Update generation - 0 - 1 - - - - - CCMR1_Output - CCMR1_Output - capture/compare mode register (output mode) - 0x18 - 0x20 - read-write - 0x00000000 - - - OC1M_3 - Output Compare 1 mode - 16 - 1 - - - OC1M - Output Compare 1 mode - 4 - 3 - - - OC1PE - Output Compare 1 preload enable - 3 - 1 - - - OC1FE - Output Compare 1 fast enable - 2 - 1 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - - - CCMR1_Input - CCMR1_Input - capture/compare mode register 1 (input mode) - CCMR1_Output - 0x18 - 0x20 - read-write - 0x00000000 - - - IC1F - Input capture 1 filter - 4 - 4 - - - IC1PSC - Input capture 1 prescaler - 2 - 2 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - - - CCER - CCER - capture/compare enable register - 0x20 - 0x20 - read-write - 0x0000 - - - CC1NP - Capture/Compare 1 output Polarity - 3 - 1 - - - CC1NE - Capture/Compare 1 complementary output enable - 2 - 1 - - - CC1P - Capture/Compare 1 output Polarity - 1 - 1 - - - CC1E - Capture/Compare 1 output enable - 0 - 1 - - - - - CNT - CNT - counter - 0x24 - 0x20 - 0x00000000 - - - CNT - counter value - 0 - 16 - read-write - - - UIFCPY - UIF Copy - 31 - 1 - read-only - - - - - PSC - PSC - prescaler - 0x28 - 0x20 - read-write - 0x0000 - - - PSC - Prescaler value - 0 - 16 - - - - - ARR - ARR - auto-reload register - 0x2C - 0x20 - read-write - 0xFFFF - - - ARR - Auto-reload value - 0 - 16 - - - - - RCR - RCR - repetition counter register - 0x30 - 0x20 - read-write - 0x0000 - - - REP - Repetition counter value - 0 - 8 - - - - - CCR1 - CCR1 - capture/compare register 1 - 0x34 - 0x20 - read-write - 0x00000000 - - - CCR1 - Capture/Compare 1 value - 0 - 16 - - - - - BDTR - BDTR - break and dead-time register - 0x44 - 0x20 - read-write - 0x0000 - - - DTG - Dead-time generator setup - 0 - 8 - - - LOCK - Lock configuration - 8 - 2 - - - OSSI - Off-state selection for Idle mode - 10 - 1 - - - OSSR - Off-state selection for Run mode - 11 - 1 - - - BKE - Break enable - 12 - 1 - - - BKP - Break polarity - 13 - 1 - - - AOE - Automatic output enable - 14 - 1 - - - MOE - Main output enable - 15 - 1 - - - BKDSRM - Break Disarm - 26 - 1 - - - BKBID - Break Bidirectional - 28 - 1 - - - - - DCR - DCR - DMA control register - 0x48 - 0x20 - read-write - 0x0000 - - - DBL - DMA burst length - 8 - 5 - - - DBA - DMA base address - 0 - 5 - - - - - DMAR - DMAR - DMA address for full transfer - 0x4C - 0x20 - read-write - 0x0000 - - - DMAB - DMA register for burst accesses - 0 - 16 - - - - - OR1 - OR1 - TIM option register 1 - 0x50 - 0x20 - read-write - 0x0000 - - - TI1_RMP - Input capture 1 remap - 0 - 2 - - - - - AF1 - AF1 - alternate function register 1 - 0x60 - 0x20 - read-write - 0x00000001 - - - BKINE - BRK BKIN input enable - 0 - 1 - - - BKCMP1E - BRK COMP1 enable - 1 - 1 - - - BKCMP2E - BRK COMP2 enable - 2 - 1 - - - BKINP - BRK BKIN input polarity - 9 - 1 - - - BKCMP1P - BRK COMP1 input polarity - 10 - 1 - - - BKCMP2P - BRK COMP2 input polarit - 11 - 1 - - - - - TISEL - TISEL - input selection register - 0x68 - 0x20 - read-write - 0x00000000 - - - TI1SEL - selects TI1[0] to TI1[15] input - 0 - 4 - - - - - - - TIM1 - Advanced-timers - TIM - 0x40012C00 - - 0x0 - 0x400 - registers - - - TIM1_BRK - Timer 1 break interrupt - 24 - - - TIM1_UP - Timer 1 Update - 25 - - - TIM1_TRG_COM_TIM17 - TIM1 Trigger and Commutation interrupts and - TIM17 global interrupt - 26 - - - TIM1_CC - TIM1 Capture Compare interrupt - 27 - - - - CR1 - CR1 - control register 1 - 0x0 - 0x20 - read-write - 0x0000 - - - CEN - Counter enable - 0 - 1 - - - OPM - One-pulse mode - 3 - 1 - - - UDIS - Update disable - 1 - 1 - - - URS - Update request source - 2 - 1 - - - DIR - Direction - 4 - 1 - - - CMS - Center-aligned mode selection - 5 - 2 - - - ARPE - Auto-reload preload enable - 7 - 1 - - - CKD - Clock division - 8 - 2 - - - UIFREMAP - UIF status bit remapping - 11 - 1 - - - - - CR2 - CR2 - control register 2 - 0x4 - 0x20 - read-write - 0x0000 - - - MMS2 - Master mode selection 2 - 20 - 4 - - - OIS6 - Output Idle state 6 (OC6 output) - 18 - 1 - - - OIS5 - Output Idle state 5 (OC5 output) - 16 - 1 - - - OIS4 - Output Idle state 4 - 14 - 1 - - - OIS3N - Output Idle state 3 - 13 - 1 - - - OIS3 - Output Idle state 3 - 12 - 1 - - - OIS2N - Output Idle state 2 - 11 - 1 - - - OIS2 - Output Idle state 2 - 10 - 1 - - - OIS1N - Output Idle state 1 - 9 - 1 - - - OIS1 - Output Idle state 1 - 8 - 1 - - - TI1S - TI1 selection - 7 - 1 - - - MMS - Master mode selection - 4 - 3 - - - CCDS - Capture/compare DMA selection - 3 - 1 - - - CCUS - Capture/compare control update selection - 2 - 1 - - - CCPC - Capture/compare preloaded control - 0 - 1 - - - - - SMCR - SMCR - slave mode control register - 0x8 - 0x20 - read-write - 0x0000 - - - SMS - Slave mode selection - 0 - 3 - - - OCCS - OCREF clear selection - 3 - 1 - - - TS - Trigger selection - 4 - 3 - - - MSM - Master/Slave mode - 7 - 1 - - - ETF - External trigger filter - 8 - 4 - - - ETPS - External trigger prescaler - 12 - 2 - - - ECE - External clock enable - 14 - 1 - - - ETP - External trigger polarity - 15 - 1 - - - SMS_3 - Slave mode selection - bit 3 - 16 - 1 - - - - - DIER - DIER - DMA/Interrupt enable register - 0xC - 0x20 - read-write - 0x0000 - - - UIE - Update interrupt enable - 0 - 1 - - - CC1IE - Capture/Compare 1 interrupt enable - 1 - 1 - - - CC2IE - Capture/Compare 2 interrupt enable - 2 - 1 - - - CC3IE - Capture/Compare 3 interrupt enable - 3 - 1 - - - CC4IE - Capture/Compare 4 interrupt enable - 4 - 1 - - - COMIE - COM interrupt enable - 5 - 1 - - - TIE - Trigger interrupt enable - 6 - 1 - - - BIE - Break interrupt enable - 7 - 1 - - - UDE - Update DMA request enable - 8 - 1 - - - CC1DE - Capture/Compare 1 DMA request enable - 9 - 1 - - - CC2DE - Capture/Compare 2 DMA request enable - 10 - 1 - - - CC3DE - Capture/Compare 3 DMA request enable - 11 - 1 - - - CC4DE - Capture/Compare 4 DMA request enable - 12 - 1 - - - COMDE - COM DMA request enable - 13 - 1 - - - TDE - Trigger DMA request enable - 14 - 1 - - - - - SR - SR - status register - 0x10 - 0x20 - read-write - 0x0000 - - - UIF - Update interrupt flag - 0 - 1 - - - CC1IF - Capture/compare 1 interrupt flag - 1 - 1 - - - CC2IF - Capture/Compare 2 interrupt flag - 2 - 1 - - - CC3IF - Capture/Compare 3 interrupt flag - 3 - 1 - - - CC4IF - Capture/Compare 4 interrupt flag - 4 - 1 - - - COMIF - COM interrupt flag - 5 - 1 - - - TIF - Trigger interrupt flag - 6 - 1 - - - BIF - Break interrupt flag - 7 - 1 - - - B2IF - Break 2 interrupt flag - 8 - 1 - - - CC1OF - Capture/Compare 1 overcapture flag - 9 - 1 - - - CC2OF - Capture/compare 2 overcapture flag - 10 - 1 - - - CC3OF - Capture/Compare 3 overcapture flag - 11 - 1 - - - CC4OF - Capture/Compare 4 overcapture flag - 12 - 1 - - - SBIF - System Break interrupt flag - 13 - 1 - - - CC5IF - Compare 5 interrupt flag - 16 - 1 - - - CC6IF - Compare 6 interrupt flag - 17 - 1 - - - - - EGR - EGR - event generation register - 0x14 - 0x20 - write-only - 0x0000 - - - UG - Update generation - 0 - 1 - - - CC1G - Capture/compare 1 generation - 1 - 1 - - - CC2G - Capture/compare 2 generation - 2 - 1 - - - CC3G - Capture/compare 3 generation - 3 - 1 - - - CC4G - Capture/compare 4 generation - 4 - 1 - - - COMG - Capture/Compare control update generation - 5 - 1 - - - TG - Trigger generation - 6 - 1 - - - BG - Break generation - 7 - 1 - - - B2G - Break 2 generation - 8 - 1 - - - - - CCMR1_Input - CCMR1_Input - capture/compare mode register 1 (output mode) - 0x18 - 0x20 - read-write - 0x00000000 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - IC1PSC - Input capture 1 prescaler - 2 - 2 - - - C1F - Input capture 1 filter - 4 - 4 - - - CC2S - capture/Compare 2 selection - 8 - 2 - - - IC2PSC - Input capture 2 prescaler - 10 - 2 - - - IC2F - Input capture 2 filter - 12 - 4 - - - - - CCMR1_Output - CCMR1_Output - capture/compare mode register 1 (output mode) - CCMR1_Input - 0x18 - 0x20 - read-write - 0x00000000 - - - CC1S - Capture/Compare 1 selection - 0 - 2 - - - OC1FE - Output Compare 1 fast enable - 2 - 1 - - - OC1PE - Output Compare 1 preload enable - 3 - 1 - - - OC1M - Output Compare 1 mode - 4 - 3 - - - OC1CE - Output Compare 1 clear enable - 7 - 1 - - - CC2S - Capture/Compare 2 selection - 8 - 2 - - - OC2FE - Output Compare 2 fast enable - 10 - 1 - - - OC2PE - Output Compare 2 preload enable - 11 - 1 - - - OC2M - Output Compare 2 mode - 12 - 3 - - - OC2CE - Output Compare 2 clear enable - 15 - 1 - - - OC1M_3 - Output Compare 1 mode - bit 3 - 16 - 1 - - - OC2M_3 - Output Compare 2 mode - bit 3 - 24 - 1 - - - - - CCMR2_Output - CCMR2_Output - capture/compare mode register 2 (output mode) - 0x1C - 0x20 - read-write - 0x00000000 - - - CC3S - Capture/Compare 3 selection - 0 - 2 - - - OC3FE - Output compare 3 fast enable - 2 - 1 - - - OC3PE - Output compare 3 preload enable - 3 - 1 - - - OC3M - Output compare 3 mode - 4 - 3 - - - OC3CE - Output compare 3 clear enable - 7 - 1 - - - CC4S - Capture/Compare 4 selection - 8 - 2 - - - OC4FE - Output compare 4 fast enable - 10 - 1 - - - OC4PE - Output compare 4 preload enable - 11 - 1 - - - OC4M - Output compare 4 mode - 12 - 3 - - - OC4CE - Output compare 4 clear enable - 15 - 1 - - - OC3M_3 - Output Compare 3 mode - bit 3 - 16 - 1 - - - OC4M_3 - Output Compare 4 mode - bit 3 - 24 - 1 - - - - - CCMR2_Input - CCMR2_Input - capture/compare mode register 2 (output mode) - CCMR2_Output - 0x1C - 0x20 - read-write - 0x00000000 - - - CC3S - Capture/Compare 3 selection - 0 - 2 - - - C3PSC - Input capture 3 prescaler - 2 - 2 - - - IC3F - Input capture 3 filter - 4 - 4 - - - CC4S - Capture/Compare 4 selection - 8 - 2 - - - IC4PSC - Input capture 4 prescaler - 10 - 2 - - - IC4F - Input capture 4 filter - 12 - 4 - - - - - CCER - CCER - capture/compare enable register - 0x20 - 0x20 - read-write - 0x0000 - - - CC1E - Capture/Compare 1 output enable - 0 - 1 - - - CC1P - Capture/Compare 1 output Polarity - 1 - 1 - - - CC1NE - Capture/Compare 1 complementary output enable - 2 - 1 - - - CC1NP - Capture/Compare 1 output Polarity - 3 - 1 - - - CC2E - Capture/Compare 2 output enable - 4 - 1 - - - CC2P - Capture/Compare 2 output Polarity - 5 - 1 - - - CC2NE - Capture/Compare 2 complementary output enable - 6 - 1 - - - CC2NP - Capture/Compare 2 output Polarity - 7 - 1 - - - CC3E - Capture/Compare 3 output enable - 8 - 1 - - - CC3P - Capture/Compare 3 output Polarity - 9 - 1 - - - CC3NE - Capture/Compare 3 complementary output enable - 10 - 1 - - - CC3NP - Capture/Compare 3 output Polarity - 11 - 1 - - - CC4E - Capture/Compare 4 output enable - 12 - 1 - - - CC4P - Capture/Compare 3 output Polarity - 13 - 1 - - - CC4NP - Capture/Compare 4 complementary output polarity - 15 - 1 - - - CC5E - Capture/Compare 5 output enable - 16 - 1 - - - CC5P - Capture/Compare 5 output polarity - 17 - 1 - - - CC6E - Capture/Compare 6 output enable - 20 - 1 - - - CC6P - Capture/Compare 6 output polarity - 21 - 1 - - - - - CNT - CNT - counter - 0x24 - 0x20 - 0x00000000 - - - CNT - counter value - 0 - 16 - read-write - - - UIFCPY - UIF copy - 31 - 1 - read-only - - - - - PSC - PSC - prescaler - 0x28 - 0x20 - read-write - 0x0000 - - - PSC - Prescaler value - 0 - 16 - - - - - ARR - ARR - auto-reload register - 0x2C - 0x20 - read-write - 0x0000FFFF - - - ARR - Auto-reload value - 0 - 16 - - - - - RCR - RCR - repetition counter register - 0x30 - 0x20 - read-write - 0x0000 - - - REP - Repetition counter value - 0 - 16 - - - - - CCR1 - CCR1 - capture/compare register 1 - 0x34 - 0x20 - read-write - 0x00000000 - - - CCR1 - Capture/Compare 1 value - 0 - 16 - - - - - CCR2 - CCR2 - capture/compare register 2 - 0x38 - 0x20 - read-write - 0x00000000 - - - CCR2 - Capture/Compare 2 value - 0 - 16 - - - - - CCR3 - CCR3 - capture/compare register 3 - 0x3C - 0x20 - read-write - 0x00000000 - - - CCR3 - Capture/Compare value - 0 - 16 - - - - - CCR4 - CCR4 - capture/compare register 4 - 0x40 - 0x20 - read-write - 0x00000000 - - - CCR4 - Capture/Compare value - 0 - 16 - - - - - BDTR - BDTR - break and dead-time register - 0x44 - 0x20 - read-write - 0x0000 - - - DTG - Dead-time generator setup - 0 - 8 - - - LOCK - Lock configuration - 8 - 2 - - - OSSI - Off-state selection for Idle mode - 10 - 1 - - - OSSR - Off-state selection for Run mode - 11 - 1 - - - BKE - Break enable - 12 - 1 - - - BKP - Break polarity - 13 - 1 - - - AOE - Automatic output enable - 14 - 1 - - - MOE - Main output enable - 15 - 1 - - - BKF - Break filter - 16 - 4 - - - BK2F - Break 2 filter - 20 - 4 - - - BK2E - Break 2 enable - 24 - 1 - - - BK2P - Break 2 polarity - 25 - 1 - - - - - DCR - DCR - DMA control register - 0x48 - 0x20 - read-write - 0x0000 - - - DBL - DMA burst length - 8 - 5 - - - DBA - DMA base address - 0 - 5 - - - - - DMAR - DMAR - DMA address for full transfer - 0x4C - 0x20 - read-write - 0x0000 - - - DMAB - DMA register for burst accesses - 0 - 16 - - - - - OR - OR - DMA address for full transfer - 0x50 - 0x20 - read-write - 0x0000 - - - TIM1_ETR_ADC1_RMP - TIM1_ETR_ADC1 remapping capability - 0 - 2 - - - TI1_RMP - Input Capture 1 remap - 4 - 1 - - - - - CCMR3_Output - CCMR3_Output - capture/compare mode register 2 (output mode) - 0x54 - 0x20 - read-write - 0x00000000 - - - OC6M_bit3 - Output Compare 6 mode bit 3 - 24 - 1 - - - OC5M_bit3 - Output Compare 5 mode bit 3 - 16 - 1 - - - OC6CE - Output compare 6 clear enable - 15 - 1 - - - OC6M - Output compare 6 mode - 12 - 3 - - - OC6PE - Output compare 6 preload enable - 11 - 1 - - - OC6FE - Output compare 6 fast enable - 10 - 1 - - - OC5CE - Output compare 5 clear enable - 7 - 1 - - - OC5M - Output compare 5 mode - 4 - 3 - - - OC5PE - Output compare 5 preload enable - 3 - 1 - - - OC5FE - Output compare 5 fast enable - 2 - 1 - - - - - CCR5 - CCR5 - capture/compare register 4 - 0x58 - 0x20 - read-write - 0x00000000 - - - CCR5 - Capture/Compare value - 0 - 16 - - - GC5C1 - Group Channel 5 and Channel 1 - 29 - 1 - - - GC5C2 - Group Channel 5 and Channel 2 - 30 - 1 - - - GC5C3 - Group Channel 5 and Channel 3 - 31 - 1 - - - - - CCR6 - CCR6 - capture/compare register 4 - 0x5C - 0x20 - read-write - 0x00000000 - - - CCR6 - Capture/Compare value - 0 - 16 - - - - - AF1 - AF1 - DMA address for full transfer - 0x60 - 0x20 - read-write - 0x00000001 - - - BKINE - BRK BKIN input enable - 0 - 1 - - - BKCMP1E - BRK COMP1 enable - 1 - 1 - - - BKCMP2E - BRK COMP2 enable - 2 - 1 - - - BKINP - BRK BKIN input polarity - 9 - 1 - - - BKCMP1P - BRK COMP1 input polarity - 10 - 1 - - - BKCMP2P - BRK COMP2 input polarity - 11 - 1 - - - ETRSEL - ETR source selection - 14 - 3 - - - - - AF2 - AF2 - DMA address for full transfer - 0x64 - 0x20 - read-write - 0x00000001 - - - BK2INE - BRK2 BKIN input enable - 0 - 1 - - - BK2CMP1E - BRK2 COMP1 enable - 1 - 1 - - - BK2CMP2E - BRK2 COMP2 enable - 2 - 1 - - - BK2DFBK0E - BRK2 DFSDM_BREAK0 enable - 8 - 1 - - - BK2INP - BRK2 BKIN input polarity - 9 - 1 - - - BK2CMP1P - BRK2 COMP1 input polarity - 10 - 1 - - - BK2CMP2P - BRK2 COMP2 input polarity - 11 - 1 - - - - - - - LPTIM1 - Low power timer - LPTIM - 0x40007C00 - - 0x0 - 0x400 - registers - - - LPTIM1 - LPtimer 1 global interrupt - 47 - - - - ISR - ISR - Interrupt and Status Register - 0x0 - 0x20 - read-only - 0x00000000 - - - DOWN - Counter direction change up to down - 6 - 1 - - - UP - Counter direction change down to up - 5 - 1 - - - ARROK - Autoreload register update OK - 4 - 1 - - - CMPOK - Compare register update OK - 3 - 1 - - - EXTTRIG - External trigger edge event - 2 - 1 - - - ARRM - Autoreload match - 1 - 1 - - - CMPM - Compare match - 0 - 1 - - - - - ICR - ICR - Interrupt Clear Register - 0x4 - 0x20 - write-only - 0x00000000 - - - DOWNCF - Direction change to down Clear Flag - 6 - 1 - - - UPCF - Direction change to UP Clear Flag - 5 - 1 - - - ARROKCF - Autoreload register update OK Clear Flag - 4 - 1 - - - CMPOKCF - Compare register update OK Clear Flag - 3 - 1 - - - EXTTRIGCF - External trigger valid edge Clear Flag - 2 - 1 - - - ARRMCF - Autoreload match Clear Flag - 1 - 1 - - - CMPMCF - compare match Clear Flag - 0 - 1 - - - - - IER - IER - Interrupt Enable Register - 0x8 - 0x20 - read-write - 0x00000000 - - - DOWNIE - Direction change to down Interrupt Enable - 6 - 1 - - - UPIE - Direction change to UP Interrupt Enable - 5 - 1 - - - ARROKIE - Autoreload register update OK Interrupt Enable - 4 - 1 - - - CMPOKIE - Compare register update OK Interrupt Enable - 3 - 1 - - - EXTTRIGIE - External trigger valid edge Interrupt Enable - 2 - 1 - - - ARRMIE - Autoreload match Interrupt Enable - 1 - 1 - - - CMPMIE - Compare match Interrupt Enable - 0 - 1 - - - - - CFGR - CFGR - Configuration Register - 0xC - 0x20 - read-write - 0x00000000 - - - ENC - Encoder mode enable - 24 - 1 - - - COUNTMODE - counter mode enabled - 23 - 1 - - - PRELOAD - Registers update mode - 22 - 1 - - - WAVPOL - Waveform shape polarity - 21 - 1 - - - WAVE - Waveform shape - 20 - 1 - - - TIMOUT - Timeout enable - 19 - 1 - - - TRIGEN - Trigger enable and polarity - 17 - 2 - - - TRIGSEL - Trigger selector - 13 - 3 - - - PRESC - Clock prescaler - 9 - 3 - - - TRGFLT - Configurable digital filter for trigger - 6 - 2 - - - CKFLT - Configurable digital filter for external clock - 3 - 2 - - - CKPOL - Clock Polarity - 1 - 2 - - - CKSEL - Clock selector - 0 - 1 - - - - - CR - CR - Control Register - 0x10 - 0x20 - read-write - 0x00000000 - - - RSTARE - Reset after read enable - 4 - 1 - - - COUNTRST - Counter reset - 3 - 1 - - - CNTSTRT - Timer start in continuous mode - 2 - 1 - - - SNGSTRT - LPTIM start in single mode - 1 - 1 - - - ENABLE - LPTIM Enable - 0 - 1 - - - - - CMP - CMP - Compare Register - 0x14 - 0x20 - read-write - 0x00000000 - - - CMP - Compare value - 0 - 16 - - - - - ARR - ARR - Autoreload Register - 0x18 - 0x20 - read-write - 0x00000001 - - - ARR - Auto reload value - 0 - 16 - - - - - CNT - CNT - Counter Register - 0x1C - 0x20 - read-only - 0x00000000 - - - CNT - Counter value - 0 - 16 - - - - - OR - OR - Option Register - 0x20 - 0x20 - read-write - 0x00000000 - - - OR1 - Option register bit 1 - 0 - 1 - - - OR2 - Option register bit 2 - 1 - 1 - - - - - - - LPTIM2 - 0x40009400 - - LPTIM2 - LPtimer 2 global interrupt - 48 - - - - USART1 - Universal synchronous asynchronous receiver transmitter - USART - 0x40013800 - - 0x0 - 0x400 - registers - - - USART1 - USART1 global interrupt - 36 - - - - CR1 - CR1 - Control register 1 - 0x0 - 0x20 - read-write - 0x0000 - - - RXFFIE - RXFIFO Full interrupt enable - 31 - 1 - - - TXFEIE - TXFIFO empty interrupt enable - 30 - 1 - - - FIFOEN - FIFO mode enable - 29 - 1 - - - M1 - Word length - 28 - 1 - - - EOBIE - End of Block interrupt enable - 27 - 1 - - - RTOIE - Receiver timeout interrupt enable - 26 - 1 - - - DEAT4 - Driver Enable assertion time - 25 - 1 - - - DEAT3 - DEAT3 - 24 - 1 - - - DEAT2 - DEAT2 - 23 - 1 - - - DEAT1 - DEAT1 - 22 - 1 - - - DEAT0 - DEAT0 - 21 - 1 - - - DEDT4 - Driver Enable de-assertion time - 20 - 1 - - - DEDT3 - DEDT3 - 19 - 1 - - - DEDT2 - DEDT2 - 18 - 1 - - - DEDT1 - DEDT1 - 17 - 1 - - - DEDT0 - DEDT0 - 16 - 1 - - - OVER8 - Oversampling mode - 15 - 1 - - - CMIE - Character match interrupt enable - 14 - 1 - - - MME - Mute mode enable - 13 - 1 - - - M0 - Word length - 12 - 1 - - - WAKE - Receiver wakeup method - 11 - 1 - - - PCE - Parity control enable - 10 - 1 - - - PS - Parity selection - 9 - 1 - - - PEIE - PE interrupt enable - 8 - 1 - - - TXEIE - interrupt enable - 7 - 1 - - - TCIE - Transmission complete interrupt enable - 6 - 1 - - - RXNEIE - RXNE interrupt enable - 5 - 1 - - - IDLEIE - IDLE interrupt enable - 4 - 1 - - - TE - Transmitter enable - 3 - 1 - - - RE - Receiver enable - 2 - 1 - - - UESM - USART enable in Stop mode - 1 - 1 - - - UE - USART enable - 0 - 1 - - - - - CR2 - CR2 - Control register 2 - 0x4 - 0x20 - read-write - 0x0000 - - - ADD4_7 - Address of the USART node - 28 - 4 - - - ADD0_3 - Address of the USART node - 24 - 4 - - - RTOEN - Receiver timeout enable - 23 - 1 - - - ABRMOD1 - Auto baud rate mode - 22 - 1 - - - ABRMOD0 - ABRMOD0 - 21 - 1 - - - ABREN - Auto baud rate enable - 20 - 1 - - - MSBFIRST - Most significant bit first - 19 - 1 - - - TAINV - Binary data inversion - 18 - 1 - - - TXINV - TX pin active level inversion - 17 - 1 - - - RXINV - RX pin active level inversion - 16 - 1 - - - SWAP - Swap TX/RX pins - 15 - 1 - - - LINEN - LIN mode enable - 14 - 1 - - - STOP - STOP bits - 12 - 2 - - - CLKEN - Clock enable - 11 - 1 - - - CPOL - Clock polarity - 10 - 1 - - - CPHA - Clock phase - 9 - 1 - - - LBCL - Last bit clock pulse - 8 - 1 - - - LBDIE - LIN break detection interrupt enable - 6 - 1 - - - LBDL - LIN break detection length - 5 - 1 - - - ADDM7 - 7-bit Address Detection/4-bit Address Detection - 4 - 1 - - - DIS_NSS - When the DSI_NSS bit is set, the NSS pin input will be ignored - 3 - 1 - - - SLVEN - Synchronous Slave mode enable - 0 - 1 - - - - - CR3 - CR3 - Control register 3 - 0x8 - 0x20 - read-write - 0x0000 - - - TXFTCFG - TXFIFO threshold configuration - 29 - 3 - - - RXFTIE - RXFIFO threshold interrupt enable - 28 - 1 - - - RXFTCFG - Receive FIFO threshold configuration - 25 - 3 - - - TCBGTIE - Tr Complete before guard time, interrupt enable - 24 - 1 - - - TXFTIE - threshold interrupt enable - 23 - 1 - - - WUFIE - Wakeup from Stop mode interrupt enable - 22 - 1 - - - WUS - Wakeup from Stop mode interrupt flag selection - 20 - 2 - - - SCARCNT - Smartcard auto-retry count - 17 - 3 - - - DEP - Driver enable polarity selection - 15 - 1 - - - DEM - Driver enable mode - 14 - 1 - - - DDRE - DMA Disable on Reception Error - 13 - 1 - - - OVRDIS - Overrun Disable - 12 - 1 - - - ONEBIT - One sample bit method enable - 11 - 1 - - - CTSIE - CTS interrupt enable - 10 - 1 - - - CTSE - CTS enable - 9 - 1 - - - RTSE - RTS enable - 8 - 1 - - - DMAT - DMA enable transmitter - 7 - 1 - - - DMAR - DMA enable receiver - 6 - 1 - - - SCEN - Smartcard mode enable - 5 - 1 - - - NACK - Smartcard NACK enable - 4 - 1 - - - HDSEL - Half-duplex selection - 3 - 1 - - - IRLP - Ir low-power - 2 - 1 - - - IREN - Ir mode enable - 1 - 1 - - - EIE - Error interrupt enable - 0 - 1 - - - - - BRR - BRR - Baud rate register - 0xC - 0x20 - read-write - 0x0000 - - - BRR - BRR_4_15 - 0 - 16 - - - - - GTPR - GTPR - Guard time and prescaler register - 0x10 - 0x20 - read-write - 0x0000 - - - GT - Guard time value - 8 - 8 - - - PSC - Prescaler value - 0 - 8 - - - - - RTOR - RTOR - Receiver timeout register - 0x14 - 0x20 - read-write - 0x0000 - - - BLEN - Block Length - 24 - 8 - - - RTO - Receiver timeout value - 0 - 24 - - - - - RQR - RQR - Request register - 0x18 - 0x20 - write-only - 0x0000 - - - TXFRQ - Transmit data flush request - 4 - 1 - - - RXFRQ - Receive data flush request - 3 - 1 - - - MMRQ - Mute mode request - 2 - 1 - - - SBKRQ - Send break request - 1 - 1 - - - ABRRQ - Auto baud rate request - 0 - 1 - - - - - ISR - ISR - Interrupt & status register - 0x1C - 0x20 - read-only - 0x00C0 - - - TXFT - TXFIFO threshold flag - 27 - 1 - - - RXFT - RXFIFO threshold flag - 26 - 1 - - - TCBGT - Transmission complete before guard time flag - 25 - 1 - - - RXFF - RXFIFO Full - 24 - 1 - - - TXFE - TXFIFO Empty - 23 - 1 - - - REACK - REACK - 22 - 1 - - - TEACK - TEACK - 21 - 1 - - - WUF - WUF - 20 - 1 - - - RWU - RWU - 19 - 1 - - - SBKF - SBKF - 18 - 1 - - - CMF - CMF - 17 - 1 - - - BUSY - BUSY - 16 - 1 - - - ABRF - ABRF - 15 - 1 - - - ABRE - ABRE - 14 - 1 - - - UDR - SPI slave underrun error flag - 13 - 1 - - - EOBF - EOBF - 12 - 1 - - - RTOF - RTOF - 11 - 1 - - - CTS - CTS - 10 - 1 - - - CTSIF - CTSIF - 9 - 1 - - - LBDF - LBDF - 8 - 1 - - - TXE - TXE - 7 - 1 - - - TC - TC - 6 - 1 - - - RXNE - RXNE - 5 - 1 - - - IDLE - IDLE - 4 - 1 - - - ORE - ORE - 3 - 1 - - - NF - NF - 2 - 1 - - - FE - FE - 1 - 1 - - - PE - PE - 0 - 1 - - - - - ICR - ICR - Interrupt flag clear register - 0x20 - 0x20 - write-only - 0x0000 - - - WUCF - Wakeup from Stop mode clear flag - 20 - 1 - - - CMCF - Character match clear flag - 17 - 1 - - - UDRCF - SPI slave underrun clear flag - 13 - 1 - - - EOBCF - End of block clear flag - 12 - 1 - - - RTOCF - Receiver timeout clear flag - 11 - 1 - - - CTSCF - CTS clear flag - 9 - 1 - - - LBDCF - LIN break detection clear flag - 8 - 1 - - - TCBGTCF - Transmission complete before Guard time clear flag - 7 - 1 - - - TCCF - Transmission complete clear flag - 6 - 1 - - - TXFECF - TXFIFO empty clear flag - 5 - 1 - - - IDLECF - Idle line detected clear flag - 4 - 1 - - - ORECF - Overrun error clear flag - 3 - 1 - - - NCF - Noise detected clear flag - 2 - 1 - - - FECF - Framing error clear flag - 1 - 1 - - - PECF - Parity error clear flag - 0 - 1 - - - - - RDR - RDR - Receive data register - 0x24 - 0x20 - read-only - 0x0000 - - - RDR - Receive data value - 0 - 9 - - - - - TDR - TDR - Transmit data register - 0x28 - 0x20 - read-write - 0x0000 - - - TDR - Transmit data value - 0 - 9 - - - - - PRESC - PRESC - Prescaler register - 0x2C - 0x20 - read-write - 0x0000 - - - PRESCALER - Clock prescaler - 0 - 4 - - - - - - - LPUART1 - 0x40008000 - - LPUART1 - LPUART1 global interrupt - 37 - - - - SPI1 - Serial peripheral interface/Inter-IC sound - SPI - 0x40013000 - - 0x0 - 0x400 - registers - - - SPI1 - SPI 1 global interrupt - 34 - - - - CR1 - CR1 - control register 1 - 0x0 - 0x20 - read-write - 0x00000000 - - - BIDIMODE - Bidirectional data mode enable - 15 - 1 - - - BIDIOE - Output enable in bidirectional mode - 14 - 1 - - - CRCEN - Hardware CRC calculation enable - 13 - 1 - - - CRCNEXT - CRC transfer next - 12 - 1 - - - DFF - Data frame format - 11 - 1 - - - RXONLY - Receive only - 10 - 1 - - - SSM - Software slave management - 9 - 1 - - - SSI - Internal slave select - 8 - 1 - - - LSBFIRST - Frame format - 7 - 1 - - - SPE - SPI enable - 6 - 1 - - - BR - Baud rate control - 3 - 3 - - - MSTR - Master selection - 2 - 1 - - - CPOL - Clock polarity - 1 - 1 - - - CPHA - Clock phase - 0 - 1 - - - - - CR2 - CR2 - control register 2 - 0x4 - 0x20 - read-write - 0x00000700 - - - RXDMAEN - Rx buffer DMA enable - 0 - 1 - - - TXDMAEN - Tx buffer DMA enable - 1 - 1 - - - SSOE - SS output enable - 2 - 1 - - - NSSP - NSS pulse management - 3 - 1 - - - FRF - Frame format - 4 - 1 - - - ERRIE - Error interrupt enable - 5 - 1 - - - RXNEIE - RX buffer not empty interrupt enable - 6 - 1 - - - TXEIE - Tx buffer empty interrupt enable - 7 - 1 - - - DS - Data size - 8 - 4 - - - FRXTH - FIFO reception threshold - 12 - 1 - - - LDMA_RX - Last DMA transfer for reception - 13 - 1 - - - LDMA_TX - Last DMA transfer for transmission - 14 - 1 - - - - - SR - SR - status register - 0x8 - 0x20 - 0x00000002 - - - RXNE - Receive buffer not empty - 0 - 1 - read-only - - - TXE - Transmit buffer empty - 1 - 1 - read-only - - - CRCERR - CRC error flag - 4 - 1 - read-write - - - MODF - Mode fault - 5 - 1 - read-only - - - OVR - Overrun flag - 6 - 1 - read-only - - - BSY - Busy flag - 7 - 1 - read-only - - - TIFRFE - TI frame format error - 8 - 1 - read-only - - - FRLVL - FIFO reception level - 9 - 2 - read-only - - - FTLVL - FIFO transmission level - 11 - 2 - read-only - - - - - DR - DR - data register - 0xC - 0x20 - read-write - 0x00000000 - - - DR - Data register - 0 - 16 - - - - - CRCPR - CRCPR - CRC polynomial register - 0x10 - 0x20 - read-write - 0x00000007 - - - CRCPOLY - CRC polynomial register - 0 - 16 - - - - - RXCRCR - RXCRCR - RX CRC register - 0x14 - 0x20 - read-only - 0x00000000 - - - RxCRC - Rx CRC register - 0 - 16 - - - - - TXCRCR - TXCRCR - TX CRC register - 0x18 - 0x20 - read-only - 0x00000000 - - - TxCRC - Tx CRC register - 0 - 16 - - - - - - - SPI2 - 0x40003800 - - SPI2 - SPI1 global interrupt - 35 - - - - RTC - Real-time clock - RTC - 0x40002800 - - 0x0 - 0x400 - registers - - - RTC_TAMP - RTC/TAMP/CSS on LSE through EXTI line 19 interrupt - 2 - - - RTC_WKUP - RTC wakeup interrupt through EXTI[19] - 3 - - - RTC_ALARM - RTC Alarms (A and B) interrupt through - AIEC - 41 - - - - TR - TR - time register - 0x0 - 0x20 - read-write - 0x00000000 - - - PM - AM/PM notation - 22 - 1 - - - HT - Hour tens in BCD format - 20 - 2 - - - HU - Hour units in BCD format - 16 - 4 - - - MNT - Minute tens in BCD format - 12 - 3 - - - MNU - Minute units in BCD format - 8 - 4 - - - ST - Second tens in BCD format - 4 - 3 - - - SU - Second units in BCD format - 0 - 4 - - - - - DR - DR - date register - 0x4 - 0x20 - read-write - 0x00002101 - - - YT - Year tens in BCD format - 20 - 4 - - - YU - Year units in BCD format - 16 - 4 - - - WDU - Week day units - 13 - 3 - - - MT - Month tens in BCD format - 12 - 1 - - - MU - Month units in BCD format - 8 - 4 - - - DT - Date tens in BCD format - 4 - 2 - - - DU - Date units in BCD format - 0 - 4 - - - - - CR - CR - control register - 0x8 - 0x20 - read-write - 0x00000000 - - - WCKSEL - Wakeup clock selection - 0 - 3 - - - TSEDGE - Time-stamp event active edge - 3 - 1 - - - REFCKON - Reference clock detection enable (50 or 60 Hz) - 4 - 1 - - - BYPSHAD - Bypass the shadow registers - 5 - 1 - - - FMT - Hour format - 6 - 1 - - - ALRAE - Alarm A enable - 8 - 1 - - - ALRBE - Alarm B enable - 9 - 1 - - - WUTE - Wakeup timer enable - 10 - 1 - - - TSE - Time stamp enable - 11 - 1 - - - ALRAIE - Alarm A interrupt enable - 12 - 1 - - - ALRBIE - Alarm B interrupt enable - 13 - 1 - - - WUTIE - Wakeup timer interrupt enable - 14 - 1 - - - TSIE - Time-stamp interrupt enable - 15 - 1 - - - ADD1H - Add 1 hour (summer time change) - 16 - 1 - - - SUB1H - Subtract 1 hour (winter time change) - 17 - 1 - - - BKP - Backup - 18 - 1 - - - COSEL - Calibration output selection - 19 - 1 - - - POL - Output polarity - 20 - 1 - - - OSEL - Output selection - 21 - 2 - - - COE - Calibration output enable - 23 - 1 - - - ITSE - timestamp on internal event enable - 24 - 1 - - - - - ISR - ISR - initialization and status register - 0xC - 0x20 - 0x00000007 - - - ALRAWF - Alarm A write flag - 0 - 1 - read-only - - - ALRBWF - Alarm B write flag - 1 - 1 - read-only - - - WUTWF - Wakeup timer write flag - 2 - 1 - read-only - - - SHPF - Shift operation pending - 3 - 1 - read-write - - - INITS - Initialization status flag - 4 - 1 - read-only - - - RSF - Registers synchronization flag - 5 - 1 - read-write - - - INITF - Initialization flag - 6 - 1 - read-only - - - INIT - Initialization mode - 7 - 1 - read-write - - - ALRAF - Alarm A flag - 8 - 1 - read-write - - - ALRBF - Alarm B flag - 9 - 1 - read-write - - - WUTF - Wakeup timer flag - 10 - 1 - read-write - - - TSF - Time-stamp flag - 11 - 1 - read-write - - - TSOVF - Time-stamp overflow flag - 12 - 1 - read-write - - - TAMP1F - Tamper detection flag - 13 - 1 - read-write - - - TAMP2F - RTC_TAMP2 detection flag - 14 - 1 - read-write - - - TAMP3F - RTC_TAMP3 detection flag - 15 - 1 - read-write - - - RECALPF - Recalibration pending Flag - 16 - 1 - read-only - - - ITSF - INTERNAL TIME-STAMP FLAG - 17 - 1 - read-write - - - - - PRER - PRER - prescaler register - 0x10 - 0x20 - read-write - 0x007F00FF - - - PREDIV_A - Asynchronous prescaler factor - 16 - 7 - - - PREDIV_S - Synchronous prescaler factor - 0 - 15 - - - - - WUTR - WUTR - wakeup timer register - 0x14 - 0x20 - read-write - 0x0000FFFF - - - WUT - Wakeup auto-reload value bits - 0 - 16 - - - - - ALRMAR - ALRMAR - alarm A register - 0x1C - 0x20 - read-write - 0x00000000 - - - MSK4 - Alarm A date mask - 31 - 1 - - - WDSEL - Week day selection - 30 - 1 - - - DT - Date tens in BCD format - 28 - 2 - - - DU - Date units or day in BCD format - 24 - 4 - - - MSK3 - Alarm A hours mask - 23 - 1 - - - PM - AM/PM notation - 22 - 1 - - - HT - Hour tens in BCD format - 20 - 2 - - - HU - Hour units in BCD format - 16 - 4 - - - MSK2 - Alarm A minutes mask - 15 - 1 - - - MNT - Minute tens in BCD format - 12 - 3 - - - MNU - Minute units in BCD format - 8 - 4 - - - MSK1 - Alarm A seconds mask - 7 - 1 - - - ST - Second tens in BCD format - 4 - 3 - - - SU - Second units in BCD format - 0 - 4 - - - - - ALRMBR - ALRMBR - alarm B register - 0x20 - 0x20 - read-write - 0x00000000 - - - MSK4 - Alarm B date mask - 31 - 1 - - - WDSEL - Week day selection - 30 - 1 - - - DT - Date tens in BCD format - 28 - 2 - - - DU - Date units or day in BCD format - 24 - 4 - - - MSK3 - Alarm B hours mask - 23 - 1 - - - PM - AM/PM notation - 22 - 1 - - - HT - Hour tens in BCD format - 20 - 2 - - - HU - Hour units in BCD format - 16 - 4 - - - MSK2 - Alarm B minutes mask - 15 - 1 - - - MNT - Minute tens in BCD format - 12 - 3 - - - MNU - Minute units in BCD format - 8 - 4 - - - MSK1 - Alarm B seconds mask - 7 - 1 - - - ST - Second tens in BCD format - 4 - 3 - - - SU - Second units in BCD format - 0 - 4 - - - - - WPR - WPR - write protection register - 0x24 - 0x20 - write-only - 0x00000000 - - - KEY - Write protection key - 0 - 8 - - - - - SSR - SSR - sub second register - 0x28 - 0x20 - read-only - 0x00000000 - - - SS - Sub second value - 0 - 16 - - - - - SHIFTR - SHIFTR - shift control register - 0x2C - 0x20 - write-only - 0x00000000 - - - ADD1S - Add one second - 31 - 1 - - - SUBFS - Subtract a fraction of a second - 0 - 15 - - - - - TSTR - TSTR - time stamp time register - 0x30 - 0x20 - read-only - 0x00000000 - - - SU - Second units in BCD format - 0 - 4 - - - ST - Second tens in BCD format - 4 - 3 - - - MNU - Minute units in BCD format - 8 - 4 - - - MNT - Minute tens in BCD format - 12 - 3 - - - HU - Hour units in BCD format - 16 - 4 - - - HT - Hour tens in BCD format - 20 - 2 - - - PM - AM/PM notation - 22 - 1 - - - - - TSDR - TSDR - time stamp date register - 0x34 - 0x20 - read-only - 0x00000000 - - - WDU - Week day units - 13 - 3 - - - MT - Month tens in BCD format - 12 - 1 - - - MU - Month units in BCD format - 8 - 4 - - - DT - Date tens in BCD format - 4 - 2 - - - DU - Date units in BCD format - 0 - 4 - - - - - TSSSR - TSSSR - timestamp sub second register - 0x38 - 0x20 - read-only - 0x00000000 - - - SS - Sub second value - 0 - 16 - - - - - CALR - CALR - calibration register - 0x3C - 0x20 - read-write - 0x00000000 - - - CALP - Increase frequency of RTC by 488.5 ppm - 15 - 1 - - - CALW8 - Use an 8-second calibration cycle period - 14 - 1 - - - CALW16 - Use a 16-second calibration cycle period - 13 - 1 - - - CALM - Calibration minus - 0 - 9 - - - - - TAMPCR - TAMPCR - tamper configuration register - 0x40 - 0x20 - read-write - 0x00000000 - - - TAMP1E - Tamper 1 detection enable - 0 - 1 - - - TAMP1TRG - Active level for tamper 1 - 1 - 1 - - - TAMPIE - Tamper interrupt enable - 2 - 1 - - - TAMP2E - Tamper 2 detection enable - 3 - 1 - - - TAMP2TRG - Active level for tamper 2 - 4 - 1 - - - TAMP3E - Tamper 3 detection enable - 5 - 1 - - - TAMP3TRG - Active level for tamper 3 - 6 - 1 - - - TAMPTS - Activate timestamp on tamper detection event - 7 - 1 - - - TAMPFREQ - Tamper sampling frequency - 8 - 3 - - - TAMPFLT - Tamper filter count - 11 - 2 - - - TAMPPRCH - Tamper precharge duration - 13 - 2 - - - TAMPPUDIS - TAMPER pull-up disable - 15 - 1 - - - TAMP1IE - Tamper 1 interrupt enable - 16 - 1 - - - TAMP1NOERASE - Tamper 1 no erase - 17 - 1 - - - TAMP1MF - Tamper 1 mask flag - 18 - 1 - - - TAMP2IE - Tamper 2 interrupt enable - 19 - 1 - - - TAMP2NOERASE - Tamper 2 no erase - 20 - 1 - - - TAMP2MF - Tamper 2 mask flag - 21 - 1 - - - TAMP3IE - Tamper 3 interrupt enable - 22 - 1 - - - TAMP3NOERASE - Tamper 3 no erase - 23 - 1 - - - TAMP3MF - Tamper 3 mask flag - 24 - 1 - - - - - ALRMASSR - ALRMASSR - alarm A sub second register - 0x44 - 0x20 - read-write - 0x00000000 - - - MASKSS - Mask the most-significant bits starting at this bit - 24 - 4 - - - SS - Sub seconds value - 0 - 15 - - - - - ALRMBSSR - ALRMBSSR - alarm B sub second register - 0x48 - 0x20 - read-write - 0x00000000 - - - MASKSS - Mask the most-significant bits starting at this bit - 24 - 4 - - - SS - Sub seconds value - 0 - 15 - - - - - OR - OR - option register - 0x4C - 0x20 - read-write - 0x00000000 - - - RTC_ALARM_TYPE - RTC_ALARM on PC13 output type - 0 - 1 - - - RTC_OUT_RMP - RTC_OUT remap - 1 - 1 - - - - - BKP0R - BKP0R - backup register - 0x50 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP1R - BKP1R - backup register - 0x54 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP2R - BKP2R - backup register - 0x58 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP3R - BKP3R - backup register - 0x5C - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP4R - BKP4R - backup register - 0x60 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP5R - BKP5R - backup register - 0x64 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP6R - BKP6R - backup register - 0x68 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP7R - BKP7R - backup register - 0x6C - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP8R - BKP8R - backup register - 0x70 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP9R - BKP9R - backup register - 0x74 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP10R - BKP10R - backup register - 0x78 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP11R - BKP11R - backup register - 0x7C - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP12R - BKP12R - backup register - 0x80 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP13R - BKP13R - backup register - 0x84 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP14R - BKP14R - backup register - 0x88 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP15R - BKP15R - backup register - 0x8C - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP16R - BKP16R - backup register - 0x90 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP17R - BKP17R - backup register - 0x94 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP18R - BKP18R - backup register - 0x98 - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - BKP19R - BKP19R - backup register - 0x9C - 0x20 - read-write - 0x00000000 - - - BKP - BKP - 0 - 32 - - - - - - - DBGMCU - Debug support - DBGMCU - 0xE0042000 - - 0x0 - 0x400 - registers - - - - IDCODE - IDCODE - MCU Device ID Code Register - 0x0 - 0x20 - read-only - 0x0 - - - DEV_ID - Device Identifier - 0 - 12 - - - REV_ID - Revision Identifier - 16 - 16 - - - - - CR - CR - Debug MCU Configuration Register - 0x4 - 0x20 - read-write - 0x0 - - - DBG_SLEEP - Debug Sleep Mode - 0 - 1 - - - DBG_STOP - Debug Stop Mode - 1 - 1 - - - DBG_STANDBY - Debug Standby Mode - 2 - 1 - - - TRACE_IOEN - Trace port and clock enable - 5 - 1 - - - TRGOEN - External trigger output enable - 28 - 1 - - - - - APB1FZR1 - APB1FZR1 - APB1 Low Freeze Register CPU1 - 0x3C - 0x20 - read-write - 0x0 - - - DBG_TIMER2_STOP - Debug Timer 2 stopped when Core is halted - 0 - 1 - - - DBG_RTC_STOP - RTC counter stopped when core is halted - 10 - 1 - - - DBG_WWDG_STOP - WWDG counter stopped when core is halted - 11 - 1 - - - DBG_IWDG_STOP - IWDG counter stopped when core is halted - 12 - 1 - - - DBG_I2C1_STOP - Debug I2C1 SMBUS timeout stopped when Core is halted - 21 - 1 - - - DBG_I2C3_STOP - Debug I2C3 SMBUS timeout stopped when core is halted - 23 - 1 - - - DBG_LPTIM1_STOP - Debug LPTIM1 stopped when Core is halted - 31 - 1 - - - - - C2AP_B1FZR1 - C2AP_B1FZR1 - APB1 Low Freeze Register CPU2 - 0x40 - 0x20 - read-write - 0x0 - - - DBG_LPTIM2_STOP - LPTIM2 counter stopped when core is halted - 0 - 1 - - - DBG_RTC_STOP - RTC counter stopped when core is halted - 10 - 1 - - - DBG_IWDG_STOP - IWDG stopped when core is halted - 12 - 1 - - - DBG_I2C1_STOP - I2C1 SMBUS timeout stopped when core is halted - 21 - 1 - - - DBG_I2C3_STOP - I2C3 SMBUS timeout stopped when core is halted - 23 - 1 - - - DBG_LPTIM1_STOP - LPTIM1 counter stopped when core is halted - 31 - 1 - - - - - APB1FZR2 - APB1FZR2 - APB1 High Freeze Register CPU1 - 0x44 - 0x20 - read-write - 0x0 - - - DBG_LPTIM2_STOP - LPTIM2 counter stopped when core is halted - 5 - 1 - - - - - C2APB1FZR2 - C2APB1FZR2 - APB1 High Freeze Register CPU2 - 0x48 - 0x20 - read-write - 0x0 - - - DBG_LPTIM2_STOP - LPTIM2 counter stopped when core is halted - 5 - 1 - - - - - APB2FZR - APB2FZR - APB2 Freeze Register CPU1 - 0x4C - 0x20 - read-write - 0x0 - - - DBG_TIM1_STOP - TIM1 counter stopped when core is halted - 11 - 1 - - - DBG_TIM16_STOP - TIM16 counter stopped when core is halted - 17 - 1 - - - DBG_TIM17_STOP - TIM17 counter stopped when core is halted - 18 - 1 - - - - - C2APB2FZR - C2APB2FZR - APB2 Freeze Register CPU2 - C2APB1FZR2 - 0x48 - 0x20 - read-write - 0x0 - - - DBG_TIM1_STOP - TIM1 counter stopped when core is halted - 11 - 1 - - - DBG_TIM16_STOP - TIM16 counter stopped when core is halted - 17 - 1 - - - DBG_TIM17_STOP - TIM17 counter stopped when core is halted - 18 - 1 - - - - - - - PKA - PKA - PKA - 0x58002000 - - 0x0 - 0x2000 - registers - - - PKA - Private key accelerator - interrupt - 29 - - - - CR - CR - Control register - 0x0 - 0x20 - read-write - 0x00000000 - - - ADDRERRIE - Address error interrupt enable - 20 - 1 - - - RAMERRIE - RAM error interrupt enable - 19 - 1 - - - PROCENDIE - End of operation interrupt enable - 17 - 1 - - - MODE - PKA Operation Mode - 8 - 6 - - - SECLVL - Security Enable - 2 - 1 - - - START - Start the operation - 1 - 1 - - - EN - Peripheral Enable - 0 - 1 - - - - - SR - SR - PKA status register - 0x4 - 0x20 - read-only - 0x00000000 - - - ADDRERRF - Address error flag - 20 - 1 - - - RAMERRF - RAM error flag - 19 - 1 - - - PROCENDF - PKA End of Operation flag - 17 - 1 - - - BUSY - PKA Operation in progress - 16 - 1 - - - - - CLRFR - CLRFR - PKA clear flag register - 0x8 - 0x20 - read-write - 0x00000000 - - - ADDRERRFC - Clear Address error flag - 20 - 1 - - - RAMERRFC - Clear RAM error flag - 19 - 1 - - - PROCENDFC - Clear PKA End of Operation flag - 17 - 1 - - - - - VERR - VERR - PKA version register - 0x1FF4 - 0x20 - read-only - 0x00000010 - - - MINREV - Minor revision - 0 - 4 - - - MAJREV - Major revision - 4 - 4 - - - - - IPIDR - IPIDR - PKA identification register - 0x1FF8 - 0x20 - read-only - 0x00170061 - - - ID - Identification Code - 0 - 32 - - - - - SIDR - SIDR - PKA size ID register - 0x1FFC - 0x20 - read-only - 0xA3C5DD08 - - - SID - Side Identification Code - 0 - 32 - - - - - - - IPCC - IPCC - IPCC - 0x58000C00 - - 0x0 - 0x400 - registers - - - IPCC_C1_RX_IT - IPCC CPU1 RX occupied interrupt - 44 - - - IPCC_C1_TX_IT - IPCC CPU1 TX free interrupt - 45 - - - - C1CR - C1CR - Control register CPU1 - 0x0 - 0x20 - read-write - 0x00000000 - - - TXFIE - processor 1 Transmit channel free interrupt enable - 16 - 1 - - - RXOIE - processor 1 Receive channel occupied interrupt enable - 0 - 1 - - - - - C1MR - C1MR - Mask register CPU1 - 0x4 - 0x20 - read-write - 0xFFFFFFFF - - - CH6FM - processor 1 Transmit channel 6 free interrupt mask - 21 - 1 - - - CH5FM - processor 1 Transmit channel 5 free interrupt mask - 20 - 1 - - - CH4FM - processor 1 Transmit channel 4 free interrupt mask - 19 - 1 - - - CH3FM - processor 1 Transmit channel 3 free interrupt mask - 18 - 1 - - - CH2FM - processor 1 Transmit channel 2 free interrupt mask - 17 - 1 - - - CH1FM - processor 1 Transmit channel 1 free interrupt mask - 16 - 1 - - - CH6OM - processor 1 Receive channel 6 occupied interrupt enable - 5 - 1 - - - CH5OM - processor 1 Receive channel 5 occupied interrupt enable - 4 - 1 - - - CH4OM - processor 1 Receive channel 4 occupied interrupt enable - 3 - 1 - - - CH3OM - processor 1 Receive channel 3 occupied interrupt enable - 2 - 1 - - - CH2OM - processor 1 Receive channel 2 occupied interrupt enable - 1 - 1 - - - CH1OM - processor 1 Receive channel 1 occupied interrupt enable - 0 - 1 - - - - - C1SCR - C1SCR - Status Set or Clear register CPU1 - 0x8 - 0x20 - write-only - 0x00000000 - - - CH6S - processor 1 Transmit channel 6 status set - 21 - 1 - - - CH5S - processor 1 Transmit channel 5 status set - 20 - 1 - - - CH4S - processor 1 Transmit channel 4 status set - 19 - 1 - - - CH3S - processor 1 Transmit channel 3 status set - 18 - 1 - - - CH2S - processor 1 Transmit channel 2 status set - 17 - 1 - - - CH1S - processor 1 Transmit channel 1 status set - 16 - 1 - - - CH6C - processor 1 Receive channel 6 status clear - 5 - 1 - - - CH5C - processor 1 Receive channel 5 status clear - 4 - 1 - - - CH4C - processor 1 Receive channel 4 status clear - 3 - 1 - - - CH3C - processor 1 Receive channel 3 status clear - 2 - 1 - - - CH2C - processor 1 Receive channel 2 status clear - 1 - 1 - - - CH1C - processor 1 Receive channel 1 status clear - 0 - 1 - - - - - C1TO2SR - C1TO2SR - CPU1 to CPU2 status register - 0xC - 0x20 - read-only - 0x00000000 - - - CH6F - processor 1 transmit to process 2 Receive channel 6 status flag - 5 - 1 - - - CH5F - processor 1 transmit to process 2 Receive channel 5 status flag - 4 - 1 - - - CH4F - processor 1 transmit to process 2 Receive channel 4 status flag - 3 - 1 - - - CH3F - processor 1 transmit to process 2 Receive channel 3 status flag - 2 - 1 - - - CH2F - processor 1 transmit to process 2 Receive channel 2 status flag - 1 - 1 - - - CH1F - processor 1 transmit to process 2 Receive channel 1 status flag - 0 - 1 - - - - - C2CR - C2CR - Control register CPU2 - 0x10 - 0x20 - read-write - 0x00000000 - - - TXFIE - processor 2 Transmit channel free interrupt enable - 16 - 1 - - - RXOIE - processor 2 Receive channel occupied interrupt enable - 0 - 1 - - - - - C2MR - C2MR - Mask register CPU2 - 0x14 - 0x20 - read-write - 0xFFFFFFFF - - - CH6FM - processor 2 Transmit channel 6 free interrupt mask - 21 - 1 - - - CH5FM - processor 2 Transmit channel 5 free interrupt mask - 20 - 1 - - - CH4FM - processor 2 Transmit channel 4 free interrupt mask - 19 - 1 - - - CH3FM - processor 2 Transmit channel 3 free interrupt mask - 18 - 1 - - - CH2FM - processor 2 Transmit channel 2 free interrupt mask - 17 - 1 - - - CH1FM - processor 2 Transmit channel 1 free interrupt mask - 16 - 1 - - - CH6OM - processor 2 Receive channel 6 occupied interrupt enable - 5 - 1 - - - CH5OM - processor 2 Receive channel 5 occupied interrupt enable - 4 - 1 - - - CH4OM - processor 2 Receive channel 4 occupied interrupt enable - 3 - 1 - - - CH3OM - processor 2 Receive channel 3 occupied interrupt enable - 2 - 1 - - - CH2OM - processor 2 Receive channel 2 occupied interrupt enable - 1 - 1 - - - CH1OM - processor 2 Receive channel 1 occupied interrupt enable - 0 - 1 - - - - - C2SCR - C2SCR - Status Set or Clear register CPU2 - 0x18 - 0x20 - write-only - 0x00000000 - - - CH6S - processor 2 Transmit channel 6 status set - 21 - 1 - - - CH5S - processor 2 Transmit channel 5 status set - 20 - 1 - - - CH4S - processor 2 Transmit channel 4 status set - 19 - 1 - - - CH3S - processor 2 Transmit channel 3 status set - 18 - 1 - - - CH2S - processor 2 Transmit channel 2 status set - 17 - 1 - - - CH1S - processor 2 Transmit channel 1 status set - 16 - 1 - - - CH6C - processor 2 Receive channel 6 status clear - 5 - 1 - - - CH5C - processor 2 Receive channel 5 status clear - 4 - 1 - - - CH4C - processor 2 Receive channel 4 status clear - 3 - 1 - - - CH3C - processor 2 Receive channel 3 status clear - 2 - 1 - - - CH2C - processor 2 Receive channel 2 status clear - 1 - 1 - - - CH1C - processor 2 Receive channel 1 status clear - 0 - 1 - - - - - C2TOC1SR - C2TOC1SR - CPU2 to CPU1 status register - 0x1C - 0x20 - read-only - 0x00000000 - - - CH6F - processor 2 transmit to process 1 Receive channel 6 status flag - 5 - 1 - - - CH5F - processor 2 transmit to process 1 Receive channel 5 status flag - 4 - 1 - - - CH4F - processor 2 transmit to process 1 Receive channel 4 status flag - 3 - 1 - - - CH3F - processor 2 transmit to process 1 Receive channel 3 status flag - 2 - 1 - - - CH2F - processor 2 transmit to process 1 Receive channel 2 status flag - 1 - 1 - - - CH1F - processor 2 transmit to process 1 Receive channel 1 status flag - 0 - 1 - - - - - HWCFGR - HWCFGR - IPCC Hardware configuration register - 0x3F0 - 0x20 - read-only - 0x00000006 - - - CHANNELS - Number of channels per CPU supported by the IP, range 1 to 16 - 0 - 8 - - - - - VERR - VERR - IPCC version register - 0x3F4 - 0x20 - read-only - 0x00000010 - - - MAJREV - Major Revision - 4 - 4 - - - MINREV - Minor Revision - 0 - 4 - - - - - IPIDR - IPIDR - IPCC indentification register - 0x3F8 - 0x20 - read-only - 0x00100071 - - - IPID - Identification Code - 0 - 32 - - - - - SIDR - SIDR - IPCC size indentification register - 0x3FC - 0x20 - read-only - 0xA3C5DD01 - - - SID - Size Identification Code - 0 - 32 - - - - - - - EXTI - External interrupt/event controller - EXTI - 0x58000800 - - 0x0 - 0x400 - registers - - - PVD - PVD through EXTI[16] (C1IMR2[20]) - 1 - - - EXTI0 - EXTI line 0 interrupt through - EXTI[0] - 6 - - - EXTI1 - EXTI line 0 interrupt through - EXTI[1] - 7 - - - EXTI2 - EXTI line 0 interrupt through - EXTI[2] - 8 - - - EXTI3 - EXTI line 0 interrupt through - EXTI[3] - 9 - - - EXTI4 - EXTI line 0 interrupt through - EXTI[4] - 10 - - - C2SEV - CPU2 SEV through EXTI[40] - 21 - - - EXTI5_9 - EXTI line [9:5] interrupt through - EXTI[9:5] - 23 - - - EXTI10_15 - EXTI line [15:10] interrupt through - EXTI[15:10] - 40 - - - - RTSR1 - RTSR1 - rising trigger selection register - 0x0 - 0x20 - read-write - 0x00000000 - - - RT - Rising trigger event configuration bit of Configurable Event input - 0 - 22 - - - RT_31 - Rising trigger event configuration bit of Configurable Event input - 31 - 1 - - - - - FTSR1 - FTSR1 - falling trigger selection register - 0x4 - 0x20 - read-write - 0x00000000 - - - FT - Falling trigger event configuration bit of Configurable Event input - 0 - 22 - - - FT_31 - Falling trigger event configuration bit of Configurable Event input - 31 - 1 - - - - - SWIER1 - SWIER1 - software interrupt event register - 0x8 - 0x20 - read-write - 0x00000000 - - - SWI - Software interrupt on event - 0 - 22 - - - SWI_31 - Software interrupt on event - 31 - 1 - - - - - PR1 - PR1 - EXTI pending register - 0xC - 0x20 - read-write - 0x00000000 - - - PIF - Configurable event inputs Pending bit - 0 - 22 - - - PIF_31 - Configurable event inputs Pending bit - 31 - 1 - - - - - RTSR2 - RTSR2 - rising trigger selection register - 0x20 - 0x20 - read-write - 0x00000000 - - - RT33 - Rising trigger event configuration bit of Configurable Event input - 1 - 1 - - - RT40_41 - Rising trigger event configuration bit of Configurable Event input - 8 - 2 - - - - - FTSR2 - FTSR2 - falling trigger selection register - 0x24 - 0x20 - read-write - 0x00000000 - - - FT33 - Falling trigger event configuration bit of Configurable Event input - 1 - 1 - - - FT40_41 - Falling trigger event configuration bit of Configurable Event input - 8 - 2 - - - - - SWIER2 - SWIER2 - software interrupt event register - 0x28 - 0x20 - read-write - 0x00000000 - - - SWI33 - Software interrupt on event - 1 - 1 - - - SWI40_41 - Software interrupt on event - 8 - 2 - - - - - PR2 - PR2 - pending register - 0x2C - 0x20 - read-write - 0x00000000 - - - PIF33 - Configurable event inputs x+32 Pending bit. - 1 - 1 - - - PIF40_41 - Configurable event inputs x+32 Pending bit. - 8 - 2 - - - - - C1IMR1 - C1IMR1 - CPUm wakeup with interrupt mask register - 0x80 - 0x20 - read-write - 0x7FC00000 - - - IM - CPU(m) wakeup with interrupt Mask on Event input - 0 - 32 - - - - - C2IMR1 - C2IMR1 - CPUm wakeup with interrupt mask register - 0xC0 - 0x20 - read-write - 0x7FC00000 - - - IM - CPU(m) wakeup with interrupt Mask on Event input - 0 - 32 - - - - - C1EMR1 - C1EMR1 - CPUm wakeup with event mask register - 0x84 - 0x20 - read-write - 0x00000000 - - - EM0_15 - CPU(m) Wakeup with event generation Mask on Event input - 0 - 16 - - - EM17_21 - CPU(m) Wakeup with event generation Mask on Event input - 17 - 5 - - - - - C2EMR1 - C2EMR1 - CPUm wakeup with event mask register - 0xC4 - 0x20 - read-write - 0x00000000 - - - EM0_15 - CPU(m) Wakeup with event generation Mask on Event input - 0 - 16 - - - EM17_21 - CPU(m) Wakeup with event generation Mask on Event input - 17 - 5 - - - - - C1IMR2 - C1IMR2 - CPUm wakeup with interrupt mask register - 0x90 - 0x20 - read-write - 0x0001FCFD - - - IM - CPUm Wakeup with interrupt Mask on Event input - 0 - 17 - - - - - C2IMR2 - C2IMR2 - CPUm wakeup with interrupt mask register - 0xD0 - 0x20 - read-write - 0x0001FCFD - - - IM - CPUm Wakeup with interrupt Mask on Event input - 0 - 17 - - - - - C1EMR2 - C1EMR2 - CPUm wakeup with event mask register - 0x94 - 0x20 - read-write - 0x00000000 - - - EM - CPU(m) Wakeup with event generation Mask on Event input - 8 - 2 - - - - - C2EMR2 - C2EMR2 - CPUm wakeup with event mask register - 0xD4 - 0x20 - read-write - 0x00000000 - - - EM - CPU(m) Wakeup with event generation Mask on Event input - 8 - 2 - - - - - HWCFGR5 - HWCFGR5 - Hardware configuration registers - 0x3E0 - 0x20 - read-only - 0x003EFFFF - - - CPUEVENT - HW configuration CPU event generation - 0 - 32 - - - - - HWCFGR6 - HWCFGR6 - Hardware configuration registers - 0x3DC - 0x20 - read-only - 0x00000300 - - - CPUEVENT - HW configuration CPU event generation - 0 - 32 - - - - - HWCFGR7 - HWCFGR7 - EXTI Hardware configuration registers - 0x3D8 - 0x20 - read-only - 0x00000000 - - - CPUEVENT - HW configuration CPU event generation - 0 - 32 - - - - - HWCFGR2 - HWCFGR2 - Hardware configuration registers - 0x3EC - 0x20 - read-only - 0x803FFFFF - - - EVENT_TRG - HW configuration event trigger type - 0 - 32 - - - - - HWCFGR3 - HWCFGR3 - Hardware configuration registers - 0x3E8 - 0x20 - read-only - 0x00000302 - - - EVENT_TRG - HW configuration event trigger type - 0 - 32 - - - - - HWCFGR4 - HWCFGR4 - Hardware configuration registers - 0x3E4 - 0x20 - read-only - 0x00000000 - - - EVENT_TRG - HW configuration event trigger type - 0 - 32 - - - - - HWCFGR1 - HWCFGR1 - Hardware configuration register 1 - 0x3F0 - 0x20 - read-only - 0x00003130 - - - NBEVENTS - HW configuration number of event - 0 - 8 - - - NBCPUS - HW configuration number of CPUs - 8 - 4 - - - CPUEVTEN - HW configuration of CPU(m) event output enable - 12 - 4 - - - - - VERR - VERR - EXTI IP Version register - 0x3F4 - 0x20 - read-only - 0X00000020 - - - MINREV - Minor Revision number - 0 - 4 - - - MAJREV - Major Revision number - 4 - 4 - - - - - IPIDR - IPIDR - Identification register - 0x3F8 - 0x20 - read-only - 0x000E0001 - - - IPID - IP Identification - 0 - 32 - - - - - SIDR - SIDR - Size ID register - 0x3FC - 0x20 - read-only - 0xA3C5DD01 - - - SID - Size Identification - 0 - 32 - - - - - - - CRS - Clock recovery system - CRS - 0x40006000 - - 0x0 - 0x400 - registers - - - CRS_IT - CRS interrupt - 42 - - - - CR - CR - CRS control register - 0x0 - 0x20 - read-write - 0x00002000 - - - SYNCOKIE - SYNC event OK interrupt enable - 0 - 1 - - - SYNCWARNIE - SYNC warning interrupt enable - 1 - 1 - - - ERRIE - Synchronization or trimming error interrupt enable - 2 - 1 - - - ESYNCIE - Expected SYNC interrupt enable - 3 - 1 - - - CEN - Frequency error counter enable - 5 - 1 - - - AUTOTRIMEN - Automatic trimming enable - 6 - 1 - - - SWSYNC - Automatic trimming enable - 7 - 1 - - - TRIM - HSI48 oscillator smooth trimming - 8 - 6 - - - - - CFGR - CFGR - CRS configuration register - 0x4 - 0x20 - read-write - 0x2022BB7F - - - RELOAD - Counter reload value - 0 - 16 - - - FELIM - Frequency error limit - 16 - 8 - - - SYNCDIV - SYNCDIV - 24 - 3 - - - SYNCSRC - SYNC signal source selection - 28 - 2 - - - SYNCPOL - SYNC polarity selection - 31 - 1 - - - - - ISR - ISR - CRS interrupt and status register - 0x8 - 0x20 - read-only - 0x00000000 - - - SYNCOKF - SYNC event OK flag - 0 - 1 - - - SYNCWARNF - SYNC warning flag - 1 - 1 - - - ERRF - Error flag - 2 - 1 - - - ESYNCF - Expected SYNC flag - 3 - 1 - - - SYNCERR - SYNC error - 8 - 1 - - - SYNCMISS - SYNC missed - 9 - 1 - - - TRIMOVF - Trimming overflow or underflow - 10 - 1 - - - FEDIR - Frequency error direction - 15 - 1 - - - FECAP - Frequency error capture - 16 - 16 - - - - - ICR - ICR - CRS interrupt flag clear register - 0xC - 0x20 - read-write - 0x00000000 - - - SYNCOKC - SYNC event OK clear flag - 0 - 1 - - - SYNCWARNC - warning clear flag - 1 - 1 - - - ERRC - Error clear flag - 2 - 1 - - - ESYNCC - Expected SYNC clear flag - 3 - 1 - - - - - - - USB - Universal serial bus full-speed device interface - USB - 0x40006800 - - 0x0 - 0x800 - registers - - - USB_HP - USB high priority interrupt - 19 - - - USB_LP - USB low priority interrupt (including USB - wakeup) - 20 - - - - EP0R - EP0R - endpoint 0 register - 0x0 - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP1R - EP1R - endpoint 1 register - 0x4 - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP2R - EP2R - endpoint 2 register - 0x8 - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP3R - EP3R - endpoint 3 register - 0xC - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP4R - EP4R - endpoint 4 register - 0x10 - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP5R - EP5R - endpoint 5 register - 0x14 - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP6R - EP6R - endpoint 6 register - 0x18 - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - EP7R - EP7R - endpoint 7 register - 0x1C - 0x10 - read-write - 0x00000000 - - - EA - Endpoint address - 0 - 4 - - - STAT_TX - Status bits, for transmission transfers - 4 - 2 - - - DTOG_TX - Data Toggle, for transmission transfers - 6 - 1 - - - CTR_TX - Correct Transfer for transmission - 7 - 1 - - - EP_KIND - Endpoint kind - 8 - 1 - - - EP_TYPE - Endpoint type - 9 - 2 - - - SETUP - Setup transaction completed - 11 - 1 - - - STAT_RX - Status bits, for reception transfers - 12 - 2 - - - DTOG_RX - Data Toggle, for reception transfers - 14 - 1 - - - CTR_RX - Correct transfer for reception - 15 - 1 - - - - - CNTR - CNTR - control register - 0x40 - 0x10 - read-write - 0x00000003 - - - FRES - Force USB Reset - 0 - 1 - - - PDWN - Power down - 1 - 1 - - - LPMODE - Low-power mode - 2 - 1 - - - FSUSP - Force suspend - 3 - 1 - - - RESUME - Resume request - 4 - 1 - - - L1RESUME - LPM L1 Resume request - 5 - 1 - - - L1REQM - LPM L1 state request interrupt mask - 7 - 1 - - - ESOFM - Expected start of frame interrupt mask - 8 - 1 - - - SOFM - Start of frame interrupt mask - 9 - 1 - - - RESETM - USB reset interrupt mask - 10 - 1 - - - SUSPM - Suspend mode interrupt mask - 11 - 1 - - - WKUPM - Wakeup interrupt mask - 12 - 1 - - - ERRM - Error interrupt mask - 13 - 1 - - - PMAOVRM - Packet memory area over / underrun interrupt mask - 14 - 1 - - - CTRM - Correct transfer interrupt mask - 15 - 1 - - - - - ISTR - ISTR - interrupt status register - 0x44 - 0x10 - 0x00000000 - - - EP_ID - Endpoint Identifier - 0 - 4 - read-only - - - DIR - Direction of transaction - 4 - 1 - read-only - - - L1REQ - LPM L1 state request - 7 - 1 - read-write - - - ESOF - Expected start frame - 8 - 1 - read-write - - - SOF - start of frame - 9 - 1 - read-write - - - RESET - reset request - 10 - 1 - read-write - - - SUSP - Suspend mode request - 11 - 1 - read-write - - - WKUP - Wakeup - 12 - 1 - read-write - - - ERR - Error - 13 - 1 - read-write - - - PMAOVR - Packet memory area over / underrun - 14 - 1 - read-write - - - CTR - Correct transfer - 15 - 1 - read-only - - - - - FNR - FNR - frame number register - 0x48 - 0x10 - read-only - 0x0000 - - - FN - Frame number - 0 - 11 - - - LSOF - Lost SOF - 11 - 2 - - - LCK - Locked - 13 - 1 - - - RXDM - Receive data - line status - 14 - 1 - - - RXDP - Receive data + line status - 15 - 1 - - - - - DADDR - DADDR - device address - 0x4C - 0x10 - read-write - 0x0000 - - - ADD - Device address - 0 - 7 - - - EF - Enable function - 7 - 1 - - - - - BTABLE - BTABLE - Buffer table address - 0x50 - 0x10 - read-write - 0x0000 - - - BTABLE - Buffer table - 3 - 13 - - - - - COUNT0_TX - COUNT0_TX - Transmission byte count 0 - 0x52 - 0x10 - read-write - 0x0000 - - - COUNT0_TX - Transmission byte count - 0 - 10 - - - - - COUNT1_TX - COUNT1_TX - Transmission byte count 0 - 0x5A - 0x10 - read-write - 0x0000 - - - COUNT1_TX - Transmission byte count - 0 - 10 - - - - - COUNT2_TX - COUNT2_TX - Transmission byte count 0 - 0x62 - 0x10 - read-write - 0x0000 - - - COUNT2_TX - Transmission byte count - 0 - 10 - - - - - COUNT3_TX - COUNT3_TX - Transmission byte count 0 - 0x6A - 0x10 - read-write - 0x0000 - - - COUNT3_TX - Transmission byte count - 0 - 10 - - - - - COUNT4_TX - COUNT4_TX - Transmission byte count 0 - 0x72 - 0x10 - read-write - 0x0000 - - - COUNT4_TX - Transmission byte count - 0 - 10 - - - - - COUNT5_TX - COUNT5_TX - Transmission byte count 0 - 0x7A - 0x10 - read-write - 0x0000 - - - COUNT5_TX - Transmission byte count - 0 - 10 - - - - - COUNT6_TX - COUNT6_TX - Transmission byte count 0 - 0x82 - 0x10 - read-write - 0x0000 - - - COUNT6_TX - Transmission byte count - 0 - 10 - - - - - COUNT7_TX - COUNT7_TX - Transmission byte count 0 - 0x8A - 0x10 - read-write - 0x0000 - - - COUNT7_TX - Transmission byte count - 0 - 10 - - - - - ADDR0_RX - ADDR0_RX - Reception buffer address 0 - 0x54 - 0x10 - read-write - 0x0000 - - - ADDR0_RX - Reception buffer address - 1 - 15 - - - - - ADDR1_RX - ADDR1_RX - Reception buffer address 0 - 0x5C - 0x10 - read-write - 0x0000 - - - ADDR1_RX - Reception buffer address - 1 - 15 - - - - - ADDR2_RX - ADDR2_RX - Reception buffer address 0 - 0x64 - 0x10 - read-write - 0x0000 - - - ADDR2_RX - Reception buffer address - 1 - 15 - - - - - ADDR3_RX - ADDR3_RX - Reception buffer address 0 - 0x6C - 0x10 - read-write - 0x0000 - - - ADDR3_RX - Reception buffer address - 1 - 15 - - - - - ADDR4_RX - ADDR4_RX - Reception buffer address 0 - 0x74 - 0x10 - read-write - 0x0000 - - - ADDR4_RX - Reception buffer address - 1 - 15 - - - - - ADDR5_RX - ADDR5_RX - Reception buffer address 0 - 0x7C - 0x10 - read-write - 0x0000 - - - ADDR5_RX - Reception buffer address - 1 - 15 - - - - - ADDR6_RX - ADDR6_RX - Reception buffer address 0 - 0x84 - 0x10 - read-write - 0x0000 - - - ADDR6_RX - Reception buffer address - 1 - 15 - - - - - ADDR7_RX - ADDR7_RX - Reception buffer address 0 - 0x8C - 0x10 - read-write - 0x0000 - - - ADDR7_RX - Reception buffer address - 1 - 15 - - - - - COUNT0_RX - COUNT0_RX - Reception byte count 0 - 0x56 - 0x10 - 0x0000 - - - COUNT0_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT1_RX - COUNT1_RX - Reception byte count 0 - 0x5E - 0x10 - 0x0000 - - - COUNT1_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT2_RX - COUNT2_RX - Reception byte count 0 - 0x66 - 0x10 - 0x0000 - - - COUNT2_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT3_RX - COUNT3_RX - Reception byte count 0 - 0x6E - 0x10 - 0x0000 - - - COUNT3_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT4_RX - COUNT4_RX - Reception byte count 0 - 0x76 - 0x10 - 0x0000 - - - COUNT4_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT5_RX - COUNT5_RX - Reception byte count 0 - 0x7E - 0x10 - 0x0000 - - - COUNT5_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT6_RX - COUNT6_RX - Reception byte count 0 - 0x86 - 0x10 - 0x0000 - - - COUNT6_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - COUNT7_RX - COUNT7_RX - Reception byte count 0 - 0x8E - 0x10 - 0x0000 - - - COUNT7_RX - Reception byte count - 0 - 10 - read-only - - - NUM_BLOCK - Number of blocks - 10 - 5 - read-write - - - BL_SIZE - Block size - 15 - 1 - read-write - - - - - LPMCSR - LPMCSR - control and status register - ADDR0_RX - 0x54 - 0x10 - 0x0000 - - - LPMEN - LPM support enable - 0 - 1 - read-write - - - LPMACK - LPM Token acknowledge enable - 1 - 1 - read-write - - - REMWAKE - RemoteWake value - 3 - 1 - read-write - - - BESL - BESL value - 4 - 4 - read-only - - - - - BCDR - BCDR - Battery charging detector( - 0x58 - 0x10 - 0x0000 - - - BCDEN - Battery charging detector (BCD) enable - 0 - 1 - read-write - - - DCDEN - Data contact detection (DCD) mode enable - 1 - 1 - read-write - - - PDEN - Primary detection (PD) mode enable - 2 - 1 - read-write - - - SDEN - Secondary detection (SD) mode enable - 3 - 1 - read-write - - - DCDET - Data contact detection (DCD) status - 4 - 1 - read-only - - - PDET - Primary detection (PD) status - 5 - 1 - read-only - - - SDET - Secondary detection (SD) status - 6 - 1 - read-only - - - PS2DET - DM pull-up detection status - 7 - 1 - read-only - - - DPPU - DP pull-up control - 15 - 1 - read-write - - - - - - - SCB - System control block - SCB - 0xE000ED00 - - 0x0 - 0x41 - registers - - - - CPUID - CPUID - CPUID base register - 0x0 - 0x20 - read-only - 0x410FC241 - - - Revision - Revision number - 0 - 4 - - - PartNo - Part number of the processor - 4 - 12 - - - Constant - Reads as 0xF - 16 - 4 - - - Variant - Variant number - 20 - 4 - - - Implementer - Implementer code - 24 - 8 - - - - - ICSR - ICSR - Interrupt control and state register - 0x4 - 0x20 - read-write - 0x00000000 - - - VECTACTIVE - Active vector - 0 - 9 - - - RETTOBASE - Return to base level - 11 - 1 - - - VECTPENDING - Pending vector - 12 - 7 - - - ISRPENDING - Interrupt pending flag - 22 - 1 - - - PENDSTCLR - SysTick exception clear-pending bit - 25 - 1 - - - PENDSTSET - SysTick exception set-pending bit - 26 - 1 - - - PENDSVCLR - PendSV clear-pending bit - 27 - 1 - - - PENDSVSET - PendSV set-pending bit - 28 - 1 - - - NMIPENDSET - NMI set-pending bit. - 31 - 1 - - - - - VTOR - VTOR - Vector table offset register - 0x8 - 0x20 - read-write - 0x00000000 - - - TBLOFF - Vector table base offset field - 9 - 21 - - - - - AIRCR - AIRCR - Application interrupt and reset control register - 0xC - 0x20 - read-write - 0x00000000 - - - VECTRESET - VECTRESET - 0 - 1 - - - VECTCLRACTIVE - VECTCLRACTIVE - 1 - 1 - - - SYSRESETREQ - SYSRESETREQ - 2 - 1 - - - PRIGROUP - PRIGROUP - 8 - 3 - - - ENDIANESS - ENDIANESS - 15 - 1 - - - VECTKEYSTAT - Register key - 16 - 16 - - - - - SCR - SCR - System control register - 0x10 - 0x20 - read-write - 0x00000000 - - - SLEEPONEXIT - SLEEPONEXIT - 1 - 1 - - - SLEEPDEEP - SLEEPDEEP - 2 - 1 - - - SEVEONPEND - Send Event on Pending bit - 4 - 1 - - - - - CCR - CCR - Configuration and control register - 0x14 - 0x20 - read-write - 0x00000000 - - - NONBASETHRDENA - Configures how the processor enters Thread mode - 0 - 1 - - - USERSETMPEND - USERSETMPEND - 1 - 1 - - - UNALIGN__TRP - UNALIGN_ TRP - 3 - 1 - - - DIV_0_TRP - DIV_0_TRP - 4 - 1 - - - BFHFNMIGN - BFHFNMIGN - 8 - 1 - - - STKALIGN - STKALIGN - 9 - 1 - - - - - SHPR1 - SHPR1 - System handler priority registers - 0x18 - 0x20 - read-write - 0x00000000 - - - PRI_4 - Priority of system handler 4 - 0 - 8 - - - PRI_5 - Priority of system handler 5 - 8 - 8 - - - PRI_6 - Priority of system handler 6 - 16 - 8 - - - - - SHPR2 - SHPR2 - System handler priority registers - 0x1C - 0x20 - read-write - 0x00000000 - - - PRI_11 - Priority of system handler 11 - 24 - 8 - - - - - SHPR3 - SHPR3 - System handler priority registers - 0x20 - 0x20 - read-write - 0x00000000 - - - PRI_14 - Priority of system handler 14 - 16 - 8 - - - PRI_15 - Priority of system handler 15 - 24 - 8 - - - - - SHCSR - SHCSR - System handler control and state register - 0x24 - 0x20 - read-write - 0x00000000 - - - MEMFAULTACT - Memory management fault exception active bit - 0 - 1 - - - BUSFAULTACT - Bus fault exception active bit - 1 - 1 - - - USGFAULTACT - Usage fault exception active bit - 3 - 1 - - - SVCALLACT - SVC call active bit - 7 - 1 - - - MONITORACT - Debug monitor active bit - 8 - 1 - - - PENDSVACT - PendSV exception active bit - 10 - 1 - - - SYSTICKACT - SysTick exception active bit - 11 - 1 - - - USGFAULTPENDED - Usage fault exception pending bit - 12 - 1 - - - MEMFAULTPENDED - Memory management fault exception pending bit - 13 - 1 - - - BUSFAULTPENDED - Bus fault exception pending bit - 14 - 1 - - - SVCALLPENDED - SVC call pending bit - 15 - 1 - - - MEMFAULTENA - Memory management fault enable bit - 16 - 1 - - - BUSFAULTENA - Bus fault enable bit - 17 - 1 - - - USGFAULTENA - Usage fault enable bit - 18 - 1 - - - - - CFSR_UFSR_BFSR_MMFSR - CFSR_UFSR_BFSR_MMFSR - Configurable fault status register - 0x28 - 0x20 - read-write - 0x00000000 - - - IACCVIOL - Instruction access violation flag - 1 - 1 - - - MUNSTKERR - Memory manager fault on unstacking for a return from exception - 3 - 1 - - - MSTKERR - Memory manager fault on stacking for exception entry. - 4 - 1 - - - MLSPERR - MLSPERR - 5 - 1 - - - MMARVALID - Memory Management Fault Address Register (MMAR) valid flag - 7 - 1 - - - IBUSERR - Instruction bus error - 8 - 1 - - - PRECISERR - Precise data bus error - 9 - 1 - - - IMPRECISERR - Imprecise data bus error - 10 - 1 - - - UNSTKERR - Bus fault on unstacking for a return from exception - 11 - 1 - - - STKERR - Bus fault on stacking for exception entry - 12 - 1 - - - LSPERR - Bus fault on floating-point lazy state preservation - 13 - 1 - - - BFARVALID - Bus Fault Address Register (BFAR) valid flag - 15 - 1 - - - UNDEFINSTR - Undefined instruction usage fault - 16 - 1 - - - INVSTATE - Invalid state usage fault - 17 - 1 - - - INVPC - Invalid PC load usage fault - 18 - 1 - - - NOCP - No coprocessor usage fault. - 19 - 1 - - - UNALIGNED - Unaligned access usage fault - 24 - 1 - - - DIVBYZERO - Divide by zero usage fault - 25 - 1 - - - - - HFSR - HFSR - Hard fault status register - 0x2C - 0x20 - read-write - 0x00000000 - - - VECTTBL - Vector table hard fault - 1 - 1 - - - FORCED - Forced hard fault - 30 - 1 - - - DEBUG_VT - Reserved for Debug use - 31 - 1 - - - - - MMFAR - MMFAR - Memory management fault address register - 0x34 - 0x20 - read-write - 0x00000000 - - - MMFAR - Memory management fault address - 0 - 32 - - - - - BFAR - BFAR - Bus fault address register - 0x38 - 0x20 - read-write - 0x00000000 - - - BFAR - Bus fault address - 0 - 32 - - - - - AFSR - AFSR - Auxiliary fault status register - 0x3C - 0x20 - read-write - 0x00000000 - - - IMPDEF - Implementation defined - 0 - 32 - - - - - - - STK - SysTick timer - STK - 0xE000E010 - - 0x0 - 0x11 - registers - - - - CTRL - CTRL - SysTick control and status register - 0x0 - 0x20 - read-write - 0X00000000 - - - ENABLE - Counter enable - 0 - 1 - - - TICKINT - SysTick exception request enable - 1 - 1 - - - CLKSOURCE - Clock source selection - 2 - 1 - - - COUNTFLAG - COUNTFLAG - 16 - 1 - - - - - LOAD - LOAD - SysTick reload value register - 0x4 - 0x20 - read-write - 0X00000000 - - - RELOAD - RELOAD value - 0 - 24 - - - - - VAL - VAL - SysTick current value register - 0x8 - 0x20 - read-write - 0X00000000 - - - CURRENT - Current counter value - 0 - 24 - - - - - CALIB - CALIB - SysTick calibration value register - 0xC - 0x20 - read-write - 0X00000000 - - - TENMS - Calibration value - 0 - 24 - - - SKEW - SKEW flag: Indicates whether the TENMS value is exact - 30 - 1 - - - NOREF - NOREF flag. Reads as zero - 31 - 1 - - - - - - - MPU - Memory protection unit - MPU - 0xE000ED90 - - 0x0 - 0x15 - registers - - - - MPU_TYPER - MPU_TYPER - MPU type register - 0x0 - 0x20 - read-only - 0X00000800 - - - SEPARATE - Separate flag - 0 - 1 - - - DREGION - Number of MPU data regions - 8 - 8 - - - IREGION - Number of MPU instruction regions - 16 - 8 - - - - - MPU_CTRL - MPU_CTRL - MPU control register - 0x4 - 0x20 - read-only - 0X00000000 - - - ENABLE - Enables the MPU - 0 - 1 - - - HFNMIENA - Enables the operation of MPU during hard fault - 1 - 1 - - - PRIVDEFENA - Enable priviliged software access to default memory map - 2 - 1 - - - - - MPU_RNR - MPU_RNR - MPU region number register - 0x8 - 0x20 - read-write - 0X00000000 - - - REGION - MPU region - 0 - 8 - - - - - MPU_RBAR - MPU_RBAR - MPU region base address register - 0xC - 0x20 - read-write - 0X00000000 - - - REGION - MPU region field - 0 - 4 - - - VALID - MPU region number valid - 4 - 1 - - - ADDR - Region base address field - 5 - 27 - - - - - MPU_RASR - MPU_RASR - MPU region attribute and size register - 0x10 - 0x20 - read-write - 0X00000000 - - - ENABLE - Region enable bit. - 0 - 1 - - - SIZE - Size of the MPU protection region - 1 - 5 - - - SRD - Subregion disable bits - 8 - 8 - - - B - memory attribute - 16 - 1 - - - C - memory attribute - 17 - 1 - - - S - Shareable memory attribute - 18 - 1 - - - TEX - memory attribute - 19 - 3 - - - AP - Access permission - 24 - 3 - - - XN - Instruction access disable bit - 28 - 1 - - - - - - - FPU - Floting point unit - FPU - 0xE000EF34 - - 0x0 - 0xD - registers - - - FPU - Floating point unit interrupt - 54 - - - - FPCCR - FPCCR - Floating-point context control register - 0x0 - 0x20 - read-write - 0x00000000 - - - LSPACT - LSPACT - 0 - 1 - - - USER - USER - 1 - 1 - - - THREAD - THREAD - 3 - 1 - - - HFRDY - HFRDY - 4 - 1 - - - MMRDY - MMRDY - 5 - 1 - - - BFRDY - BFRDY - 6 - 1 - - - MONRDY - MONRDY - 8 - 1 - - - LSPEN - LSPEN - 30 - 1 - - - ASPEN - ASPEN - 31 - 1 - - - - - FPCAR - FPCAR - Floating-point context address register - 0x4 - 0x20 - read-write - 0x00000000 - - - ADDRESS - Location of unpopulated floating-point - 3 - 29 - - - - - FPSCR - FPSCR - Floating-point status control register - 0x8 - 0x20 - read-write - 0x00000000 - - - IOC - Invalid operation cumulative exception bit - 0 - 1 - - - DZC - Division by zero cumulative exception bit. - 1 - 1 - - - OFC - Overflow cumulative exception bit - 2 - 1 - - - UFC - Underflow cumulative exception bit - 3 - 1 - - - IXC - Inexact cumulative exception bit - 4 - 1 - - - IDC - Input denormal cumulative exception bit. - 7 - 1 - - - RMode - Rounding Mode control field - 22 - 2 - - - FZ - Flush-to-zero mode control bit: - 24 - 1 - - - DN - Default NaN mode control bit - 25 - 1 - - - AHP - Alternative half-precision control bit - 26 - 1 - - - V - Overflow condition code flag - 28 - 1 - - - C - Carry condition code flag - 29 - 1 - - - Z - Zero condition code flag - 30 - 1 - - - N - Negative condition code flag - 31 - 1 - - - - - - - NVIC - Nested Vectored Interrupt Controller - NVIC - 0xE000E100 - - 0x0 - 0x351 - registers - - - - ISER0 - ISER0 - Interrupt Set-Enable Register - 0x0 - 0x20 - read-write - 0x00000000 - - - SETENA - SETENA - 0 - 32 - - - - - ISER1 - ISER1 - Interrupt Set-Enable Register - 0x4 - 0x20 - read-write - 0x00000000 - - - SETENA - SETENA - 0 - 32 - - - - - ICER0 - ICER0 - Interrupt Clear-Enable Register - 0x80 - 0x20 - read-write - 0x00000000 - - - CLRENA - CLRENA - 0 - 32 - - - - - ICER1 - ICER1 - Interrupt Clear-Enable Register - 0x84 - 0x20 - read-write - 0x00000000 - - - CLRENA - CLRENA - 0 - 32 - - - - - ISPR0 - ISPR0 - Interrupt Set-Pending Register - 0x100 - 0x20 - read-write - 0x00000000 - - - SETPEND - SETPEND - 0 - 32 - - - - - ISPR1 - ISPR1 - Interrupt Set-Pending Register - 0x104 - 0x20 - read-write - 0x00000000 - - - SETPEND - SETPEND - 0 - 32 - - - - - ICPR0 - ICPR0 - Interrupt Clear-Pending Register - 0x180 - 0x20 - read-write - 0x00000000 - - - CLRPEND - CLRPEND - 0 - 32 - - - - - ICPR1 - ICPR1 - Interrupt Clear-Pending Register - 0x184 - 0x20 - read-write - 0x00000000 - - - CLRPEND - CLRPEND - 0 - 32 - - - - - IABR0 - IABR0 - Interrupt Active Bit Register - 0x200 - 0x20 - read-only - 0x00000000 - - - ACTIVE - ACTIVE - 0 - 32 - - - - - IABR1 - IABR1 - Interrupt Active Bit Register - 0x204 - 0x20 - read-only - 0x00000000 - - - ACTIVE - ACTIVE - 0 - 32 - - - - - IPR0 - IPR0 - Interrupt Priority Register - 0x300 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR1 - IPR1 - Interrupt Priority Register - 0x304 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR2 - IPR2 - Interrupt Priority Register - 0x308 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR3 - IPR3 - Interrupt Priority Register - 0x30C - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR4 - IPR4 - Interrupt Priority Register - 0x310 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR5 - IPR5 - Interrupt Priority Register - 0x314 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR6 - IPR6 - Interrupt Priority Register - 0x318 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR7 - IPR7 - Interrupt Priority Register - 0x31C - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR8 - IPR8 - Interrupt Priority Register - 0x320 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR9 - IPR9 - Interrupt Priority Register - 0x324 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR10 - IPR10 - Interrupt Priority Register - 0x328 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR11 - IPR11 - Interrupt Priority Register - 0x32C - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR12 - IPR12 - Interrupt Priority Register - 0x330 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR13 - IPR13 - Interrupt Priority Register - 0x334 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR14 - IPR14 - Interrupt Priority Register - 0x338 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR15 - IPR15 - Interrupt Priority Register - 0x33C - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR16 - IPR16 - Interrupt Priority Register - 0x340 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - IPR17 - IPR17 - Interrupt Priority Register - 0x344 - 0x20 - read-write - 0x00000000 - - - IPR_N0 - IPR_N0 - 0 - 8 - - - IPR_N1 - IPR_N1 - 8 - 8 - - - IPR_N2 - IPR_N2 - 16 - 8 - - - IPR_N3 - IPR_N3 - 24 - 8 - - - - - - - NVIC_STIR - Nested vectored interrupt controller - NVIC - 0xE000EF00 - - 0x0 - 0x5 - registers - - - - STIR - STIR - Software trigger interrupt register - 0x0 - 0x20 - read-write - 0x00000000 - - - INTID - Software generated interrupt ID - 0 - 9 - - - - - - - SCB_ACTRL - System control block ACTLR - SCB - 0xE000E008 - - 0x0 - 0x5 - registers - - - - ACTRL - ACTRL - Auxiliary control register - 0x0 - 0x20 - read-write - 0x00000000 - - - DISMCYCINT - DISMCYCINT - 0 - 1 - - - DISDEFWBUF - DISDEFWBUF - 1 - 1 - - - DISFOLD - DISFOLD - 2 - 1 - - - DISFPCA - DISFPCA - 8 - 1 - - - DISOOFP - DISOOFP - 9 - 1 - - - - - - - FPU_CPACR - Floating point unit CPACR - FPU - 0xE000ED88 - - 0x0 - 0x5 - registers - - - - CPACR - CPACR - Coprocessor access control register - 0x0 - 0x20 - read-write - 0x0000000 - - - CP - CP - 20 - 4 - - - - - - - +STM32WB55_CM41.9STM32WB55_CM4CM4r0p1littletruetrue4false8320x200x00xFFFFFFFFDMA1Direct memory access controllerDMA0x400200000x00x400registersDMA1_Channel1DMA1 Channel1 global interrupt11DMA1_Channel2DMA1 Channel2 global interrupt12DMA1_Channel3DMA1 Channel3 interrupt13DMA1_Channel4DMA1 Channel4 interrupt14DMA1_Channel5DMA1 Channel5 interrupt15DMA1_Channel6DMA1 Channel6 interrupt16DMA1_Channel7DMA1 Channel 7 interrupt17ISRISRinterrupt status register0x00x20read-only0x00000000TEIF7Channel x transfer error flag (x = 1 ..7)271HTIF7Channel x half transfer flag (x = 1 ..7)261TCIF7Channel x transfer complete flag (x = 1 ..7)251GIF7Channel x global interrupt flag (x = 1 ..7)241TEIF6Channel x transfer error flag (x = 1 ..7)231HTIF6Channel x half transfer flag (x = 1 ..7)221TCIF6Channel x transfer complete flag (x = 1 ..7)211GIF6Channel x global interrupt flag (x = 1 ..7)201TEIF5Channel x transfer error flag (x = 1 ..7)191HTIF5Channel x half transfer flag (x = 1 ..7)181TCIF5Channel x transfer complete flag (x = 1 ..7)171GIF5Channel x global interrupt flag (x = 1 ..7)161TEIF4Channel x transfer error flag (x = 1 ..7)151HTIF4Channel x half transfer flag (x = 1 ..7)141TCIF4Channel x transfer complete flag (x = 1 ..7)131GIF4Channel x global interrupt flag (x = 1 ..7)121TEIF3Channel x transfer error flag (x = 1 ..7)111HTIF3Channel x half transfer flag (x = 1 ..7)101TCIF3Channel x transfer complete flag (x = 1 ..7)91GIF3Channel x global interrupt flag (x = 1 ..7)81TEIF2Channel x transfer error flag (x = 1 ..7)71HTIF2Channel x half transfer flag (x = 1 ..7)61TCIF2Channel x transfer complete flag (x = 1 ..7)51GIF2Channel x global interrupt flag (x = 1 ..7)41TEIF1Channel x transfer error flag (x = 1 ..7)31HTIF1Channel x half transfer flag (x = 1 ..7)21TCIF1Channel x transfer complete flag (x = 1 ..7)11GIF1Channel x global interrupt flag (x = 1 ..7)01IFCRIFCRinterrupt flag clear register0x40x20write-only0x00000000CTEIF7Channel x transfer error clear (x = 1 ..7)271CHTIF7Channel x half transfer clear (x = 1 ..7)261CTCIF7Channel x transfer complete clear (x = 1 ..7)251CGIF7Channel x global interrupt clear (x = 1 ..7)241CTEIF6Channel x transfer error clear (x = 1 ..7)231CHTIF6Channel x half transfer clear (x = 1 ..7)221CTCIF6Channel x transfer complete clear (x = 1 ..7)211CGIF6Channel x global interrupt clear (x = 1 ..7)201CTEIF5Channel x transfer error clear (x = 1 ..7)191CHTIF5Channel x half transfer clear (x = 1 ..7)181CTCIF5Channel x transfer complete clear (x = 1 ..7)171CGIF5Channel x global interrupt clear (x = 1 ..7)161CTEIF4Channel x transfer error clear (x = 1 ..7)151CHTIF4Channel x half transfer clear (x = 1 ..7)141CTCIF4Channel x transfer complete clear (x = 1 ..7)131CGIF4Channel x global interrupt clear (x = 1 ..7)121CTEIF3Channel x transfer error clear (x = 1 ..7)111CHTIF3Channel x half transfer clear (x = 1 ..7)101CTCIF3Channel x transfer complete clear (x = 1 ..7)91CGIF3Channel x global interrupt clear (x = 1 ..7)81CTEIF2Channel x transfer error clear (x = 1 ..7)71CHTIF2Channel x half transfer clear (x = 1 ..7)61CTCIF2Channel x transfer complete clear (x = 1 ..7)51CGIF2Channel x global interrupt clear (x = 1 ..7)41CTEIF1Channel x transfer error clear (x = 1 ..7)31CHTIF1Channel x half transfer clear (x = 1 ..7)21CTCIF1Channel x transfer complete clear (x = 1 ..7)11CGIF1Channel x global interrupt clear (x = 1 ..7)01CCR1CCR1channel x configuration register0x80x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR1CNDTR1channel x number of data register0xC0x20read-write0x00000000NDTNumber of data to transfer016CPAR1CPAR1channel x peripheral address register0x100x20read-write0x00000000PAPeripheral address032CMAR1CMAR1channel x memory address register0x140x20read-write0x00000000MAMemory address032CCR2CCR2channel x configuration register0x1C0x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR2CNDTR2channel x number of data register0x200x20read-write0x00000000NDTNumber of data to transfer016CPAR2CPAR2channel x peripheral address register0x240x20read-write0x00000000PAPeripheral address032CMAR2CMAR2channel x memory address register0x280x20read-write0x00000000MAMemory address032CCR3CCR3channel x configuration register0x300x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR3CNDTR3channel x number of data register0x340x20read-write0x00000000NDTNumber of data to transfer016CPAR3CPAR3channel x peripheral address register0x380x20read-write0x00000000PAPeripheral address032CMAR3CMAR3channel x memory address register0x3C0x20read-write0x00000000MAMemory address032CCR4CCR4channel x configuration register0x440x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR4CNDTR4channel x number of data register0x480x20read-write0x00000000NDTNumber of data to transfer016CPAR4CPAR4channel x peripheral address register0x4C0x20read-write0x00000000PAPeripheral address032CMAR4CMAR4channel x memory address register0x500x20read-write0x00000000MAMemory address032CCR5CCR5channel x configuration register0x580x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR5CNDTR5channel x number of data register0x5C0x20read-write0x00000000NDTNumber of data to transfer016CPAR5CPAR5channel x peripheral address register0x600x20read-write0x00000000PAPeripheral address032CMAR5CMAR5channel x memory address register0x640x20read-write0x00000000MAMemory address032CCR6CCR6channel x configuration register0x6C0x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR6CNDTR6channel x number of data register0x700x20read-write0x00000000NDTNumber of data to transfer016CPAR6CPAR6channel x peripheral address register0x740x20read-write0x00000000PAPeripheral address032CMAR6CMAR6channel x memory address register0x780x20read-write0x00000000MAMemory address032CCR7CCR7channel x configuration register0x800x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR7CNDTR7channel x number of data register0x840x20read-write0x00000000NDTNumber of data to transfer016CPAR7CPAR7channel x peripheral address register0x880x20read-write0x00000000PAPeripheral address032CMAR7CMAR7channel x memory address register0x8C0x20read-write0x00000000MAMemory address032DMA2Direct memory access controllerDMA0x400204000x00x400registersDMA2_CH1DMA2 channel 1 interrupt55DMA2_CH2DMA2 channel 2 interrupt56DMA2_CH3DMA2 channel 3 interrupt57DMA2_CH4DMA2 channel 4 interrupt58DMA2_CH5DMA2 channel 5 interrupt59DMA2_CH6DMA2 channel 6 interrupt60DMA2_CH7DMA2 channel 7 interrupt61ISRISRinterrupt status register0x00x20read-only0x00000000TEIF7Channel x transfer error flag (x = 1 ..7)271HTIF7Channel x half transfer flag (x = 1 ..7)261TCIF7Channel x transfer complete flag (x = 1 ..7)251GIF7Channel x global interrupt flag (x = 1 ..7)241TEIF6Channel x transfer error flag (x = 1 ..7)231HTIF6Channel x half transfer flag (x = 1 ..7)221TCIF6Channel x transfer complete flag (x = 1 ..7)211GIF6Channel x global interrupt flag (x = 1 ..7)201TEIF5Channel x transfer error flag (x = 1 ..7)191HTIF5Channel x half transfer flag (x = 1 ..7)181TCIF5Channel x transfer complete flag (x = 1 ..7)171GIF5Channel x global interrupt flag (x = 1 ..7)161TEIF4Channel x transfer error flag (x = 1 ..7)151HTIF4Channel x half transfer flag (x = 1 ..7)141TCIF4Channel x transfer complete flag (x = 1 ..7)131GIF4Channel x global interrupt flag (x = 1 ..7)121TEIF3Channel x transfer error flag (x = 1 ..7)111HTIF3Channel x half transfer flag (x = 1 ..7)101TCIF3Channel x transfer complete flag (x = 1 ..7)91GIF3Channel x global interrupt flag (x = 1 ..7)81TEIF2Channel x transfer error flag (x = 1 ..7)71HTIF2Channel x half transfer flag (x = 1 ..7)61TCIF2Channel x transfer complete flag (x = 1 ..7)51GIF2Channel x global interrupt flag (x = 1 ..7)41TEIF1Channel x transfer error flag (x = 1 ..7)31HTIF1Channel x half transfer flag (x = 1 ..7)21TCIF1Channel x transfer complete flag (x = 1 ..7)11GIF1Channel x global interrupt flag (x = 1 ..7)01IFCRIFCRinterrupt flag clear register0x40x20write-only0x00000000CTEIF7Channel x transfer error clear (x = 1 ..7)271CHTIF7Channel x half transfer clear (x = 1 ..7)261CTCIF7Channel x transfer complete clear (x = 1 ..7)251CGIF7Channel x global interrupt clear (x = 1 ..7)241CTEIF6Channel x transfer error clear (x = 1 ..7)231CHTIF6Channel x half transfer clear (x = 1 ..7)221CTCIF6Channel x transfer complete clear (x = 1 ..7)211CGIF6Channel x global interrupt clear (x = 1 ..7)201CTEIF5Channel x transfer error clear (x = 1 ..7)191CHTIF5Channel x half transfer clear (x = 1 ..7)181CTCIF5Channel x transfer complete clear (x = 1 ..7)171CGIF5Channel x global interrupt clear (x = 1 ..7)161CTEIF4Channel x transfer error clear (x = 1 ..7)151CHTIF4Channel x half transfer clear (x = 1 ..7)141CTCIF4Channel x transfer complete clear (x = 1 ..7)131CGIF4Channel x global interrupt clear (x = 1 ..7)121CTEIF3Channel x transfer error clear (x = 1 ..7)111CHTIF3Channel x half transfer clear (x = 1 ..7)101CTCIF3Channel x transfer complete clear (x = 1 ..7)91CGIF3Channel x global interrupt clear (x = 1 ..7)81CTEIF2Channel x transfer error clear (x = 1 ..7)71CHTIF2Channel x half transfer clear (x = 1 ..7)61CTCIF2Channel x transfer complete clear (x = 1 ..7)51CGIF2Channel x global interrupt clear (x = 1 ..7)41CTEIF1Channel x transfer error clear (x = 1 ..7)31CHTIF1Channel x half transfer clear (x = 1 ..7)21CTCIF1Channel x transfer complete clear (x = 1 ..7)11CGIF1Channel x global interrupt clear (x = 1 ..7)01CCR1CCR1channel x configuration register0x80x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR1CNDTR1channel x number of data register0xC0x20read-write0x00000000NDTNumber of data to transfer016CPAR1CPAR1channel x peripheral address register0x100x20read-write0x00000000PAPeripheral address032CMAR1CMAR1channel x memory address register0x140x20read-write0x00000000MAMemory address032CCR2CCR2channel x configuration register0x1C0x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR2CNDTR2channel x number of data register0x200x20read-write0x00000000NDTNumber of data to transfer016CPAR2CPAR2channel x peripheral address register0x240x20read-write0x00000000PAPeripheral address032CMAR2CMAR2channel x memory address register0x280x20read-write0x00000000MAMemory address032CCR3CCR3channel x configuration register0x300x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR3CNDTR3channel x number of data register0x340x20read-write0x00000000NDTNumber of data to transfer016CPAR3CPAR3channel x peripheral address register0x380x20read-write0x00000000PAPeripheral address032CMAR3CMAR3channel x memory address register0x3C0x20read-write0x00000000MAMemory address032CCR4CCR4channel x configuration register0x440x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR4CNDTR4channel x number of data register0x480x20read-write0x00000000NDTNumber of data to transfer016CPAR4CPAR4channel x peripheral address register0x4C0x20read-write0x00000000PAPeripheral address032CMAR4CMAR4channel x memory address register0x500x20read-write0x00000000MAMemory address032CCR5CCR5channel x configuration register0x580x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR5CNDTR5channel x number of data register0x5C0x20read-write0x00000000NDTNumber of data to transfer016CPAR5CPAR5channel x peripheral address register0x600x20read-write0x00000000PAPeripheral address032CMAR5CMAR5channel x memory address register0x640x20read-write0x00000000MAMemory address032CCR6CCR6channel x configuration register0x6C0x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR6CNDTR6channel x number of data register0x700x20read-write0x00000000NDTNumber of data to transfer016CPAR6CPAR6channel x peripheral address register0x740x20read-write0x00000000PAPeripheral address032CMAR6CMAR6channel x memory address register0x780x20read-write0x00000000MAMemory address032CCR7CCR7channel x configuration register0x800x20read-write0x00000000MEM2MEMMemory to memory mode141PLChannel priority level122MSIZEMemory size102PSIZEPeripheral size82MINCMemory increment mode71PINCPeripheral increment mode61CIRCCircular mode51DIRData transfer direction41TEIETransfer error interrupt enable31HTIEHalf transfer interrupt enable21TCIETransfer complete interrupt enable11ENChannel enable01CNDTR7CNDTR7channel x number of data register0x840x20read-write0x00000000NDTNumber of data to transfer016CPAR7CPAR7channel x peripheral address register0x880x20read-write0x00000000PAPeripheral address032CMAR7CMAR7channel x memory address register0x8C0x20read-write0x00000000MAMemory address032CSELRCSELRchannel selection register0xA80x20read-write0x00000000C7SDMA channel 7 selection244C6SDMA channel 6 selection204C5SDMA channel 5 selection164C4SDMA channel 4 selection124C3SDMA channel 3 selection84C2SDMA channel 2 selection44C1SDMA channel 1 selection04DMAMUX1Direct memory access MultiplexerDMAMUX0x400208000x00x400registersDMAMUX_OVRDMAMUX overrun interrupt62C0CRC0CRDMA Multiplexer Channel 0 Control register0x00x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C1CRC1CRDMA Multiplexer Channel 1 Control register0x40x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C2CRC2CRDMA Multiplexer Channel 2 Control register0x80x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C3CRC3CRDMA Multiplexer Channel 3 Control register0xC0x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C4CRC4CRDMA Multiplexer Channel 4 Control register0x100x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C5CRC5CRDMA Multiplexer Channel 5 Control register0x140x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C6CRC6CRDMA Multiplexer Channel 6 Control register0x180x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C7CRC7CRDMA Multiplexer Channel 7 Control register0x1C0x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C8CRC8CRDMA Multiplexer Channel 8 Control register0x200x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C9CRC9CRDMA Multiplexer Channel 9 Control register0x240x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C10CRC10CRDMA Multiplexer Channel 10 Control register0x280x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C11CRC11CRDMA Multiplexer Channel 11 Control register0x2C0x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C12CRC12CRDMA Multiplexer Channel 12 Control register0x300x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08C13CRC13CRDMA Multiplexer Channel 13 Control register0x340x20read-write0x00000000SYNC_IDSYNC_ID245NBREQNb request195SPOLSync polarity172SESynchronization enable161EGEEvent Generation Enable91SOIESynchronization Overrun Interrupt Enable81DMAREQ_IDDMA Request ID08CSRCSRDMA Multiplexer Channel Status register0x800x20read-only0x00000000SOF0Synchronization Overrun Flag 001SOF1Synchronization Overrun Flag 111SOF2Synchronization Overrun Flag 221SOF3Synchronization Overrun Flag 331SOF4Synchronization Overrun Flag 441SOF5Synchronization Overrun Flag 551SOF6Synchronization Overrun Flag 661SOF7Synchronization Overrun Flag 771SOF8Synchronization Overrun Flag 881SOF9Synchronization Overrun Flag 991SOF10Synchronization Overrun Flag 10101SOF11Synchronization Overrun Flag 11111SOF12Synchronization Overrun Flag 12121SOF13Synchronization Overrun Flag 13131CFRCFRDMA Channel Clear Flag Register0x840x20write-only0x00000000CSOF0Synchronization Clear Overrun Flag 001CSOF1Synchronization Clear Overrun Flag 111CSOF2Synchronization Clear Overrun Flag 221CSOF3Synchronization Clear Overrun Flag 331CSOF4Synchronization Clear Overrun Flag 441CSOF5Synchronization Clear Overrun Flag 551CSOF6Synchronization Clear Overrun Flag 661CSOF7Synchronization Clear Overrun Flag 771CSOF8Synchronization Clear Overrun Flag 881CSOF9Synchronization Clear Overrun Flag 991CSOF10Synchronization Clear Overrun Flag 10101CSOF11Synchronization Clear Overrun Flag 11111CSOF12Synchronization Clear Overrun Flag 12121CSOF13Synchronization Clear Overrun Flag 13131RG0CRRG0CRDMA Request Generator 0 Control Register0x1000x20read-write0x00000000GNBREQNumber of Request195GPOLGeneration Polarity172GEGeneration Enable161OIEOverrun Interrupt Enable81SIG_IDSignal ID05RG1CRRG1CRDMA Request Generator 1 Control Register0x1040x20read-write0x00000000GNBREQNumber of Request195GPOLGeneration Polarity172GEGeneration Enable161OIEOverrun Interrupt Enable81SIG_IDSignal ID05RG2CRRG2CRDMA Request Generator 2 Control Register0x1080x20read-write0x00000000GNBREQNumber of Request195GPOLGeneration Polarity172GEGeneration Enable161OIEOverrun Interrupt Enable81SIG_IDSignal ID05RG3CRRG3CRDMA Request Generator 3 Control Register0x10C0x20read-write0x00000000GNBREQNumber of Request195GPOLGeneration Polarity172GEGeneration Enable161OIEOverrun Interrupt Enable81SIG_IDSignal ID05RGSRRGSRDMA Request Generator Status Register0x1400x20read-only0x00000000OF0Generator Overrun Flag 001OF1Generator Overrun Flag 111OF2Generator Overrun Flag 221OF3Generator Overrun Flag 331RGCFRRGCFRDMA Request Generator Clear Flag Register0x1440x20write-only0x00000000COF0Clear trigger Overrun Flag 001COF1Clear trigger Overrun Flag 111COF2Clear trigger Overrun Flag 221COF3Clear trigger Overrun Flag 331CRCCyclic redundancy check calculation unitCRC0x400230000x00x400registersDRDRData register0x00x20read-write0xFFFFFFFFDRData register bits032IDRIDRIndependent data register0x40x20read-write0x00000000IDRGeneral-purpose 32-bit data register bits032CRCRControl register0x80x20read-write0x00000000REV_OUTReverse output data71REV_INReverse input data52POLYSIZEPolynomial size32RESETRESET bit01INITINITInitial CRC value0x100x20read-write0xFFFFFFFFCRC_INITProgrammable initial CRC value032POLPOLpolynomial0x140x20read-write0x04C11DB7POLProgrammable polynomial032LCDLiquid crystal display controllerLCD0x400024000x00x400registersLCDLCD global interrupt49CRCRcontrol register0x00x20read-write0x00000000BIASBias selector52DUTYDuty selection23VSELVoltage source selection11LCDENLCD controller enable01MUX_SEGMux segment enable71BUFENVoltage output buffer enable81FCRFCRframe control register0x40x20read-write0x00000000PSPS 16-bit prescaler224DIVDIV clock divider184BLINKBlink mode selection162BLINKFBlink frequency selection133CCContrast control103DEADDead time duration73PONPulse ON duration43UDDIEUpdate display done interrupt enable31SOFIEStart of frame interrupt enable11HDHigh drive enable01SRSRstatus register0x80x200x00000020FCRSFLCD Frame Control Register Synchronization flag51read-onlyRDYReady flag41read-onlyUDDUpdate Display Done31read-onlyUDRUpdate display request21read-writeSOFStart of frame flag11read-onlyENSENS01read-onlyCLRCLRclear register0xC0x20write-only0x00000000UDDCUpdate display done clear31SOFCStart of frame flag clear11RAM_COM0RAM_COM0display memory0x140x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM1RAM_COM1display memory0x1C0x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM2RAM_COM2display memory0x240x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM3RAM_COM3display memory0x2C0x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM4RAM_COM4display memory0x340x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM5RAM_COM5display memory0x3C0x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM6RAM_COM6display memory0x440x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001RAM_COM7RAM_COM7display memory0x4C0x20read-write0x00000000S31S31311S30S30301S29S29291S28S28281S27S27271S26S26261S25S25251S24S24241S23S23231S22S22221S21S21211S20S20201S19S19191S18S18181S17S17171S16S16161S15S15151S14S14141S13S13131S12S12121S11S11111S10S10101S09S0991S08S0881S07S0771S06S0661S05S0551S04S0441S03S0331S02S0221S01S0111S00S0001TSCTouch sensing controllerTSC0x400240000x00x400registersTSCTSC global interrupt39CRCRcontrol register0x00x20read-write0x00000000CTPHCharge transfer pulse high284CTPLCharge transfer pulse low244SSDSpread spectrum deviation177SSESpread spectrum enable161SSPSCSpread spectrum prescaler151PGPSCpulse generator prescaler123MCVMax count value53IODEFI/O Default mode41SYNCPOLSynchronization pin polarity31AMAcquisition mode21STARTStart a new acquisition11TSCETouch sensing controller enable01IERIERinterrupt enable register0x40x20read-write0x00000000MCEIEMax count error interrupt enable11EOAIEEnd of acquisition interrupt enable01ICRICRinterrupt clear register0x80x20read-write0x00000000MCEICMax count error interrupt clear11EOAICEnd of acquisition interrupt clear01ISRISRinterrupt status register0xC0x20read-write0x00000000MCEFMax count error flag11EOAFEnd of acquisition flag01IOHCRIOHCRI/O hysteresis control register0x100x20read-write0xFFFFFFFFG7_IO4G7_IO4271G7_IO3G7_IO3261G7_IO2G7_IO2251G7_IO1G7_IO1241G6_IO4G6_IO4231G6_IO3G6_IO3221G6_IO2G6_IO2211G6_IO1G6_IO1201G5_IO4G5_IO4191G5_IO3G5_IO3181G5_IO2G5_IO2171G5_IO1G5_IO1161G4_IO4G4_IO4151G4_IO3G4_IO3141G4_IO2G4_IO2131G4_IO1G4_IO1121G3_IO4G3_IO4111G3_IO3G3_IO3101G3_IO2G3_IO291G3_IO1G3_IO181G2_IO4G2_IO471G2_IO3G2_IO361G2_IO2G2_IO251G2_IO1G2_IO141G1_IO4G1_IO431G1_IO3G1_IO321G1_IO2G1_IO211G1_IO1G1_IO101IOASCRIOASCRI/O analog switch control register0x180x20read-write0x00000000G7_IO4G7_IO4271G7_IO3G7_IO3261G7_IO2G7_IO2251G7_IO1G7_IO1241G6_IO4G6_IO4231G6_IO3G6_IO3221G6_IO2G6_IO2211G6_IO1G6_IO1201G5_IO4G5_IO4191G5_IO3G5_IO3181G5_IO2G5_IO2171G5_IO1G5_IO1161G4_IO4G4_IO4151G4_IO3G4_IO3141G4_IO2G4_IO2131G4_IO1G4_IO1121G3_IO4G3_IO4111G3_IO3G3_IO3101G3_IO2G3_IO291G3_IO1G3_IO181G2_IO4G2_IO471G2_IO3G2_IO361G2_IO2G2_IO251G2_IO1G2_IO141G1_IO4G1_IO431G1_IO3G1_IO321G1_IO2G1_IO211G1_IO1G1_IO101IOSCRIOSCRI/O sampling control register0x200x20read-write0x00000000G7_IO4G7_IO4271G7_IO3G7_IO3261G7_IO2G7_IO2251G7_IO1G7_IO1241G6_IO4G6_IO4231G6_IO3G6_IO3221G6_IO2G6_IO2211G6_IO1G6_IO1201G5_IO4G5_IO4191G5_IO3G5_IO3181G5_IO2G5_IO2171G5_IO1G5_IO1161G4_IO4G4_IO4151G4_IO3G4_IO3141G4_IO2G4_IO2131G4_IO1G4_IO1121G3_IO4G3_IO4111G3_IO3G3_IO3101G3_IO2G3_IO291G3_IO1G3_IO181G2_IO4G2_IO471G2_IO3G2_IO361G2_IO2G2_IO251G2_IO1G2_IO141G1_IO4G1_IO431G1_IO3G1_IO321G1_IO2G1_IO211G1_IO1G1_IO101IOCCRIOCCRI/O channel control register0x280x20read-write0x00000000G7_IO4G7_IO4271G7_IO3G7_IO3261G7_IO2G7_IO2251G7_IO1G7_IO1241G6_IO4G6_IO4231G6_IO3G6_IO3221G6_IO2G6_IO2211G6_IO1G6_IO1201G5_IO4G5_IO4191G5_IO3G5_IO3181G5_IO2G5_IO2171G5_IO1G5_IO1161G4_IO4G4_IO4151G4_IO3G4_IO3141G4_IO2G4_IO2131G4_IO1G4_IO1121G3_IO4G3_IO4111G3_IO3G3_IO3101G3_IO2G3_IO291G3_IO1G3_IO181G2_IO4G2_IO471G2_IO3G2_IO361G2_IO2G2_IO251G2_IO1G2_IO141G1_IO4G1_IO431G1_IO3G1_IO321G1_IO2G1_IO211G1_IO1G1_IO101IOGCSRIOGCSRI/O group control status register0x300x200x00000000G7SAnalog I/O group x status221read-onlyG6SAnalog I/O group x status211read-onlyG5SAnalog I/O group x status201read-onlyG4SAnalog I/O group x status191read-onlyG3SAnalog I/O group x status181read-onlyG2SAnalog I/O group x status171read-onlyG1SAnalog I/O group x status161read-onlyG7EAnalog I/O group x enable61read-writeG6EAnalog I/O group x enable51read-writeG5EAnalog I/O group x enable41read-writeG4EAnalog I/O group x enable31read-writeG3EAnalog I/O group x enable21read-writeG2EAnalog I/O group x enable11read-writeG1EAnalog I/O group x enable01read-writeIOG1CRIOG1CRI/O group x counter register0x340x20read-only0x00000000CNTCounter value014IOG2CRIOG2CRI/O group x counter register0x380x20read-only0x00000000CNTCounter value014IOG3CRIOG3CRI/O group x counter register0x3C0x20read-only0x00000000CNTCounter value014IOG4CRIOG4CRI/O group x counter register0x400x20read-only0x00000000CNTCounter value014IOG5CRIOG5CRI/O group x counter register0x440x20read-only0x00000000CNTCounter value014IOG6CRIOG6CRI/O group x counter register0x480x20read-only0x00000000CNTCounter value014IOG7CRIOG7CRI/O group x counter register0x4C0x20read-only0x00000000CNTCounter value014IWDGIndependent watchdogIWDG0x400030000x00x400registersKRKRKey register0x00x20write-only0x00000000KEYKey value (write only, read 0x0000)016PRPRPrescaler register0x40x20read-write0x00000000PRPrescaler divider03RLRRLRReload register0x80x20read-write0x00000FFFRLWatchdog counter reload value012SRSRStatus register0xC0x20read-only0x00000000WVUWatchdog counter window value update21RVUWatchdog counter reload value update11PVUWatchdog prescaler value update01WINRWINRWindow register0x100x20read-write0x00000FFFWINWatchdog counter window value012WWDGSystem window watchdogWWDG0x40002C000x00x400registersWWDGWindow Watchdog interrupt0CRCRControl register0x00x20read-write0x0000007FWDGAActivation bit71T7-bit counter (MSB to LSB)07CFRCFRConfiguration register0x40x20read-write0x0000007FWDGTBTimer base113EWIEarly wakeup interrupt91W7-bit window value07SRSRStatus register0x80x20read-write0x00000000EWIFEarly wakeup interrupt flag01I2C1Inter-integrated circuitI2C0x400054000x00x400registersI2C1_EVI2C1 event interrupt30I2C1_ERI2C1 error interrupt31CR1CR1Control register 10x00x20read-write0x00000000PEPeripheral enable01TXIETX Interrupt enable11RXIERX Interrupt enable21ADDRIEAddress match interrupt enable (slave only)31NACKIENot acknowledge received interrupt enable41STOPIESTOP detection Interrupt enable51TCIETransfer Complete interrupt enable61ERRIEError interrupts enable71DNFDigital noise filter84ANFOFFAnalog noise filter OFF121TXDMAENDMA transmission requests enable141RXDMAENDMA reception requests enable151SBCSlave byte control161NOSTRETCHClock stretching disable171WUPENWakeup from STOP enable181GCENGeneral call enable191SMBHENSMBus Host address enable201SMBDENSMBus Device Default address enable211ALERTENSMBUS alert enable221PECENPEC enable231CR2CR2Control register 20x40x20read-write0x00000000PECBYTEPacket error checking byte261AUTOENDAutomatic end mode (master mode)251RELOADNBYTES reload mode241NBYTESNumber of bytes168NACKNACK generation (slave mode)151STOPStop generation (master mode)141STARTStart generation131HEAD10R10-bit address header only read direction (master receiver mode)121ADD1010-bit addressing mode (master mode)111RD_WRNTransfer direction (master mode)101SADDSlave address bit (master mode)010OAR1OAR1Own address register 10x80x20read-write0x00000000OA1Interface address010OA1MODEOwn Address 1 10-bit mode101OA1ENOwn Address 1 enable151OAR2OAR2Own address register 20xC0x20read-write0x00000000OA2Interface address17OA2MSKOwn Address 2 masks83OA2ENOwn Address 2 enable151TIMINGRTIMINGRTiming register0x100x20read-write0x00000000SCLLSCL low period (master mode)08SCLHSCL high period (master mode)88SDADELData hold time164SCLDELData setup time204PRESCTiming prescaler284TIMEOUTRTIMEOUTRStatus register 10x140x20read-write0x00000000TIMEOUTABus timeout A012TIDLEIdle clock timeout detection121TIMOUTENClock timeout enable151TIMEOUTBBus timeout B1612TEXTENExtended clock timeout enable311ISRISRInterrupt and Status register0x180x200x00000001ADDCODEAddress match code (Slave mode)177read-onlyDIRTransfer direction (Slave mode)161read-onlyBUSYBus busy151read-onlyALERTSMBus alert131read-onlyTIMEOUTTimeout or t_low detection flag121read-onlyPECERRPEC Error in reception111read-onlyOVROverrun/Underrun (slave mode)101read-onlyARLOArbitration lost91read-onlyBERRBus error81read-onlyTCRTransfer Complete Reload71read-onlyTCTransfer Complete (master mode)61read-onlySTOPFStop detection flag51read-onlyNACKFNot acknowledge received flag41read-onlyADDRAddress matched (slave mode)31read-onlyRXNEReceive data register not empty (receivers)21read-onlyTXISTransmit interrupt status (transmitters)11read-writeTXETransmit data register empty (transmitters)01read-writeICRICRInterrupt clear register0x1C0x20write-only0x00000000ALERTCFAlert flag clear131TIMOUTCFTimeout detection flag clear121PECCFPEC Error flag clear111OVRCFOverrun/Underrun flag clear101ARLOCFArbitration lost flag clear91BERRCFBus error flag clear81STOPCFStop detection flag clear51NACKCFNot Acknowledge flag clear41ADDRCFAddress Matched flag clear31PECRPECRPEC register0x200x20read-only0x00000000PECPacket error checking register08RXDRRXDRReceive data register0x240x20read-only0x00000000RXDATA8-bit receive data08TXDRTXDRTransmit data register0x280x20read-write0x00000000TXDATA8-bit transmit data08I2C30x40005C00I2C3_EVI2C3 event interrupt32I2C3_ERI2C3 error interrupt33FlashFlashFlash0x580040000x00x90registersFLASHFlash global interrupt4ACRACRAccess control register0x00x20read-write0x00000600LATENCYLatency03PRFTENPrefetch enable81ICENInstruction cache enable91DCENData cache enable101ICRSTInstruction cache reset111DCRSTData cache reset121PESCPU1 CortexM4 program erase suspend request151EMPTYFlash User area empty161KEYRKEYRFlash key register0x80x20write-only0x00000000KEYRKEYR032OPTKEYROPTKEYROption byte key register0xC0x20write-only0x00000000OPTKEYROption byte key032SRSRStatus register0x100x200x00000000EOPEnd of operation01read-writeOPERROperation error11read-writePROGERRProgramming error31read-writeWRPERRWrite protected error41read-writePGAERRProgramming alignment error51read-writeSIZERRSize error61read-writePGSERRProgramming sequence error71read-writeMISERRFast programming data miss error81read-writeFASTERRFast programming error91read-writeOPTNVUser Option OPTVAL indication131read-onlyRDERRPCROP read error141read-writeOPTVERROption validity error151read-writeBSYBusy161read-onlyCFGBSYProgramming or erase configuration busy181read-onlyPESDProgramming or erase operation suspended191read-onlyCRCRFlash control register0x140x20read-write0xC0000000PGProgramming01PERPage erase11MERThis bit triggers the mass erase (all user pages) when set21PNBPage number selection38STRTStart161OPTSTRTOptions modification start171FSTPGFast programming181EOPIEEnd of operation interrupt enable241ERRIEError interrupt enable251RDERRIEPCROP read error interrupt enable261OBL_LAUNCHForce the option byte loading271OPTLOCKOptions Lock301LOCKFLASH_CR Lock311ECCRECCRFlash ECC register0x180x200x00000000ADDR_ECCECC fail address017read-onlySYSF_ECCSystem Flash ECC fail201read-onlyECCCIEECC correction interrupt enable241read-writeCPUIDCPU identification263read-onlyECCCECC correction301read-writeECCDECC detection311read-writeOPTROPTRFlash option register0x200x20read-write0x10708000RDPRead protection level08ESESecurity enabled81BOR_LEVBOR reset Level93nRST_STOPnRST_STOP121nRST_STDBYnRST_STDBY131nRST_SHDWnRST_SHDW141IDWG_SWIndependent watchdog selection161IWDG_STOPIndependent watchdog counter freeze in Stop mode171IWDG_STDBYIndependent watchdog counter freeze in Standby mode181WWDG_SWWindow watchdog selection191nBOOT1Boot configuration231SRAM2_PESRAM2 parity check enable241SRAM2_RSTSRAM2 Erase when system reset251nSWBOOT0Software Boot0261nBOOT0nBoot0 option bit271AGC_TRIMRadio Automatic Gain Control Trimming293PCROP1ASRPCROP1ASRFlash Bank 1 PCROP Start address zone A register0x240x20read-write0xFFFFFE00PCROP1A_STRTBank 1 PCROPQ area start offset09PCROP1AERPCROP1AERFlash Bank 1 PCROP End address zone A register0x280x20read-write0x7FFFFE00PCROP1A_ENDBank 1 PCROP area end offset09PCROP_RDPPCROP area preserved when RDP level decreased311WRP1ARWRP1ARFlash Bank 1 WRP area A address register0x2C0x20read-write0xFF00FF00WRP1A_STRTBank 1 WRP first area A start offset08WRP1A_ENDBank 1 WRP first area A end offset168WRP1BRWRP1BRFlash Bank 1 WRP area B address register0x300x20read-write0xFF00FF00WRP1B_STRTBank 1 WRP second area B end offset168WRP1B_ENDBank 1 WRP second area B start offset08PCROP1BSRPCROP1BSRFlash Bank 1 PCROP Start address area B register0x340x20read-write0xFFFFFE00PCROP1B_STRTBank 1 PCROP area B start offset09PCROP1BERPCROP1BERFlash Bank 1 PCROP End address area B register0x380x20read-write0xFFFFFE00PCROP1B_ENDBank 1 PCROP area end area B offset09IPCCBRIPCCBRIPCC mailbox data buffer address register0x3C0x20read-write0xFFFFC000IPCCDBAPCC mailbox data buffer base address014C2ACRC2ACRCPU2 cortex M0 access control register0x5C0x20read-write0x00000600PRFTENCPU2 cortex M0 prefetch enable81ICENCPU2 cortex M0 instruction cache enable91ICRSTCPU2 cortex M0 instruction cache reset111PESCPU2 cortex M0 program erase suspend request151C2SRC2SRCPU2 cortex M0 status register0x600x20read-write0x00000000EOPEnd of operation01OPERROperation error11PROGERRProgramming error31WRPERRwrite protection error41PGAERRProgramming alignment error51SIZERRSize error61PGSERRProgramming sequence error71MISSERRFast programming data miss error81FASTERRFast programming error91RDERRPCROP read error141BSYBusy161CFGBSYProgramming or erase configuration busy181PESDProgramming or erase operation suspended191C2CRC2CRCPU2 cortex M0 control register0x640x20read-write0x00000000PGProgramming01PERPage erase11MERMasse erase21PNBPage Number selection38STRTStart161FSTPGFast programming181EOPIEEnd of operation interrupt enable241ERRIEError interrupt enable251RDERRIEPCROP read error interrupt enable261SFRSFRSecure flash start address register0x800x20read-write0xFFFFEE00SFSASecure flash start address08DDSDisable Cortex M0 debug access121FSDFlash security disable81SRRVRSRRVRSecure SRAM2 start address and cortex M0 reset vector register0x840x20read-write0x01000000SBRVcortex M0 access control register018SBRSASecure backup SRAM2a start address185BRSDbackup SRAM2a security disable231SNBRSASecure non backup SRAM2a start address255C2OPTCPU2 cortex M0 boot reset vector memory selection311NBRSDnon-backup SRAM2b security disable301QUADSPIQuadSPI interfaceQUADSPI0xA00010000x00x400registersQUADSPIQSPI global interrupt50CRCRcontrol register0x00x20read-write0x00000000PRESCALERClock prescaler248PMMPolling match mode231APMSAutomatic poll mode stop221TOIETimeOut interrupt enable201SMIEStatus match interrupt enable191FTIEFIFO threshold interrupt enable181TCIETransfer complete interrupt enable171TEIETransfer error interrupt enable161FTHRESFIFO threshold level85SSHIFTSample shift41TCENTimeout counter enable31DMAENDMA enable21ABORTAbort request11ENEnable01DCRDCRdevice configuration register0x40x20read-write0x00000000FSIZEFLASH memory size165CSHTChip select high time83CKMODEMode 0 / mode 301SRSRstatus register0x80x20read-only0x00000000FLEVELFIFO level86BUSYBusy51TOFTimeout flag41SMFStatus match flag31FTFFIFO threshold flag21TCFTransfer complete flag11TEFTransfer error flag01FCRFCRflag clear register0xC0x20read-write0x00000000CTOFClear timeout flag41CSMFClear status match flag31CTCFClear transfer complete flag11CTEFClear transfer error flag01DLRDLRdata length register0x100x20read-write0x00000000DLData length032CCRCCRcommunication configuration register0x140x20read-write0x00000000DDRMDouble data rate mode311SIOOSend instruction only once mode281FMODEFunctional mode262DMODEData mode242DCYCNumber of dummy cycles185ABSIZEAlternate bytes size162ABMODEAlternate bytes mode142ADSIZEAddress size122ADMODEAddress mode102IMODEInstruction mode82INSTRUCTIONInstruction08ARARaddress register0x180x20read-write0x00000000ADDRESSAddress032ABRABRABR0x1C0x20read-write0x00000000ALTERNATEALTERNATE032DRDRdata register0x200x20read-write0x00000000DATAData032PSMKRPSMKRpolling status mask register0x240x20read-write0x00000000MASKStatus mask032PSMARPSMARpolling status match register0x280x20read-write0x00000000MATCHStatus match032PIRPIRpolling interval register0x2C0x20read-write0x00000000INTERVALPolling interval016LPTRLPTRlow-power timeout register0x300x20read-write0x00000000TIMEOUTTimeout period016RCCReset and clock controlRCC0x580000000x00x400registersRCCRCC global interrupt5CRCRClock control register0x00x200x00000061PLLSAI1RDYSAI1 PLL clock ready flag271read-onlyPLLSAI1ONSAI1 PLL enable261read-writePLLRDYMain PLL clock ready flag251read-onlyPLLONMain PLL enable241read-writeHSEPREHSE sysclk and PLL M divider prescaler201read-writeCSSONHSE Clock security system enable191write-onlyHSEBYPHSE crystal oscillator bypass181read-writeHSERDYHSE clock ready flag171read-onlyHSEONHSE clock enabled161read-writeHSIKERDYHSI kernel clock ready flag for peripherals requests121read-onlyHSIASFSHSI automatic start from Stop111read-writeHSIRDYHSI clock ready flag101read-onlyHSIKERONHSI always enable for peripheral kernels91read-writeHSIONHSI clock enabled81read-writeMSIRANGEMSI clock ranges44read-writeMSIPLLENMSI clock PLL enable21read-writeMSIRDYMSI clock ready flag11read-onlyMSIONMSI clock enable01read-writeICSCRICSCRInternal clock sources calibration register0x40x200x40000000HSITRIMHSI clock trimming247read-writeHSICALHSI clock calibration168read-onlyMSITRIMMSI clock trimming88read-writeMSICALMSI clock calibration08read-onlyCFGRCFGRClock configuration register0x80x200x00070000MCOPREMicrocontroller clock output prescaler283read-writeMCOSELMicrocontroller clock output244read-writePPRE2FAPB2 prescaler flag181read-onlyPPRE1FAPB1 prescaler flag171read-onlyHPREFAHB prescaler flag161read-onlySTOPWUCKWakeup from Stop and CSS backup clock selection151read-writePPRE2APB high-speed prescaler (APB2)113read-writePPRE1PB low-speed prescaler (APB1)83read-writeHPREAHB prescaler44read-writeSWSSystem clock switch status22read-onlySWSystem clock switch02read-writePLLCFGRPLLCFGRPLLSYS configuration register0xC0x20read-write0x22040100PLLRMain PLLSYS division factor R for SYSCLK (system clock)293PLLRENMain PLLSYSR PLLCLK output enable281PLLQMain PLLSYS division factor Q for PLLSYSUSBCLK253PLLQENMain PLLSYSQ output enable241PLLPMain PLL division factor P for PPLSYSSAICLK175PLLPENMain PLLSYSP output enable161PLLNMain PLLSYS multiplication factor N87PLLMDivision factor M for the main PLL and audio PLL (PLLSAI1 and PLLSAI2) input clock43PLLSRCMain PLL, PLLSAI1 and PLLSAI2 entry clock source02PLLSAI1CFGRPLLSAI1CFGRPLLSAI1 configuration register0x100x20read-write0x22040100PLLRPLLSAI division factor R for PLLADC1CLK (ADC clock)293PLLRENPLLSAI PLLADC1CLK output enable281PLLQSAIPLL division factor Q for PLLSAIUSBCLK (48 MHz clock)253PLLQENSAIPLL PLLSAIUSBCLK output enable241PLLPSAI1PLL division factor P for PLLSAICLK (SAI1clock)175PLLPENSAIPLL PLLSAI1CLK output enable161PLLNSAIPLL multiplication factor for VCO87CIERCIERClock interrupt enable register0x180x20read-write0x00000000LSI2RDYIELSI2 ready interrupt enable111HSI48RDYIEHSI48 ready interrupt enable101LSECSSIELSE clock security system interrupt enable91PLLSAI1RDYIEPLLSAI1 ready interrupt enable61PLLRDYIEPLLSYS ready interrupt enable51HSERDYIEHSE ready interrupt enable41HSIRDYIEHSI ready interrupt enable31MSIRDYIEMSI ready interrupt enable21LSERDYIELSE ready interrupt enable11LSI1RDYIELSI1 ready interrupt enable01CIFRCIFRClock interrupt flag register0x1C0x20read-only0x00000000LSI2RDYFLSI2 ready interrupt flag111HSI48RDYFHSI48 ready interrupt flag101LSECSSFLSE Clock security system interrupt flag91HSECSSFHSE Clock security system interrupt flag81PLLSAI1RDYFPLLSAI1 ready interrupt flag61PLLRDYFPLL ready interrupt flag51HSERDYFHSE ready interrupt flag41HSIRDYFHSI ready interrupt flag31MSIRDYFMSI ready interrupt flag21LSERDYFLSE ready interrupt flag11LSI1RDYFLSI1 ready interrupt flag01CICRCICRClock interrupt clear register0x200x20write-only0x00000000LSI2RDYCLSI2 ready interrupt clear111HSI48RDYCHSI48 ready interrupt clear101LSECSSCLSE Clock security system interrupt clear91HSECSSCHSE Clock security system interrupt clear81PLLSAI1RDYCPLLSAI1 ready interrupt clear61PLLRDYCPLL ready interrupt clear51HSERDYCHSE ready interrupt clear41HSIRDYCHSI ready interrupt clear31MSIRDYCMSI ready interrupt clear21LSERDYCLSE ready interrupt clear11LSI1RDYCLSI1 ready interrupt clear01SMPSCRSMPSCRStep Down converter control register0x240x200x00000301SMPSSWSStep Down converter clock switch status82read-onlySMPSDIVStep Down converter clock prescaler42read-writeSMPSSELStep Down converter clock selection02read-writeAHB1RSTRAHB1RSTRAHB1 peripheral reset register0x280x20read-write0x00000000TSCRSTTouch Sensing Controller reset161CRCRSTCRC reset121DMAMUXRSTDMAMUX reset21DMA2RSTDMA2 reset11DMA1RSTDMA1 reset01AHB2RSTRAHB2RSTRAHB2 peripheral reset register0x2C0x20read-write0x00000000AES1RSTAES1 hardware accelerator reset161ADCRSTADC reset131GPIOHRSTIO port H reset71GPIOERSTIO port E reset41GPIODRSTIO port D reset31GPIOCRSTIO port C reset21GPIOBRSTIO port B reset11GPIOARSTIO port A reset01AHB3RSTRAHB3RSTRAHB3 peripheral reset register0x300x20read-write0x00000000FLASHRSTFlash interface reset251IPCCRSTIPCC interface reset201HSEMRSTHSEM interface reset191RNGRSTRNG interface reset181AES2RSTAES2 interface reset171PKARSTPKA interface reset161QSPIRSTQuad SPI memory interface reset81APB1RSTR1APB1RSTR1APB1 peripheral reset register 10x380x20read-write0x00000000LPTIM1RSTLow Power Timer 1 reset311USBFSRSTUSB FS reset261CRSRSTCRS reset241I2C3RSTI2C3 reset231I2C1RSTI2C1 reset211SPI2RSTSPI2 reset141LCDRSTLCD interface reset91TIM2RSTTIM2 timer reset01APB1RSTR2APB1RSTR2APB1 peripheral reset register 20x3C0x20read-write0x00000000LPTIM2RSTLow-power timer 2 reset51LPUART1RSTLow-power UART 1 reset01APB2RSTRAPB2RSTRAPB2 peripheral reset register0x400x20read-write0x00000000SAI1RSTSerial audio interface 1 (SAI1) reset211TIM17RSTTIM17 timer reset181TIM16RSTTIM16 timer reset171USART1RSTUSART1 reset141SPI1RSTSPI1 reset121TIM1RSTTIM1 timer reset111APB3RSTRAPB3RSTRAPB3 peripheral reset register0x440x20read-write0x00000000RFRSTRadio system BLE reset01AHB1ENRAHB1ENRAHB1 peripheral clock enable register0x480x20read-write0x00000100TSCENTouch Sensing Controller clock enable161CRCENCPU1 CRC clock enable121DMAMUXENDMAMUX clock enable21DMA2ENDMA2 clock enable11DMA1ENDMA1 clock enable01AHB2ENRAHB2ENRAHB2 peripheral clock enable register0x4C0x20read-write0x00000000AES1ENAES1 accelerator clock enable161ADCENADC clock enable131GPIOHENIO port H clock enable71GPIOEENIO port E clock enable41GPIODENIO port D clock enable31GPIOCENIO port C clock enable21GPIOBENIO port B clock enable11GPIOAENIO port A clock enable01AHB3ENRAHB3ENRAHB3 peripheral clock enable register0x500x20read-write0x02080000FLASHENFLASHEN251IPCCENIPCCEN201HSEMENHSEMEN191RNGENRNGEN181AES2ENAES2EN171PKAENPKAEN161QSPIENQSPIEN81APB1ENR1APB1ENR1APB1ENR10x580x20read-write0x00000400LPTIM1ENCPU1 Low power timer 1 clock enable311USBENCPU1 USB clock enable261CRSENCPU1 CRS clock enable241I2C3ENCPU1 I2C3 clock enable231I2C1ENCPU1 I2C1 clock enable211SPI2ENCPU1 SPI2 clock enable141WWDGENCPU1 Window watchdog clock enable111RTCAPBENCPU1 RTC APB clock enable101LCDENCPU1 LCD clock enable91TIM2ENCPU1 TIM2 timer clock enable01APB1ENR2APB1ENR2APB1 peripheral clock enable register 20x5C0x20read-write0x00000000LPTIM2ENCPU1 LPTIM2EN51LPUART1ENCPU1 Low power UART 1 clock enable01APB2ENRAPB2ENRAPB2ENR0x600x20read-write0x00000000SAI1ENCPU1 SAI1 clock enable211TIM17ENCPU1 TIM17 timer clock enable181TIM16ENCPU1 TIM16 timer clock enable171USART1ENCPU1 USART1clock enable141SPI1ENCPU1 SPI1 clock enable121TIM1ENCPU1 TIM1 timer clock enable111AHB1SMENRAHB1SMENRAHB1 peripheral clocks enable in Sleep and Stop modes register0x680x20read-write0x00011207TSCSMENCPU1 Touch Sensing Controller clocks enable during Sleep and Stop modes161CRCSMENCPU1 CRCSMEN121SRAM1SMENCPU1 SRAM1 interface clocks enable during Sleep and Stop modes91DMAMUXSMENCPU1 DMAMUX clocks enable during Sleep and Stop modes21DMA2SMENCPU1 DMA2 clocks enable during Sleep and Stop modes11DMA1SMENCPU1 DMA1 clocks enable during Sleep and Stop modes01AHB2SMENRAHB2SMENRAHB2 peripheral clocks enable in Sleep and Stop modes register0x6C0x20read-write0x0001209FAES1SMENCPU1 AES1 accelerator clocks enable during Sleep and Stop modes161ADCFSSMENCPU1 ADC clocks enable during Sleep and Stop modes131GPIOHSMENCPU1 IO port H clocks enable during Sleep and Stop modes71GPIOESMENCPU1 IO port E clocks enable during Sleep and Stop modes41GPIODSMENCPU1 IO port D clocks enable during Sleep and Stop modes31GPIOCSMENCPU1 IO port C clocks enable during Sleep and Stop modes21GPIOBSMENCPU1 IO port B clocks enable during Sleep and Stop modes11GPIOASMENCPU1 IO port A clocks enable during Sleep and Stop modes01AHB3SMENRAHB3SMENRAHB3 peripheral clocks enable in Sleep and Stop modes register0x700x20read-write0x03070100FLASHSMENFlash interface clocks enable during CPU1 sleep mode251SRAM2SMENSRAM2a and SRAM2b memory interface clocks enable during CPU1 sleep mode241RNGSMENTrue RNG clocks enable during CPU1 sleep mode181AES2SMENAES2 accelerator clocks enable during CPU1 sleep mode171PKASMENPKA accelerator clocks enable during CPU1 sleep mode161QSPISMENQSPISMEN81APB1SMENR1APB1SMENR1APB1SMENR10x780x20read-write0x85A04E01LPTIM1SMENLow power timer 1 clocks enable during CPU1 Sleep mode311USBSMENUSB FS clocks enable during CPU1 Sleep mode261CRSMENCRS clocks enable during CPU1 Sleep mode241I2C3SMENI2C3 clocks enable during CPU1 Sleep mode231I2C1SMENI2C1 clocks enable during CPU1 Sleep mode211SPI2SMENSPI2 clocks enable during CPU1 Sleep mode141WWDGSMENWindow watchdog clocks enable during CPU1 Sleep mode111RTCAPBSMENRTC APB clocks enable during CPU1 Sleep mode101LCDSMENLCD clocks enable during CPU1 Sleep mode91TIM2SMENTIM2 timer clocks enable during CPU1 Sleep mode01APB1SMENR2APB1SMENR2APB1 peripheral clocks enable in Sleep and Stop modes register 20x7C0x20read-write0x000000021LPTIM2SMENLow power timer 2 clocks enable during CPU1 Sleep mode51LPUART1SMENLow power UART 1 clocks enable during CPU1 Sleep mode01APB2SMENRAPB2SMENRAPB2SMENR0x800x20read-write0x00265800SAI1SMENSAI1 clocks enable during CPU1 Sleep mode211TIM17SMENTIM17 timer clocks enable during CPU1 Sleep mode181TIM16SMENTIM16 timer clocks enable during CPU1 Sleep mode171USART1SMENUSART1clocks enable during CPU1 Sleep mode141SPI1SMENSPI1 clocks enable during CPU1 Sleep mode121TIM1SMENTIM1 timer clocks enable during CPU1 Sleep mode111CCIPRCCIPRCCIPR0x880x20read-write0x00000000RNGSELRNG clock source selection302ADCSELADCs clock source selection282CLK48SEL48 MHz clock source selection262SAI1SELSAI1 clock source selection222LPTIM2SELLow power timer 2 clock source selection202LPTIM1SELLow power timer 1 clock source selection182I2C3SELI2C3 clock source selection162I2C1SELI2C1 clock source selection122LPUART1SELLPUART1 clock source selection102USART1SELUSART1 clock source selection02BDCRBDCRBDCR0x900x200x00000000LSCOSELLow speed clock output selection251read-writeLSCOENLow speed clock output enable241read-writeBDRSTBackup domain software reset161read-writeRTCENRTC clock enable151read-writeRTCSELRTC clock source selection82read-writeLSECSSD_CSS on LSE failure detection61read-onlyLSECSSONLSECSSON51read-writeLSEDRVSE oscillator drive capability32read-writeLSEBYPLSE oscillator bypass21read-writeLSERDYLSE oscillator ready11read-onlyLSEONLSE oscillator enable01read-writeCSRCSRCSR0x940x200x0C000000LPWRRSTFLow-power reset flag311read-onlyWWDGRSTFWindow watchdog reset flag301read-onlyIWDGRSTFIndependent window watchdog reset flag291read-onlySFTRSTFSoftware reset flag281read-onlyBORRSTFBOR flag271read-onlyPINRSTFPin reset flag261read-onlyOBLRSTFOption byte loader reset flag251read-onlyRMVFRemove reset flag231read-writeRFWKPSELRF system wakeup clock source selection142read-writeLSI2BWLSI2 oscillator bias configuration84read-writeLSI2TRIMOKLSI2 oscillator trim OK51read-onlyLSI2TRIMENLSI2 oscillator trimming enable41read-writeLSI2RDYLSI2 oscillator ready31read-onlyLSI2ONLSI2 oscillator enabled21read-writeLSI1RDYLSI1 oscillator ready11read-onlyLSI1ONLSI1 oscillator enabled01read-writeRFRSTSRadio system BLE and 802.15.4 reset status161read-onlyCRRCRCRRCRClock recovery RC register0x980x200x00000000HSI48CALHSI48 clock calibration79read-onlyHSI48RDYHSI48 clock ready11read-onlyHSI48ONHSI48 oscillator enabled01read-writeHSECRHSECRClock HSE register0x9C0x200x00000030HSETUNEHSE capacitor tuning86read-onlyHSEGMCHSE current control43read-writeHSESHSE Sense amplifier threshold31read-writeUNLOCKEDRegister lock system01read-writeEXTCFGREXTCFGRExtended clock recovery register0x1080x200x00030000RFCSSRF clock source selected201read-onlyC2HPREFCPU2 AHB prescaler flag171read-onlySHDHPREFShared AHB prescaler flag161read-onlyC2HPRECPU2 AHB prescaler44read-writeSHDHPREShared AHB prescaler04read-writeC2AHB1ENRC2AHB1ENRCPU2 AHB1 peripheral clock enable register0x1480x20read-write0x00000000TSCENCPU2 Touch Sensing Controller clock enable161CRCENCPU2 CRC clock enable121SRAM1ENCPU2 SRAM1 clock enable91DMAMUXENCPU2 DMAMUX clock enable21DMA2ENCPU2 DMA2 clock enable11DMA1ENCPU2 DMA1 clock enable01C2AHB2ENRC2AHB2ENRCPU2 AHB2 peripheral clock enable register0x14C0x20read-write0x00000000AES1ENCPU2 AES1 accelerator clock enable161ADCENCPU2 ADC clock enable131GPIOHENCPU2 IO port H clock enable71GPIOEENCPU2 IO port E clock enable41GPIODENCPU2 IO port D clock enable31GPIOCENCPU2 IO port C clock enable21GPIOBENCPU2 IO port B clock enable11GPIOAENCPU2 IO port A clock enable01C2AHB3ENRC2AHB3ENRCPU2 AHB3 peripheral clock enable register0x1500x20read-write0x02080000FLASHENCPU2 FLASHEN251IPCCENCPU2 IPCCEN201HSEMENCPU2 HSEMEN191RNGENCPU2 RNGEN181AES2ENCPU2 AES2EN171PKAENCPU2 PKAEN161C2APB1ENR1C2APB1ENR1CPU2 APB1ENR10x1580x20read-write0x00000400LPTIM1ENCPU2 Low power timer 1 clock enable311USBENCPU2 USB clock enable261CRSENCPU2 CRS clock enable241I2C3ENCPU2 I2C3 clock enable231I2C1ENCPU2 I2C1 clock enable211SPI2ENCPU2 SPI2 clock enable141RTCAPBENCPU2 RTC APB clock enable101LCDENCPU2 LCD clock enable91TIM2ENCPU2 TIM2 timer clock enable01C2APB1ENR2C2APB1ENR2CPU2 APB1 peripheral clock enable register 20x15C0x20read-write0x00000000LPTIM2ENCPU2 LPTIM2EN51LPUART1ENCPU2 Low power UART 1 clock enable01C2APB2ENRC2APB2ENRCPU2 APB2ENR0x1600x20read-write0x00000000SAI1ENCPU2 SAI1 clock enable211TIM17ENCPU2 TIM17 timer clock enable181TIM16ENCPU2 TIM16 timer clock enable171USART1ENCPU2 USART1clock enable141SPI1ENCPU2 SPI1 clock enable121TIM1ENCPU2 TIM1 timer clock enable111C2APB3ENRC2APB3ENRCPU2 APB3ENR0x1640x20read-write0x00000000EN802CPU2 802.15.4 interface clock enable11BLEENCPU2 BLE interface clock enable01C2AHB1SMENRC2AHB1SMENRCPU2 AHB1 peripheral clocks enable in Sleep and Stop modes register0x1680x20read-write0x00011207TSCSMENCPU2 Touch Sensing Controller clocks enable during Sleep and Stop modes161CRCSMENCPU2 CRCSMEN121SRAM1SMENSRAM1 interface clock enable during CPU1 CSleep mode91DMAMUXSMENCPU2 DMAMUX clocks enable during Sleep and Stop modes21DMA2SMENCPU2 DMA2 clocks enable during Sleep and Stop modes11DMA1SMENCPU2 DMA1 clocks enable during Sleep and Stop modes01C2AHB2SMENRC2AHB2SMENRCPU2 AHB2 peripheral clocks enable in Sleep and Stop modes register0x16C0x20read-write0x0001209FAES1SMENCPU2 AES1 accelerator clocks enable during Sleep and Stop modes161ADCFSSMENCPU2 ADC clocks enable during Sleep and Stop modes131GPIOHSMENCPU2 IO port H clocks enable during Sleep and Stop modes71GPIOESMENCPU2 IO port E clocks enable during Sleep and Stop modes41GPIODSMENCPU2 IO port D clocks enable during Sleep and Stop modes31GPIOCSMENCPU2 IO port C clocks enable during Sleep and Stop modes21GPIOBSMENCPU2 IO port B clocks enable during Sleep and Stop modes11GPIOASMENCPU2 IO port A clocks enable during Sleep and Stop modes01C2AHB3SMENRC2AHB3SMENRCPU2 AHB3 peripheral clocks enable in Sleep and Stop modes register0x1700x20read-write0x03070000FLASHSMENFlash interface clocks enable during CPU2 sleep modes251SRAM2SMENSRAM2a and SRAM2b memory interface clocks enable during CPU2 sleep modes241RNGSMENTrue RNG clocks enable during CPU2 sleep modes181AES2SMENAES2 accelerator clocks enable during CPU2 sleep modes171PKASMENPKA accelerator clocks enable during CPU2 sleep modes161C2APB1SMENR1C2APB1SMENR1CPU2 APB1SMENR10x1780x20read-write0x85A04601LPTIM1SMENLow power timer 1 clocks enable during CPU2 Sleep mode311USBSMENUSB FS clocks enable during CPU2 Sleep mode261CRSMENCRS clocks enable during CPU2 Sleep mode241I2C3SMENI2C3 clocks enable during CPU2 Sleep mode231I2C1SMENI2C1 clocks enable during CPU2 Sleep mode211SPI2SMENSPI2 clocks enable during CPU2 Sleep mode141RTCAPBSMENRTC APB clocks enable during CPU2 Sleep mode101LCDSMENLCD clocks enable during CPU2 Sleep mode91TIM2SMENTIM2 timer clocks enable during CPU2 Sleep mode01C2APB1SMENR2C2APB1SMENR2CPU2 APB1 peripheral clocks enable in Sleep and Stop modes register 20x17C0x20read-write0x000000021LPTIM2SMENLow power timer 2 clocks enable during CPU2 Sleep mode51LPUART1SMENLow power UART 1 clocks enable during CPU2 Sleep mode01C2APB2SMENRC2APB2SMENRCPU2 APB2SMENR0x1800x20read-write0x00265800SAI1SMENSAI1 clocks enable during CPU2 Sleep mode211TIM17SMENTIM17 timer clocks enable during CPU2 Sleep mode181TIM16SMENTIM16 timer clocks enable during CPU2 Sleep mode171USART1SMENUSART1clocks enable during CPU2 Sleep mode141SPI1SMENSPI1 clocks enable during CPU2 Sleep mode121TIM1SMENTIM1 timer clocks enable during CPU2 Sleep mode111C2APB3SMENRC2APB3SMENRCPU2 APB3SMENR0x1840x20read-write0x0000003SMEN802802.15.4 interface clocks enable during CPU2 Sleep modes11BLESMENBLE interface clocks enable during CPU2 Sleep mode01PWRPower controlPWR0x580004000x00x400registersPWR_SOTFPWR switching on the fly + interrupt43CR1CR1Power control register 10x00x20read-write0x00000200LPRLow-power run141VOSVoltage scaling range selection92DBPDisable backup domain write protection81FPDSFlash power down mode during LPsSleep for CPU151FPDRFlash power down mode during LPRun for CPU141LPMSLow-power mode selection for CPU103CR2CR2Power control register 20x40x20read-write0x00000000USVVDDUSB USB supply valid101PVME3Peripheral voltage monitoring 3 enable: VDDA vs. 1.62V61PVME1Peripheral voltage monitoring 1 enable: VDDUSB vs. 1.2V41PLSPower voltage detector level selection13PVDEPower voltage detector enable01CR3CR3Power control register 30x80x20read-write0x00008000EIWULEnable internal wakeup line for CPU1151EC2HEnable CPU2 Hold interrupt for CPU1141E802AEnable end of activity interrupt for CPU1131EBLEAEnable BLE end of activity interrupt for CPU1111ECRPEEnable critical radio phase end of activity interrupt for CPU1121APCApply pull-up and pull-down configuration101RRSSRAM2a retention in Standby mode91EBORHSDFBEnable BORH and Step Down counverter forced in Bypass interrups for CPU181EWUP5Enable Wakeup pin WKUP541EWUP4Enable Wakeup pin WKUP431EWUP3Enable Wakeup pin WKUP321EWUP2Enable Wakeup pin WKUP211EWUP1Enable Wakeup pin WKUP101CR4CR4Power control register 40xC0x20read-write0x00000000C2BOOTBOOT CPU2 after reset or wakeup from Stop or Standby modes151VBRSVBAT battery charging resistor selection91VBEVBAT battery charging enable81WP5Wakeup pin WKUP5 polarity41WP4Wakeup pin WKUP4 polarity31WP3Wakeup pin WKUP3 polarity21WP2Wakeup pin WKUP2 polarity11WP1Wakeup pin WKUP1 polarity01SR1SR1Power status register 10x100x20read-only0x00000000WUFIInternal Wakeup interrupt flag151C2HFCPU2 Hold interrupt flag141AF802802.15.4 end of activity interrupt flag131BLEAFBLE end of activity interrupt flag121CRPEFEnable critical radio phase end of activity interrupt flag111WUF802802.15.4 wakeup interrupt flag101BLEWUFBLE wakeup interrupt flag91BORHFBORH interrupt flag81SDFBFStep Down converter forced in Bypass interrupt flag71CWUF5Wakeup flag 541CWUF4Wakeup flag 431CWUF3Wakeup flag 321CWUF2Wakeup flag 211CWUF1Wakeup flag 101SR2SR2Power status register 20x140x20read-only0x00000002PVMO3Peripheral voltage monitoring output: VDDA vs. 1.62 V141PVMO1Peripheral voltage monitoring output: VDDUSB vs. 1.2 V121PVDOPower voltage detector output111VOSFVoltage scaling flag101REGLPFLow-power regulator flag91REGLPSLow-power regulator started81SDSMPSFStep Down converter SMPS mode flag11SDBFStep Down converter Bypass mode flag01SCRSCRPower status clear register0x180x20write-only0x00000000CC2HFClear CPU2 Hold interrupt flag141C802AFClear 802.15.4 end of activity interrupt flag131CBLEAFClear BLE end of activity interrupt flag121CCRPEFClear critical radio phase end of activity interrupt flag111C802WUFClear 802.15.4 wakeup interrupt flag101CBLEWUFClear BLE wakeup interrupt flag91CBORHFClear BORH interrupt flag81CSMPSFBFClear SMPS Step Down converter forced in Bypass interrupt flag71CWUF5Clear wakeup flag 541CWUF4Clear wakeup flag 431CWUF3Clear wakeup flag 321CWUF2Clear wakeup flag 211CWUF1Clear wakeup flag 101CR5CR5Power control register 50x1C0x20read-write0x00004270SDEBEnable Step Down converter SMPS mode enabled151SDBENEnable Step Down converter Bypass mode enabled141SMPSCFGVOS configuration selection (non user)91BORHCBORH configuration selection81SDSCStep Down converter supplt startup current selection43SDVOSStep Down converter voltage output scaling04PUCRAPUCRAPower Port A pull-up control register0x200x20read-write0x00000000PU15Port A pull-up bit y (y=0..15)151PU13Port A pull-up bit y (y=0..15)131PU12Port A pull-up bit y (y=0..15)121PU11Port A pull-up bit y (y=0..15)111PU10Port A pull-up bit y (y=0..15)101PU9Port A pull-up bit y (y=0..15)91PU8Port A pull-up bit y (y=0..15)81PU7Port A pull-up bit y (y=0..15)71PU6Port A pull-up bit y (y=0..15)61PU5Port A pull-up bit y (y=0..15)51PU4Port A pull-up bit y (y=0..15)41PU3Port A pull-up bit y (y=0..15)31PU2Port A pull-up bit y (y=0..15)21PU1Port A pull-up bit y (y=0..15)11PU0Port A pull-up bit y (y=0..15)01PDCRAPDCRAPower Port A pull-down control register0x240x20read-write0x00000000PD14Port A pull-down bit y (y=0..15)141PD12Port A pull-down bit y (y=0..15)121PD11Port A pull-down bit y (y=0..15)111PD10Port A pull-down bit y (y=0..15)101PD9Port A pull-down bit y (y=0..15)91PD8Port A pull-down bit y (y=0..15)81PD7Port A pull-down bit y (y=0..15)71PD6Port A pull-down bit y (y=0..15)61PD5Port A pull-down bit y (y=0..15)51PD4Port A pull-down bit y (y=0..15)41PD3Port A pull-down bit y (y=0..15)31PD2Port A pull-down bit y (y=0..15)21PD1Port A pull-down bit y (y=0..15)11PD0Port A pull-down bit y (y=0..15)01PUCRBPUCRBPower Port B pull-up control register0x280x20read-write0x00000000PU15Port B pull-up bit y (y=0..15)151PU14Port B pull-up bit y (y=0..15)141PU13Port B pull-up bit y (y=0..15)131PU12Port B pull-up bit y (y=0..15)121PU11Port B pull-up bit y (y=0..15)111PU10Port B pull-up bit y (y=0..15)101PU9Port B pull-up bit y (y=0..15)91PU8Port B pull-up bit y (y=0..15)81PU7Port B pull-up bit y (y=0..15)71PU6Port B pull-up bit y (y=0..15)61PU5Port B pull-up bit y (y=0..15)51PU4Port B pull-up bit y (y=0..15)41PU3Port B pull-up bit y (y=0..15)31PU2Port B pull-up bit y (y=0..15)21PU1Port B pull-up bit y (y=0..15)11PU0Port B pull-up bit y (y=0..15)01PDCRBPDCRBPower Port B pull-down control register0x2C0x20read-write0x00000000PD15Port B pull-down bit y (y=0..15)151PD14Port B pull-down bit y (y=0..15)141PD13Port B pull-down bit y (y=0..15)131PD12Port B pull-down bit y (y=0..15)121PD11Port B pull-down bit y (y=0..15)111PD10Port B pull-down bit y (y=0..15)101PD9Port B pull-down bit y (y=0..15)91PD8Port B pull-down bit y (y=0..15)81PD7Port B pull-down bit y (y=0..15)71PD6Port B pull-down bit y (y=0..15)61PD5Port B pull-down bit y (y=0..15)51PD3Port B pull-down bit y (y=0..15)31PD2Port B pull-down bit y (y=0..15)21PD1Port B pull-down bit y (y=0..15)11PD0Port B pull-down bit y (y=0..15)01PUCRCPUCRCPower Port C pull-up control register0x300x20read-write0x00000000PU15Port C pull-up bit y (y=0..15)151PU14Port C pull-up bit y (y=0..15)141PU13Port C pull-up bit y (y=0..15)131PU12Port C pull-up bit y (y=0..15)121PU11Port C pull-up bit y (y=0..15)111PU10Port C pull-up bit y (y=0..15)101PU9Port C pull-up bit y (y=0..15)91PU8Port C pull-up bit y (y=0..15)81PU7Port C pull-up bit y (y=0..15)71PU6Port C pull-up bit y (y=0..15)61PU5Port C pull-up bit y (y=0..15)51PU4Port C pull-up bit y (y=0..15)41PU3Port C pull-up bit y (y=0..15)31PU2Port C pull-up bit y (y=0..15)21PU1Port C pull-up bit y (y=0..15)11PU0Port C pull-up bit y (y=0..15)01PDCRCPDCRCPower Port C pull-down control register0x340x20read-write0x00000000PD15Port C pull-down bit y (y=0..15)151PD14Port C pull-down bit y (y=0..15)141PD13Port C pull-down bit y (y=0..15)131PD12Port C pull-down bit y (y=0..15)121PD11Port C pull-down bit y (y=0..15)111PD10Port C pull-down bit y (y=0..15)101PD9Port C pull-down bit y (y=0..15)91PD8Port C pull-down bit y (y=0..15)81PD7Port C pull-down bit y (y=0..15)71PD6Port C pull-down bit y (y=0..15)61PD5Port C pull-down bit y (y=0..15)51PD4Port C pull-down bit y (y=0..15)41PD3Port C pull-down bit y (y=0..15)31PD2Port C pull-down bit y (y=0..15)21PD1Port C pull-down bit y (y=0..15)11PD0Port C pull-down bit y (y=0..15)01PUCRDPUCRDPower Port D pull-up control register0x380x20read-write0x00000000PU15Port D pull-up bit y (y=0..15)151PU14Port D pull-up bit y (y=0..15)141PU13Port D pull-up bit y (y=0..15)131PU12Port D pull-up bit y (y=0..15)121PU11Port D pull-up bit y (y=0..15)111PU10Port D pull-up bit y (y=0..15)101PU9Port D pull-up bit y (y=0..15)91PU8Port D pull-up bit y (y=0..15)81PU7Port D pull-up bit y (y=0..15)71PU6Port D pull-up bit y (y=0..15)61PU5Port D pull-up bit y (y=0..15)51PU4Port D pull-up bit y (y=0..15)41PU3Port D pull-up bit y (y=0..15)31PU2Port D pull-up bit y (y=0..15)21PU1Port D pull-up bit y (y=0..15)11PU0Port D pull-up bit y (y=0..15)01PDCRDPDCRDPower Port D pull-down control register0x3C0x20read-write0x00000000PD15Port D pull-down bit y (y=0..15)151PD14Port D pull-down bit y (y=0..15)141PD13Port D pull-down bit y (y=0..15)131PD12Port D pull-down bit y (y=0..15)121PD11Port D pull-down bit y (y=0..15)111PD10Port D pull-down bit y (y=0..15)101PD9Port D pull-down bit y (y=0..15)91PD8Port D pull-down bit y (y=0..15)81PD7Port D pull-down bit y (y=0..15)71PD6Port D pull-down bit y (y=0..15)61PD5Port D pull-down bit y (y=0..15)51PD4Port D pull-down bit y (y=0..15)41PD3Port D pull-down bit y (y=0..15)31PD2Port D pull-down bit y (y=0..15)21PD1Port D pull-down bit y (y=0..15)11PD0Port D pull-down bit y (y=0..15)01PUCREPUCREPower Port E pull-up control register0x400x20read-write0x00000000PU4Port E pull-up bit y (y=0..15)41PU3Port E pull-up bit y (y=0..15)31PU2Port E pull-up bit y (y=0..15)21PU1Port E pull-up bit y (y=0..15)11PU0Port E pull-up bit y (y=0..15)01PDCREPDCREPower Port E pull-down control register0x440x20read-write0x00000000PD4Port E pull-down bit y (y=0..15)41PD3Port E pull-down bit y (y=0..15)31PD2Port E pull-down bit y (y=0..15)21PD1Port E pull-down bit y (y=0..15)11PD0Port E pull-down bit y (y=0..15)01PUCRHPUCRHPower Port H pull-up control register0x580x20read-write0x00000000PU3Port H pull-up bit y (y=0..1)31PU1Port H pull-up bit y (y=0..1)11PU0Port H pull-up bit y (y=0..1)01PDCRHPDCRHPower Port H pull-down control register0x5C0x20read-write0x00000000PD3Port H pull-down bit y (y=0..1)31PD1Port H pull-down bit y (y=0..1)11PD0Port H pull-down bit y (y=0..1)01C2CR1C2CR1CPU2 Power control register 10x800x20read-write0x00000000EWKUP802802.15.4 external wakeup signal151BLEEWKUPBLE external wakeup signal141FPDSFlash power down mode during LPSleep for CPU251FPDRFlash power down mode during LPRun for CPU241LPMSLow-power mode selection for CPU203C2CR3C2CR3CPU2 Power control register 30x840x20read-write0X00008000EIWULEnable internal wakeup line for CPU2151APCApply pull-up and pull-down configuration for CPU2121E802WUPEnable 802.15.4 host wakeup interrupt for CPU2101EBLEWUPEnable BLE host wakeup interrupt for CPU291EWUP5Enable Wakeup pin WKUP5 for CPU241EWUP4Enable Wakeup pin WKUP4 for CPU231EWUP3Enable Wakeup pin WKUP3 for CPU221EWUP2Enable Wakeup pin WKUP2 for CPU211EWUP1Enable Wakeup pin WKUP1 for CPU201EXTSCREXTSCRPower status clear register0x880x200x00000000C2DSCPU2 deepsleep mode151read-onlyC1DSCPU1 deepsleep mode141read-onlyCRPFCritical Radio system phase131read-onlyC2STOPFSystem Stop flag for CPU2111read-onlyC2SBFSystem Standby flag for CPU2101read-onlyC1STOPFSystem Stop flag for CPU191read-onlyC1SBFSystem Standby flag for CPU181read-onlyCCRPFClear Critical Radio system phase21write-onlyC2CSSFClear CPU2 Stop Standby flags11write-onlyC1CSSFClear CPU1 Stop Standby flags01write-onlySYSCFG_VREFBUFSYSCFG_VREFBUFSYSCFG_VREFBUF0x400100000x00x200registersSYSCFG_MEMRMPSYSCFG_MEMRMPmemory remap register0x00x20read-write0x00000000MEM_MODEMemory mapping selection03SYSCFG_CFGR1SYSCFG_CFGR1configuration register 10x40x20read-write0x7C000001FPU_IEFloating Point Unit interrupts enable bits266I2C3_FMPI2C3 Fast-mode Plus driving capability activation221I2C1_FMPI2C1 Fast-mode Plus driving capability activation201I2C_PB9_FMPFast-mode Plus (Fm+) driving capability activation on PB9191I2C_PB8_FMPFast-mode Plus (Fm+) driving capability activation on PB8181I2C_PB7_FMPFast-mode Plus (Fm+) driving capability activation on PB7171I2C_PB6_FMPFast-mode Plus (Fm+) driving capability activation on PB6161BOOSTENI/O analog switch voltage booster enable81SYSCFG_EXTICR1SYSCFG_EXTICR1external interrupt configuration register 10x80x20read-write0x00000000EXTI3EXTI 3 configuration bits123EXTI2EXTI 2 configuration bits83EXTI1EXTI 1 configuration bits43EXTI0EXTI 0 configuration bits03SYSCFG_EXTICR2SYSCFG_EXTICR2external interrupt configuration register 20xC0x20read-write0x00000000EXTI7EXTI 7 configuration bits123EXTI6EXTI 6 configuration bits83EXTI5EXTI 5 configuration bits43EXTI4EXTI 4 configuration bits03SYSCFG_EXTICR3SYSCFG_EXTICR3external interrupt configuration register 30x100x20read-write0x00000000EXTI11EXTI 11 configuration bits123EXTI10EXTI 10 configuration bits83EXTI9EXTI 9 configuration bits43EXTI8EXTI 8 configuration bits03SYSCFG_EXTICR4SYSCFG_EXTICR4external interrupt configuration register 40x140x20read-write0x00000000EXTI15EXTI15 configuration bits123EXTI14EXTI14 configuration bits83EXTI13EXTI13 configuration bits43EXTI12EXTI12 configuration bits03SYSCFG_SCSRSYSCFG_SCSRSCSR0x180x200x00000000SRAM2BSYSRAM2 busy by erase operation11read-onlySRAM2ERSRAM2 Erase01read-writeC2RFDCPU2 SRAM fetch (execution) disable.311read-writeSYSCFG_CFGR2SYSCFG_CFGR2CFGR20x1C0x200x00000000SPFSRAM2 parity error flag81read-writeECCLECC Lock31write-onlyPVDLPVD lock enable bit21write-onlySPLSRAM2 parity lock bit11write-onlyCLLCortex-M4 LOCKUP (Hardfault) output enable bit01write-onlySYSCFG_SWPRSYSCFG_SWPRSRAM2 write protection register0x200x20write-only0x00000000P31WPSRAM2 page 31 write protection311P30WPP30WP301P29WPP29WP291P28WPP28WP281P27WPP27WP271P26WPP26WP261P25WPP25WP251P24WPP24WP241P23WPP23WP231P22WPP22WP221P21WPP21WP211P20WPP20WP201P19WPP19WP191P18WPP18WP181P17WPP17WP171P16WPP16WP161P15WPP15WP151P14WPP14WP141P13WPP13WP131P12WPP12WP121P11WPP11WP111P10WPP10WP101P9WPP9WP91P8WPP8WP81P7WPP7WP71P6WPP6WP61P5WPP5WP51P4WPP4WP41P3WPP3WP31P2WPP2WP21P1WPP1WP11P0WPP0WP01SYSCFG_SKRSYSCFG_SKRSKR0x240x20write-only0x00000000KEYSRAM2 write protection key for software erase08SYSCFG_SWPR2SYSCFG_SWPR2SRAM2 write protection register 20x280x20write-only0x00000000P63WPSRAM2 page 63 write protection311P62WPP62WP301P61WPP61WP291P60WPP60WP281P59WPP59WP271P58WPP58WP261P57WPP57WP251P56WPP56WP241P55WPP55WP231P54WPP54WP221P53WPP53WP211P52WPP52WP201P51WPP51WP191P50WPP50WP181P49WPP49WP171P48WPP48WP161P47WPP47WP151P46WPP46WP141P45WPP45WP131P44WPP44WP121P43WPP43WP111P42WPP42WP101P41WPP41WP91P40WPP40WP81P39WPP39WP71P38WPP38WP61P37WPP37WP51P36WPP36WP41P35WPP35WP31P34WPP34WP21P33WPP33WP11P32WPP32WP01VREFBUF_CSRVREFBUF_CSRVREF control and status register0x300x200x00000002ENVRVoltage reference buffer enable01read-writeHIZHigh impedance mode11read-writeVRSVoltage reference scale21read-writeVRRVoltage reference buffer ready31read-onlyVREFBUF_CCRVREFBUF_CCRcalibration control register0x340x20read-write0x00000000TRIMTrimming code06SYSCFG_IMR1SYSCFG_IMR1CPU1 interrupt mask register 10x1000x20read-write0x00000000TIM1IMPeripheral TIM1 interrupt mask to CPU1131TIM16IMPeripheral TIM16 interrupt mask to CPU1141TIM17IMPeripheral TIM17 interrupt mask to CPU1151EXIT5IMPeripheral EXIT5 interrupt mask to CPU1211EXIT6IMPeripheral EXIT6 interrupt mask to CPU1221EXIT7IMPeripheral EXIT7 interrupt mask to CPU1231EXIT8IMPeripheral EXIT8 interrupt mask to CPU1241EXIT9IMPeripheral EXIT9 interrupt mask to CPU1251EXIT10IMPeripheral EXIT10 interrupt mask to CPU1261EXIT11IMPeripheral EXIT11 interrupt mask to CPU1271EXIT12IMPeripheral EXIT12 interrupt mask to CPU1281EXIT13IMPeripheral EXIT13 interrupt mask to CPU1291EXIT14IMPeripheral EXIT14 interrupt mask to CPU1301EXIT15IMPeripheral EXIT15 interrupt mask to CPU1311SYSCFG_IMR2SYSCFG_IMR2CPU1 interrupt mask register 20x1040x20read-write0x00000000PVM3IMPeripheral PVM3 interrupt mask to CPU1181PVM1IMPeripheral PVM1 interrupt mask to CPU1161PVDIMPeripheral PVD interrupt mask to CPU1201SYSCFG_C2IMR1SYSCFG_C2IMR1CPU2 interrupt mask register 10x1080x20read-write0x00000000RTCSTAMPPeripheral RTCSTAMP interrupt mask to CPU201RTCWKUPPeripheral RTCWKUP interrupt mask to CPU231RTCALARMPeripheral RTCALARM interrupt mask to CPU241RCCPeripheral RCC interrupt mask to CPU251FLASHPeripheral FLASH interrupt mask to CPU261PKAPeripheral PKA interrupt mask to CPU281RNGPeripheral RNG interrupt mask to CPU291AES1Peripheral AES1 interrupt mask to CPU2101COMPPeripheral COMP interrupt mask to CPU2111ADCPeripheral ADC interrupt mask to CPU2121SYSCFG_C2IMR2SYSCFG_C2IMR2CPU2 interrupt mask register 10x10C0x20read-write0x00000000DMA1_CH1_IMPeripheral DMA1 CH1 interrupt mask to CPU201DMA1_CH2_IMPeripheral DMA1 CH2 interrupt mask to CPU211DMA1_CH3_IMPeripheral DMA1 CH3 interrupt mask to CPU221DMA1_CH4_IMPeripheral DMA1 CH4 interrupt mask to CPU231DMA1_CH5_IMPeripheral DMA1 CH5 interrupt mask to CPU241DMA1_CH6_IMPeripheral DMA1 CH6 interrupt mask to CPU251DMA1_CH7_IMPeripheral DMA1 CH7 interrupt mask to CPU261DMA2_CH1_IMPeripheral DMA2 CH1 interrupt mask to CPU181DMA2_CH2_IMPeripheral DMA2 CH2 interrupt mask to CPU191DMA2_CH3_IMPeripheral DMA2 CH3 interrupt mask to CPU1101DMA2_CH4_IMPeripheral DMA2 CH4 interrupt mask to CPU1111DMA2_CH5_IMPeripheral DMA2 CH5 interrupt mask to CPU1121DMA2_CH6_IMPeripheral DMA2 CH6 interrupt mask to CPU1131DMA2_CH7_IMPeripheral DMA2 CH7 interrupt mask to CPU1141DMAM_UX1_IMPeripheral DMAM UX1 interrupt mask to CPU1151PVM1IMPeripheral PVM1IM interrupt mask to CPU1161PVM3IMPeripheral PVM3IM interrupt mask to CPU1181PVDIMPeripheral PVDIM interrupt mask to CPU1201TSCIMPeripheral TSCIM interrupt mask to CPU1211LCDIMPeripheral LCDIM interrupt mask to CPU1221SYSCFG_SIPCRSYSCFG_SIPCRsecure IP control register0x1100x20read-write0x00000000SAES1Enable AES1 KEY[7:0] security.01SAES2Enable AES2 security.11SPKAEnable PKA security21SRNGEnable True RNG security31COMPComparator instance 1COMP0x400102000x00x9registersCOMPCOMP2 & COMP1 interrupt through + AIEC[21:20]22COMP1_CSRCOMP1_CSRComparator control and status register0x00x200x00000000COMP1_ENComparator enable01read-writeCOMP1_PWRMODEComparator power mode22read-writeCOMP1_INMSELComparator input minus selection43read-writeCOMP1_INPSELComparator input plus selection72read-writeCOMP1_POLARITYComparator output polarity151read-writeCOMP1_HYSTComparator hysteresis162read-writeCOMP1_BLANKINGComparator blanking source183read-writeCOMP1_BRGENComparator voltage scaler enable221read-writeCOMP1_SCALENComparator scaler bridge enable231read-writeCOMP1_INMESELComparator input minus extended selection252read-writeCOMP1_VALUEComparator output level301read-onlyCOMP1_LOCKComparator lock311read-writeCOMP2_CSRCOMP2_CSRComparator 2 control and status register0x40x200x00000000COMP2_ENComparator 2 enable bit01read-writeCOMP2_PWRMODEPower Mode of the comparator 222read-writeCOMP2_INMSELComparator 2 input minus selection bits42read-writeCOMP2_INPSELComparator 1 input plus selection bit72read-writeCOMP2_WINMODEWindows mode selection bit91read-writeCOMP2_POLARITYComparator 2 polarity selection bit151read-writeCOMP2_HYSTComparator 2 hysteresis selection bits162read-writeCOMP2_BLANKINGComparator 2 blanking source selection bits183read-writeCOMP2_BRGENScaler bridge enable221read-writeCOMP2_SCALENVoltage scaler enable bit231read-writeCOMP2_INMESELcomparator 2 input minus extended selection bits.252read-writeCOMP2_VALUEComparator 2 output status bit301read-onlyCOMP2_LOCKCSR register lock bit311read-writeRNGRandom number generatorRNG0x580010000x00x400registersTrue_RNGTrue random number generator + interrupt53CRCRcontrol register0x00x20read-write0x00000000RNGENRandom number generator enable21IEInterrupt enable31BYPBypass mode enable61SRSRstatus register0x40x200x00000000SEISSeed error interrupt status61read-writeCEISClock error interrupt status51read-writeSECSSeed error current status21read-onlyCECSClock error current status11read-onlyDRDYData ready01read-onlyDRDRdata register0x80x20read-only0x00000000RNDATARandom data032AES1Advanced encryption standard hardware accelerator 1AES10x500600000x00x400registersAES1AES1 global interrupt51CRCRcontrol register0x00x20read-write0x00000000NPBLBNumber of padding bytes in last block of payload204KEYSIZEKey size selection181CHMOD2AES chaining mode Bit2161GCMPHUsed only for GCM, CCM and GMAC algorithms and has no effect when other algorithms are selected132DMAOUTENEnable DMA management of data output phase121DMAINENEnable DMA management of data input phase111ERRIEError interrupt enable101CCFIECCF flag interrupt enable91ERRCError clear81CCFCComputation Complete Flag Clear71CHMOD10AES chaining mode Bit1 Bit052MODEAES operating mode32DATATYPEData type selection (for data in and data out to/from the cryptographic block)12ENAES enable01SRSRstatus register0x40x20read-only0x00000000BUSYBusy flag31WRERRWrite error flag21RDERRRead error flag11CCFComputation complete flag01DINRDINRdata input register0x80x20read-write0x00000000AES_DINRData Input Register032DOUTRDOUTRdata output register0xC0x20read-only0x00000000AES_DOUTRData output register032KEYR0KEYR0key register 00x100x20read-write0x00000000AES_KEYR0Data Output Register (LSB key [31:0])032KEYR1KEYR1key register 10x140x20read-write0x00000000AES_KEYR1AES key register (key [63:32])032KEYR2KEYR2key register 20x180x20read-write0x00000000AES_KEYR2AES key register (key [95:64])032KEYR3KEYR3key register 30x1C0x20read-write0x00000000AES_KEYR3AES key register (MSB key [127:96])032IVR0IVR0initialization vector register 00x200x20read-write0x00000000AES_IVR0initialization vector register (LSB IVR [31:0])032IVR1IVR1initialization vector register 10x240x20read-write0x00000000AES_IVR1Initialization Vector Register (IVR [63:32])032IVR2IVR2initialization vector register 20x280x20read-write0x00000000AES_IVR2Initialization Vector Register (IVR [95:64])032IVR3IVR3initialization vector register 30x2C0x20read-write0x00000000AES_IVR3Initialization Vector Register (MSB IVR [127:96])032KEYR4KEYR4key register 40x300x20read-write0x00000000AES_KEYR4AES key register (MSB key [159:128])032KEYR5KEYR5key register 50x340x20read-write0x00000000AES_KEYR5AES key register (MSB key [191:160])032KEYR6KEYR6key register 60x380x20read-write0x00000000AES_KEYR6AES key register (MSB key [223:192])032KEYR7KEYR7key register 70x3C0x20read-write0x00000000AES_KEYR7AES key register (MSB key [255:224])032SUSP0RSUSP0RAES suspend register 00x400x20read-write0x00000000AES_SUSP0RAES suspend register 0032SUSP1RSUSP1RAES suspend register 10x440x20read-write0x00000000AES_SUSP1RAES suspend register 1032SUSP2RSUSP2RAES suspend register 20x480x20read-write0x00000000AES_SUSP2RAES suspend register 2032SUSP3RSUSP3RAES suspend register 30x4C0x20read-write0x00000000AES_SUSP3RAES suspend register 3032SUSP4RSUSP4RAES suspend register 40x500x20read-write0x00000000AES_SUSP4RAES suspend register 4032SUSP5RSUSP5RAES suspend register 50x540x20read-write0x00000000AES_SUSP5RAES suspend register 5032SUSP6RSUSP6RAES suspend register 60x580x20read-write0x00000000AES_SUSP6RAES suspend register 6032SUSP7RSUSP7RAES suspend register 70x5C0x20read-write0x00000000AES_SUSP7RAES suspend register 7032HWCFRHWCFRAES hardware configuration register0x3F00x20read-only0x00000002CFG4HW Generic 4124CFG3HW Generic 384CFG2HW Generic 244CFG1HW Generic 104VERRVERRAES version register0x3F40x20read-only0x00000010MAJREVMajor revision44MINREVMinor revision04IPIDRIPIDRAES identification register0x3F80x20read-only0x00170023IDIdentification code032SIDRSIDRAES size ID register0x3FC0x20read-only0xA3C5DD01IDSize Identification code032AES2Advanced encryption standard hardware accelerator 1AES10x580018000x00x400registersAES2AES2 global interrupt52CRCRcontrol register0x00x20read-write0x00000000NPBLBNumber of padding bytes in last block of payload204KEYSIZEKey size selection181CHMOD2AES chaining mode Bit2161GCMPHUsed only for GCM, CCM and GMAC algorithms and has no effect when other algorithms are selected132DMAOUTENEnable DMA management of data output phase121DMAINENEnable DMA management of data input phase111ERRIEError interrupt enable101CCFIECCF flag interrupt enable91ERRCError clear81CCFCComputation Complete Flag Clear71CHMOD10AES chaining mode Bit1 Bit052MODEAES operating mode32DATATYPEData type selection (for data in and data out to/from the cryptographic block)12ENAES enable01SRSRstatus register0x40x20read-only0x00000000BUSYBusy flag31WRERRWrite error flag21RDERRRead error flag11CCFComputation complete flag01DINRDINRdata input register0x80x20read-write0x00000000AES_DINRData Input Register032DOUTRDOUTRdata output register0xC0x20read-only0x00000000AES_DOUTRData output register032KEYR0KEYR0key register 00x100x20read-write0x00000000AES_KEYR0Data Output Register (LSB key [31:0])032KEYR1KEYR1key register 10x140x20read-write0x00000000AES_KEYR1AES key register (key [63:32])032KEYR2KEYR2key register 20x180x20read-write0x00000000AES_KEYR2AES key register (key [95:64])032KEYR3KEYR3key register 30x1C0x20read-write0x00000000AES_KEYR3AES key register (MSB key [127:96])032IVR0IVR0initialization vector register 00x200x20read-write0x00000000AES_IVR0initialization vector register (LSB IVR [31:0])032IVR1IVR1initialization vector register 10x240x20read-write0x00000000AES_IVR1Initialization Vector Register (IVR [63:32])032IVR2IVR2initialization vector register 20x280x20read-write0x00000000AES_IVR2Initialization Vector Register (IVR [95:64])032IVR3IVR3initialization vector register 30x2C0x20read-write0x00000000AES_IVR3Initialization Vector Register (MSB IVR [127:96])032KEYR4KEYR4key register 40x300x20read-write0x00000000AES_KEYR4AES key register (MSB key [159:128])032KEYR5KEYR5key register 50x340x20read-write0x00000000AES_KEYR5AES key register (MSB key [191:160])032KEYR6KEYR6key register 60x380x20read-write0x00000000AES_KEYR6AES key register (MSB key [223:192])032KEYR7KEYR7key register 70x3C0x20read-write0x00000000AES_KEYR7AES key register (MSB key [255:224])032SUSP0RSUSP0RAES suspend register 00x400x20read-write0x00000000AES_SUSP0RAES suspend register 0032SUSP1RSUSP1RAES suspend register 10x440x20read-write0x00000000AES_SUSP1RAES suspend register 1032SUSP2RSUSP2RAES suspend register 20x480x20read-write0x00000000AES_SUSP2RAES suspend register 2032SUSP3RSUSP3RAES suspend register 30x4C0x20read-write0x00000000AES_SUSP3RAES suspend register 3032SUSP4RSUSP4RAES suspend register 40x500x20read-write0x00000000AES_SUSP4RAES suspend register 4032SUSP5RSUSP5RAES suspend register 50x540x20read-write0x00000000AES_SUSP5RAES suspend register 5032SUSP6RSUSP6RAES suspend register 60x580x20read-write0x00000000AES_SUSP6RAES suspend register 6032SUSP7RSUSP7RAES suspend register 70x5C0x20read-write0x00000000AES_SUSP7RAES suspend register 7032HWCFRHWCFRAES hardware configuration register0x600x20read-only0x00000002CFG4HW Generic 4124CFG3HW Generic 384CFG2HW Generic 244CFG1HW Generic 104VERRVERRAES version register0x640x20read-only0x00000010MAJREVMajor revision44MINREVMinor revision04IPIDRIPIDRAES identification register0x680x20read-only0x00170023IDIdentification code032SIDRSIDRAES size ID register0x6C0x20read-only0x00170023IDSize Identification code032HSEMHSEMHardware_Semaphore0x580014000x00x400registersHSEMSemaphore interrupt 0 to CPU146R0R0Semaphore 0 register0x00x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R1R1Semaphore 1 register0x40x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R2R2Semaphore 2 register0x80x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R3R3Semaphore 3 register0xC0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R4R4Semaphore 4 register0x100x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R5R5Semaphore 5 register0x140x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R6R6Semaphore 6 register0x180x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R7R7Semaphore 7 register0x1C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R8R8Semaphore 8 register0x200x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R9R9Semaphore 9 register0x240x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R10R10Semaphore 10 register0x280x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R11R11Semaphore 11 register0x2C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R12R12Semaphore 12 register0x300x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R13R13Semaphore 13 register0x340x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R14R14Semaphore 14 register0x380x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R15R15Semaphore 15 register0x3C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R16R16Semaphore 16 register0x400x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R17R17Semaphore 17 register0x440x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R18R18Semaphore 18 register0x480x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R19R19Semaphore 19 register0x4C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R20R20Semaphore 20 register0x500x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R21R21Semaphore 21 register0x540x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R22R22Semaphore 22 register0x580x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R23R23Semaphore 23 register0x5C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R24R24Semaphore 24 register0x600x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R25R25Semaphore 25 register0x640x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R26R26Semaphore 26 register0x680x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R27R27Semaphore 27 register0x6C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R28R28Semaphore 28 register0x700x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R29R29Semaphore 29 register0x740x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R30R30Semaphore 30 register0x780x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08R31R31Semaphore 31 register0x7C0x20read-write0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR0RLR0Semaphore 0 read lock register0x800x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR1RLR1Semaphore 1 read lock register0x840x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR2RLR2Semaphore 2 read lock register0x880x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR3RLR3Semaphore 3 read lock register0x8C0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR4RLR4Semaphore 4 read lock read lock register0x900x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR5RLR5Semaphore 5 read lock register0x940x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR6RLR6Semaphore 6 read lock register0x980x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR7RLR7Semaphore 7 read lock register0x9C0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR8RLR8Semaphore 8 read lock register0xA00x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR9RLR9Semaphore 9 read lock register0xA40x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR10RLR10Semaphore 10 read lock register0xA80x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR11RLR11Semaphore 11 read lock register0xAC0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR12RLR12Semaphore 12 read lock register0xB00x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR13RLR13Semaphore 13 read lock register0xB40x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR14RLR14Semaphore 14 read lock register0xB80x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR15RLR15Semaphore 15 read lock register0xBC0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR16RLR16Semaphore 16 read lock register0xC00x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR17RLR17Semaphore 17 read lock register0xC40x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR18RLR18Semaphore 18 read lock register0xC80x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR19RLR19Semaphore 19 read lock register0xCC0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR20RLR20Semaphore 20 read lock register0xD00x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR21RLR21Semaphore 21 read lock register0xD40x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR22RLR22Semaphore 22 read lock register0xD80x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR23RLR23Semaphore 23 read lock register0xDC0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR24RLR24Semaphore 24 read lock register0xE00x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR25RLR25Semaphore 25 read lock register0xE40x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR26RLR26Semaphore 26 read lock register0xE80x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR27RLR27Semaphore 27 read lock register0xEC0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR28RLR28Semaphore 28 read lock register0xF00x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR29RLR29Semaphore 29 read lock register0xF40x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR30RLR30Semaphore 30 read lock register0xF80x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08RLR31RLR31Semaphore 31 read lock register0xFC0x20read-only0x00000000LOCKlock indication311COREIDSemaphore CoreID84PROCIDSemaphore ProcessID08CRCRSemaphore Clear register0x1400x20read-write0x00000000KEYSemaphore clear Key1616COREIDCoreID of semaphore to be cleared84KEYRKEYRInterrupt clear register0x1440x20read-write0x00000000KEYSemaphore Clear Key1616HWCFGR2HWCFGR2Semaphore hardware configuration register 20x3EC0x20read-only0x00000084MASTERID4Hardware Configuration valid bus masters ID4124MASTERID3Hardware Configuration valid bus masters ID384MASTERID2Hardware Configuration valid bus masters ID244MASTERID1Hardware Configuration valid bus masters ID104HWCFGR1HWCFGR1Semaphore hardware configuration register 10x3F00x20read-only0x00000220NBINTHardware Configuration number of interrupts supported number of master IDs84NBSEMHardware Configuration number of semaphores08VERRVERRHSEM version register0x3F40x20read-only0x00000020MAJREVMajor Revision44MINREVMinor Revision04IPIDRIPIDRHSEM indentification register0x3F80x20read-only0x00100072IDIdentification Code032SIDRSIDRHSEM size indentification register0x3FC0x20read-only0xA3C5DD01SIDSize Identification Code032C1IER0C1IER0HSEM Interrupt enable register0x1000x20read-write0x00000000ISEmCPU(n) semaphore m enable bit032C1ICRC1ICRHSEM Interrupt clear register0x1040x20read-write0x00000000ISCmCPU(n) semaphore m clear bit032C1ISRC1ISRHSEM Interrupt status register0x1080x20read-only0x00000000ISFmCPU(n) semaphore m status bit before enable (mask)032C1MISRC1MISRHSEM Masked interrupt status register0x10C0x20read-only0x00000000MISFmmasked CPU(n) semaphore m status bit after enable (mask).032C2IER0C2IER0HSEM Interrupt enable register0x1100x20read-write0x00000000ISEmCPU(2) semaphore m enable bit.032C2ICRC2ICRHSEM Interrupt clear register0x1140x20read-write0x00000000ISCmCPU(2) semaphore m clear bit032C2ISRC2ISRHSEM Interrupt status register0x1180x20read-only0x00000000ISFmCPU(2) semaphore m status bit before enable (mask).032C2MISRC2MISRHSEM Masked interrupt status register0x11C0x20read-only0x00000000MISFmmasked CPU(2) semaphore m status bit after enable (mask).032ADCAnalog to Digital Converter instance 1ADC0x500400000x00x400registersADC1ADC1 global interrupt18ISRISRADC interrupt and status register0x00x20read-write0x00000000JQOVFADC group injected contexts queue overflow flag101AWD3ADC analog watchdog 3 flag91AWD2ADC analog watchdog 2 flag81AWD1ADC analog watchdog 1 flag71JEOSADC group injected end of sequence conversions flag61JEOCADC group injected end of unitary conversion flag51OVRADC group regular overrun flag41EOSADC group regular end of sequence conversions flag31EOCADC group regular end of unitary conversion flag21EOSMPADC group regular end of sampling flag11ADRDYADC ready flag01IERIERADC interrupt enable register0x40x20read-write0x00000000JQOVFIEADC group injected contexts queue overflow interrupt101AWD3IEADC analog watchdog 3 interrupt91AWD2IEADC analog watchdog 2 interrupt81AWD1IEADC analog watchdog 1 interrupt71JEOSIEADC group injected end of sequence conversions interrupt61JEOCIEADC group injected end of unitary conversion interrupt51OVRIEADC group regular overrun interrupt41EOSIEADC group regular end of sequence conversions interrupt31EOCIEADC group regular end of unitary conversion interrupt21EOSMPIEADC group regular end of sampling interrupt11ADRDYIEADC ready interrupt01CRCRADC control register0x80x20read-write0x00000000ADCALADC calibration311ADCALDIFADC differential mode for calibration301DEEPPWDADC deep power down enable291ADVREGENADC voltage regulator enable281JADSTPADC group injected conversion stop51ADSTPADC group regular conversion stop41JADSTARTADC group injected conversion start31ADSTARTADC group regular conversion start21ADDISADC disable11ADENADC enable01CFGRCFGRADC configuration register 10xC0x20read-write0x80000000JQDISADC group injected contexts queue disable311AWDCH1CHADC analog watchdog 1 monitored channel selection265JAUTOADC group injected automatic trigger mode251JAWD1ENADC analog watchdog 1 enable on scope ADC group injected241AWD1ENADC analog watchdog 1 enable on scope ADC group regular231AWD1SGLADC analog watchdog 1 monitoring a single channel or all channels221JQMADC group injected contexts queue mode211JDISCENADC group injected sequencer discontinuous mode201DISCNUMADC group regular sequencer discontinuous number of ranks173DISCENADC group regular sequencer discontinuous mode161AUTDLYADC low power auto wait141CONTADC group regular continuous conversion mode131OVRMODADC group regular overrun configuration121EXTENADC group regular external trigger polarity102EXTSELADC group regular external trigger source64ALIGNADC data alignement51RESADC data resolution32DMACFGADC DMA transfer configuration11DMAENADC DMA transfer enable01CFGR2CFGR2ADC configuration register 20x100x20read-write0x00000000ROVSMADC oversampling mode managing interlaced conversions of ADC group regular and group injected101TOVSADC oversampling discontinuous mode (triggered mode) for ADC group regular91OVSSADC oversampling shift54OVSRADC oversampling ratio23JOVSEADC oversampler enable on scope ADC group injected11ROVSEADC oversampler enable on scope ADC group regular01SMPR1SMPR1ADC sampling time register 10x140x20read-write0x00000000SMP9ADC channel 9 sampling time selection273SMP8ADC channel 8 sampling time selection243SMP7ADC channel 7 sampling time selection213SMP6ADC channel 6 sampling time selection183SMP5ADC channel 5 sampling time selection153SMP4ADC channel 4 sampling time selection123SMP3ADC channel 3 sampling time selection93SMP2ADC channel 2 sampling time selection63SMP1ADC channel 1 sampling time selection33SMPR2SMPR2ADC sampling time register 20x180x20read-write0x00000000SMP18ADC channel 18 sampling time selection243SMP17ADC channel 17 sampling time selection213SMP16ADC channel 16 sampling time selection183SMP15ADC channel 15 sampling time selection153SMP14ADC channel 14 sampling time selection123SMP13ADC channel 13 sampling time selection93SMP12ADC channel 12 sampling time selection63SMP11ADC channel 11 sampling time selection33SMP10ADC channel 10 sampling time selection03TR1TR1ADC analog watchdog 1 threshold register0x200x20read-write0x0FFF0000HT1ADC analog watchdog 1 threshold high1612LT1ADC analog watchdog 1 threshold low012TR2TR2ADC analog watchdog 2 threshold register0x240x20read-write0x0FFF0000HT2ADC analog watchdog 2 threshold high168LT2ADC analog watchdog 2 threshold low08TR3TR3ADC analog watchdog 3 threshold register0x280x20read-write0x0FFF0000HT3ADC analog watchdog 3 threshold high168LT3ADC analog watchdog 3 threshold low08SQR1SQR1ADC group regular sequencer ranks register 10x300x20read-write0x00000000SQ4ADC group regular sequencer rank 4245SQ3ADC group regular sequencer rank 3185SQ2ADC group regular sequencer rank 2125SQ1ADC group regular sequencer rank 165L3L304SQR2SQR2ADC group regular sequencer ranks register 20x340x20read-write0x00000000SQ9ADC group regular sequencer rank 9245SQ8ADC group regular sequencer rank 8185SQ7ADC group regular sequencer rank 7125SQ6ADC group regular sequencer rank 665SQ5ADC group regular sequencer rank 505SQR3SQR3ADC group regular sequencer ranks register 30x380x20read-write0x00000000SQ14ADC group regular sequencer rank 14245SQ13ADC group regular sequencer rank 13185SQ12ADC group regular sequencer rank 12125SQ11ADC group regular sequencer rank 1165SQ10ADC group regular sequencer rank 1005SQR4SQR4ADC group regular sequencer ranks register 40x3C0x20read-write0x00000000SQ16ADC group regular sequencer rank 1665SQ15ADC group regular sequencer rank 1505DRDRADC group regular conversion data register0x400x200x00000000RDATA_0_6Regular Data converted 0_606read-writeRDATA_7_151579read-onlyJSQRJSQRADC group injected sequencer register0x4C0x20read-write0x00000000JSQ4ADC group injected sequencer rank 4265JSQ3ADC group injected sequencer rank 3205JSQ2ADC group injected sequencer rank 2145JSQ1ADC group injected sequencer rank 185JEXTENADC group injected external trigger polarity62JEXTSELADC group injected external trigger source24JLADC group injected sequencer scan length02OFR1OFR1ADC offset number 1 register0x600x20read-write0x00000000OFFSET1_ENADC offset number 1 enable311OFFSET1_CHADC offset number 1 channel selection265OFFSET1ADC offset number 1 offset level012OFR2OFR2ADC offset number 2 register0x640x20read-write0x00000000OFFSET2_ENADC offset number 2 enable311OFFSET2_CHADC offset number 2 channel selection265OFFSET2ADC offset number 2 offset level012OFR3OFR3ADC offset number 3 register0x680x20read-write0x00000000OFFSET3_ENADC offset number 3 enable311OFFSET3_CHADC offset number 3 channel selection265OFFSET3ADC offset number 3 offset level012OFR4OFR4ADC offset number 4 register0x6C0x20read-write0x00000000OFFSET4_ENADC offset number 4 enable311OFFSET4_CHADC offset number 4 channel selection265OFFSET4ADC offset number 4 offset level012JDR1JDR1ADC group injected sequencer rank 1 register0x800x20read-only0x00000000JDATA1ADC group injected sequencer rank 1 conversion data016JDR2JDR2ADC group injected sequencer rank 2 register0x840x20read-only0x00000000JDATA2ADC group injected sequencer rank 2 conversion data016JDR3JDR3ADC group injected sequencer rank 3 register0x880x20read-only0x00000000JDATA3ADC group injected sequencer rank 3 conversion data016JDR4JDR4ADC group injected sequencer rank 4 register0x8C0x20read-only0x00000000JDATA4ADC group injected sequencer rank 4 conversion data016AWD2CRAWD2CRADC analog watchdog 2 configuration register0xA00x20read-write0x00000000AWD2CHADC analog watchdog 2 monitored channel selection019AWD3CRAWD3CRADC analog watchdog 3 configuration register0xA40x20read-write0x00000000AWD3CHADC analog watchdog 3 monitored channel selection019DIFSELDIFSELADC channel differential or single-ended mode selection register0xB00x200x00000000DIFSEL_0ADC channel differential or single-ended mode for channel 001read-onlyDIFSEL_1_15ADC channel differential or single-ended mode for channels 1 to 15115read-writeDIFSEL_16_18ADC channel differential or single-ended mode for channels 18 to 16163read-onlyCALFACTCALFACTADC calibration factors register0xB40x20read-write0x00000000CALFACT_DADC calibration factor in differential mode167CALFACT_SADC calibration factor in single-ended mode07CCRCCRADC common control register0x3080x20read-write0x00000000VBATENVBAT enable241TSENTemperature sensor enable231VREFENVREFEN221PRESCADC prescaler184CKMODEADC clock mode162GPIOAGeneral-purpose I/OsGPIO0x480000000x00x400registersMODERMODERGPIO port mode register0x00x20read-write0xABFFFFFFMODER15Port x configuration bits (y = 0..15)302MODER14Port x configuration bits (y = 0..15)282MODER13Port x configuration bits (y = 0..15)262MODER12Port x configuration bits (y = 0..15)242MODER11Port x configuration bits (y = 0..15)222MODER10Port x configuration bits (y = 0..15)202MODER9Port x configuration bits (y = 0..15)182MODER8Port x configuration bits (y = 0..15)162MODER7Port x configuration bits (y = 0..15)142MODER6Port x configuration bits (y = 0..15)122MODER5Port x configuration bits (y = 0..15)102MODER4Port x configuration bits (y = 0..15)82MODER3Port x configuration bits (y = 0..15)62MODER2Port x configuration bits (y = 0..15)42MODER1Port x configuration bits (y = 0..15)22MODER0Port x configuration bits (y = 0..15)02OTYPEROTYPERGPIO port output type register0x40x20read-write0x00000000OT15Port x configuration bits (y = 0..15)151OT14Port x configuration bits (y = 0..15)141OT13Port x configuration bits (y = 0..15)131OT12Port x configuration bits (y = 0..15)121OT11Port x configuration bits (y = 0..15)111OT10Port x configuration bits (y = 0..15)101OT9Port x configuration bits (y = 0..15)91OT8Port x configuration bits (y = 0..15)81OT7Port x configuration bits (y = 0..15)71OT6Port x configuration bits (y = 0..15)61OT5Port x configuration bits (y = 0..15)51OT4Port x configuration bits (y = 0..15)41OT3Port x configuration bits (y = 0..15)31OT2Port x configuration bits (y = 0..15)21OT1Port x configuration bits (y = 0..15)11OT0Port x configuration bits (y = 0..15)01OSPEEDROSPEEDRGPIO port output speed register0x80x20read-write0x0C000000OSPEEDR15Port x configuration bits (y = 0..15)302OSPEEDR14Port x configuration bits (y = 0..15)282OSPEEDR13Port x configuration bits (y = 0..15)262OSPEEDR12Port x configuration bits (y = 0..15)242OSPEEDR11Port x configuration bits (y = 0..15)222OSPEEDR10Port x configuration bits (y = 0..15)202OSPEEDR9Port x configuration bits (y = 0..15)182OSPEEDR8Port x configuration bits (y = 0..15)162OSPEEDR7Port x configuration bits (y = 0..15)142OSPEEDR6Port x configuration bits (y = 0..15)122OSPEEDR5Port x configuration bits (y = 0..15)102OSPEEDR4Port x configuration bits (y = 0..15)82OSPEEDR3Port x configuration bits (y = 0..15)62OSPEEDR2Port x configuration bits (y = 0..15)42OSPEEDR1Port x configuration bits (y = 0..15)22OSPEEDR0Port x configuration bits (y = 0..15)02PUPDRPUPDRGPIO port pull-up/pull-down register0xC0x20read-write0x64000000PUPDR15Port x configuration bits (y = 0..15)302PUPDR14Port x configuration bits (y = 0..15)282PUPDR13Port x configuration bits (y = 0..15)262PUPDR12Port x configuration bits (y = 0..15)242PUPDR11Port x configuration bits (y = 0..15)222PUPDR10Port x configuration bits (y = 0..15)202PUPDR9Port x configuration bits (y = 0..15)182PUPDR8Port x configuration bits (y = 0..15)162PUPDR7Port x configuration bits (y = 0..15)142PUPDR6Port x configuration bits (y = 0..15)122PUPDR5Port x configuration bits (y = 0..15)102PUPDR4Port x configuration bits (y = 0..15)82PUPDR3Port x configuration bits (y = 0..15)62PUPDR2Port x configuration bits (y = 0..15)42PUPDR1Port x configuration bits (y = 0..15)22PUPDR0Port x configuration bits (y = 0..15)02IDRIDRGPIO port input data register0x100x20read-only0x00000000IDR15Port input data (y = 0..15)151IDR14Port input data (y = 0..15)141IDR13Port input data (y = 0..15)131IDR12Port input data (y = 0..15)121IDR11Port input data (y = 0..15)111IDR10Port input data (y = 0..15)101IDR9Port input data (y = 0..15)91IDR8Port input data (y = 0..15)81IDR7Port input data (y = 0..15)71IDR6Port input data (y = 0..15)61IDR5Port input data (y = 0..15)51IDR4Port input data (y = 0..15)41IDR3Port input data (y = 0..15)31IDR2Port input data (y = 0..15)21IDR1Port input data (y = 0..15)11IDR0Port input data (y = 0..15)01ODRODRGPIO port output data register0x140x20read-write0x00000000ODR15Port output data (y = 0..15)151ODR14Port output data (y = 0..15)141ODR13Port output data (y = 0..15)131ODR12Port output data (y = 0..15)121ODR11Port output data (y = 0..15)111ODR10Port output data (y = 0..15)101ODR9Port output data (y = 0..15)91ODR8Port output data (y = 0..15)81ODR7Port output data (y = 0..15)71ODR6Port output data (y = 0..15)61ODR5Port output data (y = 0..15)51ODR4Port output data (y = 0..15)41ODR3Port output data (y = 0..15)31ODR2Port output data (y = 0..15)21ODR1Port output data (y = 0..15)11ODR0Port output data (y = 0..15)01BSRRBSRRGPIO port bit set/reset register0x180x20write-only0x00000000BR15Port x reset bit y (y = 0..15)311BR14Port x reset bit y (y = 0..15)301BR13Port x reset bit y (y = 0..15)291BR12Port x reset bit y (y = 0..15)281BR11Port x reset bit y (y = 0..15)271BR10Port x reset bit y (y = 0..15)261BR9Port x reset bit y (y = 0..15)251BR8Port x reset bit y (y = 0..15)241BR7Port x reset bit y (y = 0..15)231BR6Port x reset bit y (y = 0..15)221BR5Port x reset bit y (y = 0..15)211BR4Port x reset bit y (y = 0..15)201BR3Port x reset bit y (y = 0..15)191BR2Port x reset bit y (y = 0..15)181BR1Port x reset bit y (y = 0..15)171BR0Port x set bit y (y= 0..15)161BS15Port x set bit y (y= 0..15)151BS14Port x set bit y (y= 0..15)141BS13Port x set bit y (y= 0..15)131BS12Port x set bit y (y= 0..15)121BS11Port x set bit y (y= 0..15)111BS10Port x set bit y (y= 0..15)101BS9Port x set bit y (y= 0..15)91BS8Port x set bit y (y= 0..15)81BS7Port x set bit y (y= 0..15)71BS6Port x set bit y (y= 0..15)61BS5Port x set bit y (y= 0..15)51BS4Port x set bit y (y= 0..15)41BS3Port x set bit y (y= 0..15)31BS2Port x set bit y (y= 0..15)21BS1Port x set bit y (y= 0..15)11BS0Port x set bit y (y= 0..15)01LCKRLCKRGPIO port configuration lock register0x1C0x20read-write0x00000000LCKKPort x lock bit y (y= 0..15)161LCK15Port x lock bit y (y= 0..15)151LCK14Port x lock bit y (y= 0..15)141LCK13Port x lock bit y (y= 0..15)131LCK12Port x lock bit y (y= 0..15)121LCK11Port x lock bit y (y= 0..15)111LCK10Port x lock bit y (y= 0..15)101LCK9Port x lock bit y (y= 0..15)91LCK8Port x lock bit y (y= 0..15)81LCK7Port x lock bit y (y= 0..15)71LCK6Port x lock bit y (y= 0..15)61LCK5Port x lock bit y (y= 0..15)51LCK4Port x lock bit y (y= 0..15)41LCK3Port x lock bit y (y= 0..15)31LCK2Port x lock bit y (y= 0..15)21LCK1Port x lock bit y (y= 0..15)11LCK0Port x lock bit y (y= 0..15)01AFRLAFRLGPIO alternate function low register0x200x20read-write0x00000000AFSEL7Alternate function selection for port x bit y (y = 0..7)284AFSEL6Alternate function selection for port x bit y (y = 0..7)244AFSEL5Alternate function selection for port x bit y (y = 0..7)204AFSEL4Alternate function selection for port x bit y (y = 0..7)164AFSEL3Alternate function selection for port x bit y (y = 0..7)124AFSEL2Alternate function selection for port x bit y (y = 0..7)84AFSEL1Alternate function selection for port x bit y (y = 0..7)44AFSEL0Alternate function selection for port x bit y (y = 0..7)04AFRHAFRHGPIO alternate function high register0x240x20read-write0x00000000AFSEL15Alternate function selection for port x bit y (y = 8..15)284AFSEL14Alternate function selection for port x bit y (y = 8..15)244AFSEL13Alternate function selection for port x bit y (y = 8..15)204AFSEL12Alternate function selection for port x bit y (y = 8..15)164AFSEL11Alternate function selection for port x bit y (y = 8..15)124AFSEL10Alternate function selection for port x bit y (y = 8..15)84AFSEL9Alternate function selection for port x bit y (y = 8..15)44AFSEL8Alternate function selection for port x bit y (y = 8..15)04BRRBRRport bit reset register0x280x20write-only0x00000000BR0Port Reset bit01BR1Port Reset bit11BR2Port Reset bit21BR3Port Reset bit31BR4Port Reset bit41BR5Port Reset bit51BR6Port Reset bit61BR7Port Reset bit71BR8Port Reset bit81BR9Port Reset bit91BR10Port Reset bit101BR11Port Reset bit111BR12Port Reset bit121BR13Port Reset bit131BR14Port Reset bit141BR15Port Reset bit151GPIOBGeneral-purpose I/OsGPIO0x480004000x00x400registersMODERMODERGPIO port mode register0x00x20read-write0xFFFFFEBFMODER15Port x configuration bits (y = 0..15)302MODER14Port x configuration bits (y = 0..15)282MODER13Port x configuration bits (y = 0..15)262MODER12Port x configuration bits (y = 0..15)242MODER11Port x configuration bits (y = 0..15)222MODER10Port x configuration bits (y = 0..15)202MODER9Port x configuration bits (y = 0..15)182MODER8Port x configuration bits (y = 0..15)162MODER7Port x configuration bits (y = 0..15)142MODER6Port x configuration bits (y = 0..15)122MODER5Port x configuration bits (y = 0..15)102MODER4Port x configuration bits (y = 0..15)82MODER3Port x configuration bits (y = 0..15)62MODER2Port x configuration bits (y = 0..15)42MODER1Port x configuration bits (y = 0..15)22MODER0Port x configuration bits (y = 0..15)02OTYPEROTYPERGPIO port output type register0x40x20read-write0x00000000OT15Port x configuration bits (y = 0..15)151OT14Port x configuration bits (y = 0..15)141OT13Port x configuration bits (y = 0..15)131OT12Port x configuration bits (y = 0..15)121OT11Port x configuration bits (y = 0..15)111OT10Port x configuration bits (y = 0..15)101OT9Port x configuration bits (y = 0..15)91OT8Port x configuration bits (y = 0..15)81OT7Port x configuration bits (y = 0..15)71OT6Port x configuration bits (y = 0..15)61OT5Port x configuration bits (y = 0..15)51OT4Port x configuration bits (y = 0..15)41OT3Port x configuration bits (y = 0..15)31OT2Port x configuration bits (y = 0..15)21OT1Port x configuration bits (y = 0..15)11OT0Port x configuration bits (y = 0..15)01OSPEEDROSPEEDRGPIO port output speed register0x80x20read-write0x000000C0OSPEEDR15Port x configuration bits (y = 0..15)302OSPEEDR14Port x configuration bits (y = 0..15)282OSPEEDR13Port x configuration bits (y = 0..15)262OSPEEDR12Port x configuration bits (y = 0..15)242OSPEEDR11Port x configuration bits (y = 0..15)222OSPEEDR10Port x configuration bits (y = 0..15)202OSPEEDR9Port x configuration bits (y = 0..15)182OSPEEDR8Port x configuration bits (y = 0..15)162OSPEEDR7Port x configuration bits (y = 0..15)142OSPEEDR6Port x configuration bits (y = 0..15)122OSPEEDR5Port x configuration bits (y = 0..15)102OSPEEDR4Port x configuration bits (y = 0..15)82OSPEEDR3Port x configuration bits (y = 0..15)62OSPEEDR2Port x configuration bits (y = 0..15)42OSPEEDR1Port x configuration bits (y = 0..15)22OSPEEDR0Port x configuration bits (y = 0..15)02PUPDRPUPDRGPIO port pull-up/pull-down register0xC0x20read-write0x00000100PUPDR15Port x configuration bits (y = 0..15)302PUPDR14Port x configuration bits (y = 0..15)282PUPDR13Port x configuration bits (y = 0..15)262PUPDR12Port x configuration bits (y = 0..15)242PUPDR11Port x configuration bits (y = 0..15)222PUPDR10Port x configuration bits (y = 0..15)202PUPDR9Port x configuration bits (y = 0..15)182PUPDR8Port x configuration bits (y = 0..15)162PUPDR7Port x configuration bits (y = 0..15)142PUPDR6Port x configuration bits (y = 0..15)122PUPDR5Port x configuration bits (y = 0..15)102PUPDR4Port x configuration bits (y = 0..15)82PUPDR3Port x configuration bits (y = 0..15)62PUPDR2Port x configuration bits (y = 0..15)42PUPDR1Port x configuration bits (y = 0..15)22PUPDR0Port x configuration bits (y = 0..15)02IDRIDRGPIO port input data register0x100x20read-only0x00000000IDR15Port input data (y = 0..15)151IDR14Port input data (y = 0..15)141IDR13Port input data (y = 0..15)131IDR12Port input data (y = 0..15)121IDR11Port input data (y = 0..15)111IDR10Port input data (y = 0..15)101IDR9Port input data (y = 0..15)91IDR8Port input data (y = 0..15)81IDR7Port input data (y = 0..15)71IDR6Port input data (y = 0..15)61IDR5Port input data (y = 0..15)51IDR4Port input data (y = 0..15)41IDR3Port input data (y = 0..15)31IDR2Port input data (y = 0..15)21IDR1Port input data (y = 0..15)11IDR0Port input data (y = 0..15)01ODRODRGPIO port output data register0x140x20read-write0x00000000ODR15Port output data (y = 0..15)151ODR14Port output data (y = 0..15)141ODR13Port output data (y = 0..15)131ODR12Port output data (y = 0..15)121ODR11Port output data (y = 0..15)111ODR10Port output data (y = 0..15)101ODR9Port output data (y = 0..15)91ODR8Port output data (y = 0..15)81ODR7Port output data (y = 0..15)71ODR6Port output data (y = 0..15)61ODR5Port output data (y = 0..15)51ODR4Port output data (y = 0..15)41ODR3Port output data (y = 0..15)31ODR2Port output data (y = 0..15)21ODR1Port output data (y = 0..15)11ODR0Port output data (y = 0..15)01BSRRBSRRGPIO port bit set/reset register0x180x20write-only0x00000000BR15Port x reset bit y (y = 0..15)311BR14Port x reset bit y (y = 0..15)301BR13Port x reset bit y (y = 0..15)291BR12Port x reset bit y (y = 0..15)281BR11Port x reset bit y (y = 0..15)271BR10Port x reset bit y (y = 0..15)261BR9Port x reset bit y (y = 0..15)251BR8Port x reset bit y (y = 0..15)241BR7Port x reset bit y (y = 0..15)231BR6Port x reset bit y (y = 0..15)221BR5Port x reset bit y (y = 0..15)211BR4Port x reset bit y (y = 0..15)201BR3Port x reset bit y (y = 0..15)191BR2Port x reset bit y (y = 0..15)181BR1Port x reset bit y (y = 0..15)171BR0Port x set bit y (y= 0..15)161BS15Port x set bit y (y= 0..15)151BS14Port x set bit y (y= 0..15)141BS13Port x set bit y (y= 0..15)131BS12Port x set bit y (y= 0..15)121BS11Port x set bit y (y= 0..15)111BS10Port x set bit y (y= 0..15)101BS9Port x set bit y (y= 0..15)91BS8Port x set bit y (y= 0..15)81BS7Port x set bit y (y= 0..15)71BS6Port x set bit y (y= 0..15)61BS5Port x set bit y (y= 0..15)51BS4Port x set bit y (y= 0..15)41BS3Port x set bit y (y= 0..15)31BS2Port x set bit y (y= 0..15)21BS1Port x set bit y (y= 0..15)11BS0Port x set bit y (y= 0..15)01LCKRLCKRGPIO port configuration lock register0x1C0x20read-write0x00000000LCKKPort x lock bit y (y= 0..15)161LCK15Port x lock bit y (y= 0..15)151LCK14Port x lock bit y (y= 0..15)141LCK13Port x lock bit y (y= 0..15)131LCK12Port x lock bit y (y= 0..15)121LCK11Port x lock bit y (y= 0..15)111LCK10Port x lock bit y (y= 0..15)101LCK9Port x lock bit y (y= 0..15)91LCK8Port x lock bit y (y= 0..15)81LCK7Port x lock bit y (y= 0..15)71LCK6Port x lock bit y (y= 0..15)61LCK5Port x lock bit y (y= 0..15)51LCK4Port x lock bit y (y= 0..15)41LCK3Port x lock bit y (y= 0..15)31LCK2Port x lock bit y (y= 0..15)21LCK1Port x lock bit y (y= 0..15)11LCK0Port x lock bit y (y= 0..15)01AFRLAFRLGPIO alternate function low register0x200x20read-write0x00000000AFSEL7Alternate function selection for port x bit y (y = 0..7)284AFSEL6Alternate function selection for port x bit y (y = 0..7)244AFSEL5Alternate function selection for port x bit y (y = 0..7)204AFSEL4Alternate function selection for port x bit y (y = 0..7)164AFSEL3Alternate function selection for port x bit y (y = 0..7)124AFSEL2Alternate function selection for port x bit y (y = 0..7)84AFSEL1Alternate function selection for port x bit y (y = 0..7)44AFSEL0Alternate function selection for port x bit y (y = 0..7)04AFRHAFRHGPIO alternate function high register0x240x20read-write0x00000000AFSEL15Alternate function selection for port x bit y (y = 8..15)284AFSEL14Alternate function selection for port x bit y (y = 8..15)244AFSEL13Alternate function selection for port x bit y (y = 8..15)204AFSEL12Alternate function selection for port x bit y (y = 8..15)164AFSEL11Alternate function selection for port x bit y (y = 8..15)124AFSEL10Alternate function selection for port x bit y (y = 8..15)84AFSEL9Alternate function selection for port x bit y (y = 8..15)44AFSEL8Alternate function selection for port x bit y (y = 8..15)04BRRBRRport bit reset register0x280x20write-only0x00000000BR0Port Reset bit01BR1Port Reset bit11BR2Port Reset bit21BR3Port Reset bit31BR4Port Reset bit41BR5Port Reset bit51BR6Port Reset bit61BR7Port Reset bit71BR8Port Reset bit81BR9Port Reset bit91BR10Port Reset bit101BR11Port Reset bit111BR12Port Reset bit121BR13Port Reset bit131BR14Port Reset bit141BR15Port Reset bit151GPIOCGeneral-purpose I/OsGPIO0x480008000x00x400registersMODERMODERGPIO port mode register0x00x20read-write0xFFFFFFFFMODER15Port x configuration bits (y = 0..15)302MODER14Port x configuration bits (y = 0..15)282MODER13Port x configuration bits (y = 0..15)262MODER12Port x configuration bits (y = 0..15)242MODER11Port x configuration bits (y = 0..15)222MODER10Port x configuration bits (y = 0..15)202MODER9Port x configuration bits (y = 0..15)182MODER8Port x configuration bits (y = 0..15)162MODER7Port x configuration bits (y = 0..15)142MODER6Port x configuration bits (y = 0..15)122MODER5Port x configuration bits (y = 0..15)102MODER4Port x configuration bits (y = 0..15)82MODER3Port x configuration bits (y = 0..15)62MODER2Port x configuration bits (y = 0..15)42MODER1Port x configuration bits (y = 0..15)22MODER0Port x configuration bits (y = 0..15)02OTYPEROTYPERGPIO port output type register0x40x20read-write0x00000000OT15Port x configuration bits (y = 0..15)151OT14Port x configuration bits (y = 0..15)141OT13Port x configuration bits (y = 0..15)131OT12Port x configuration bits (y = 0..15)121OT11Port x configuration bits (y = 0..15)111OT10Port x configuration bits (y = 0..15)101OT9Port x configuration bits (y = 0..15)91OT8Port x configuration bits (y = 0..15)81OT7Port x configuration bits (y = 0..15)71OT6Port x configuration bits (y = 0..15)61OT5Port x configuration bits (y = 0..15)51OT4Port x configuration bits (y = 0..15)41OT3Port x configuration bits (y = 0..15)31OT2Port x configuration bits (y = 0..15)21OT1Port x configuration bits (y = 0..15)11OT0Port x configuration bits (y = 0..15)01OSPEEDROSPEEDRGPIO port output speed register0x80x20read-write0x000000C0OSPEEDR15Port x configuration bits (y = 0..15)302OSPEEDR14Port x configuration bits (y = 0..15)282OSPEEDR13Port x configuration bits (y = 0..15)262OSPEEDR12Port x configuration bits (y = 0..15)242OSPEEDR11Port x configuration bits (y = 0..15)222OSPEEDR10Port x configuration bits (y = 0..15)202OSPEEDR9Port x configuration bits (y = 0..15)182OSPEEDR8Port x configuration bits (y = 0..15)162OSPEEDR7Port x configuration bits (y = 0..15)142OSPEEDR6Port x configuration bits (y = 0..15)122OSPEEDR5Port x configuration bits (y = 0..15)102OSPEEDR4Port x configuration bits (y = 0..15)82OSPEEDR3Port x configuration bits (y = 0..15)62OSPEEDR2Port x configuration bits (y = 0..15)42OSPEEDR1Port x configuration bits (y = 0..15)22OSPEEDR0Port x configuration bits (y = 0..15)02PUPDRPUPDRGPIO port pull-up/pull-down register0xC0x20read-write0x00000100PUPDR15Port x configuration bits (y = 0..15)302PUPDR14Port x configuration bits (y = 0..15)282PUPDR13Port x configuration bits (y = 0..15)262PUPDR12Port x configuration bits (y = 0..15)242PUPDR11Port x configuration bits (y = 0..15)222PUPDR10Port x configuration bits (y = 0..15)202PUPDR9Port x configuration bits (y = 0..15)182PUPDR8Port x configuration bits (y = 0..15)162PUPDR7Port x configuration bits (y = 0..15)142PUPDR6Port x configuration bits (y = 0..15)122PUPDR5Port x configuration bits (y = 0..15)102PUPDR4Port x configuration bits (y = 0..15)82PUPDR3Port x configuration bits (y = 0..15)62PUPDR2Port x configuration bits (y = 0..15)42PUPDR1Port x configuration bits (y = 0..15)22PUPDR0Port x configuration bits (y = 0..15)02IDRIDRGPIO port input data register0x100x20read-only0x00000000IDR15Port input data (y = 0..15)151IDR14Port input data (y = 0..15)141IDR13Port input data (y = 0..15)131IDR12Port input data (y = 0..15)121IDR11Port input data (y = 0..15)111IDR10Port input data (y = 0..15)101IDR9Port input data (y = 0..15)91IDR8Port input data (y = 0..15)81IDR7Port input data (y = 0..15)71IDR6Port input data (y = 0..15)61IDR5Port input data (y = 0..15)51IDR4Port input data (y = 0..15)41IDR3Port input data (y = 0..15)31IDR2Port input data (y = 0..15)21IDR1Port input data (y = 0..15)11IDR0Port input data (y = 0..15)01ODRODRGPIO port output data register0x140x20read-write0x00000000ODR15Port output data (y = 0..15)151ODR14Port output data (y = 0..15)141ODR13Port output data (y = 0..15)131ODR12Port output data (y = 0..15)121ODR11Port output data (y = 0..15)111ODR10Port output data (y = 0..15)101ODR9Port output data (y = 0..15)91ODR8Port output data (y = 0..15)81ODR7Port output data (y = 0..15)71ODR6Port output data (y = 0..15)61ODR5Port output data (y = 0..15)51ODR4Port output data (y = 0..15)41ODR3Port output data (y = 0..15)31ODR2Port output data (y = 0..15)21ODR1Port output data (y = 0..15)11ODR0Port output data (y = 0..15)01BSRRBSRRGPIO port bit set/reset register0x180x20write-only0x00000000BR15Port x reset bit y (y = 0..15)311BR14Port x reset bit y (y = 0..15)301BR13Port x reset bit y (y = 0..15)291BR12Port x reset bit y (y = 0..15)281BR11Port x reset bit y (y = 0..15)271BR10Port x reset bit y (y = 0..15)261BR9Port x reset bit y (y = 0..15)251BR8Port x reset bit y (y = 0..15)241BR7Port x reset bit y (y = 0..15)231BR6Port x reset bit y (y = 0..15)221BR5Port x reset bit y (y = 0..15)211BR4Port x reset bit y (y = 0..15)201BR3Port x reset bit y (y = 0..15)191BR2Port x reset bit y (y = 0..15)181BR1Port x reset bit y (y = 0..15)171BR0Port x set bit y (y= 0..15)161BS15Port x set bit y (y= 0..15)151BS14Port x set bit y (y= 0..15)141BS13Port x set bit y (y= 0..15)131BS12Port x set bit y (y= 0..15)121BS11Port x set bit y (y= 0..15)111BS10Port x set bit y (y= 0..15)101BS9Port x set bit y (y= 0..15)91BS8Port x set bit y (y= 0..15)81BS7Port x set bit y (y= 0..15)71BS6Port x set bit y (y= 0..15)61BS5Port x set bit y (y= 0..15)51BS4Port x set bit y (y= 0..15)41BS3Port x set bit y (y= 0..15)31BS2Port x set bit y (y= 0..15)21BS1Port x set bit y (y= 0..15)11BS0Port x set bit y (y= 0..15)01LCKRLCKRGPIO port configuration lock register0x1C0x20read-write0x00000000LCKKPort x lock bit y (y= 0..15)161LCK15Port x lock bit y (y= 0..15)151LCK14Port x lock bit y (y= 0..15)141LCK13Port x lock bit y (y= 0..15)131LCK12Port x lock bit y (y= 0..15)121LCK11Port x lock bit y (y= 0..15)111LCK10Port x lock bit y (y= 0..15)101LCK9Port x lock bit y (y= 0..15)91LCK8Port x lock bit y (y= 0..15)81LCK7Port x lock bit y (y= 0..15)71LCK6Port x lock bit y (y= 0..15)61LCK5Port x lock bit y (y= 0..15)51LCK4Port x lock bit y (y= 0..15)41LCK3Port x lock bit y (y= 0..15)31LCK2Port x lock bit y (y= 0..15)21LCK1Port x lock bit y (y= 0..15)11LCK0Port x lock bit y (y= 0..15)01AFRLAFRLGPIO alternate function low register0x200x20read-write0x00000000AFSEL7Alternate function selection for port x bit y (y = 0..7)284AFSEL6Alternate function selection for port x bit y (y = 0..7)244AFSEL5Alternate function selection for port x bit y (y = 0..7)204AFSEL4Alternate function selection for port x bit y (y = 0..7)164AFSEL3Alternate function selection for port x bit y (y = 0..7)124AFSEL2Alternate function selection for port x bit y (y = 0..7)84AFSEL1Alternate function selection for port x bit y (y = 0..7)44AFSEL0Alternate function selection for port x bit y (y = 0..7)04AFRHAFRHGPIO alternate function high register0x240x20read-write0x00000000AFSEL15Alternate function selection for port x bit y (y = 8..15)284AFSEL14Alternate function selection for port x bit y (y = 8..15)244AFSEL13Alternate function selection for port x bit y (y = 8..15)204AFSEL12Alternate function selection for port x bit y (y = 8..15)164AFSEL11Alternate function selection for port x bit y (y = 8..15)124AFSEL10Alternate function selection for port x bit y (y = 8..15)84AFSEL9Alternate function selection for port x bit y (y = 8..15)44AFSEL8Alternate function selection for port x bit y (y = 8..15)04BRRBRRport bit reset register0x280x20write-only0x00000000BR0Port Reset bit01BR1Port Reset bit11BR2Port Reset bit21BR3Port Reset bit31BR4Port Reset bit41BR5Port Reset bit51BR6Port Reset bit61BR7Port Reset bit71BR8Port Reset bit81BR9Port Reset bit91BR10Port Reset bit101BR11Port Reset bit111BR12Port Reset bit121BR13Port Reset bit131BR14Port Reset bit141BR15Port Reset bit151GPIOD0x48000C00GPIOEGeneral-purpose I/OsGPIO0x480010000x00x400registersMODERMODERGPIO port mode register0x00x20read-write0x000003FFMODER4Port x configuration bits (y = 0..15)82MODER3Port x configuration bits (y = 0..15)62MODER2Port x configuration bits (y = 0..15)42MODER1Port x configuration bits (y = 0..15)22MODER0Port x configuration bits (y = 0..15)02OTYPEROTYPERGPIO port output type register0x40x20read-write0x00000000OT4Port x configuration bits (y = 0..15)41OT3Port x configuration bits (y = 0..15)31OT2Port x configuration bits (y = 0..15)21OT1Port x configuration bits (y = 0..15)11OT0Port x configuration bits (y = 0..15)01OSPEEDROSPEEDRGPIO port output speed register0x80x20read-write0x000000C0OSPEEDR4Port x configuration bits (y = 0..15)82OSPEEDR3Port x configuration bits (y = 0..15)62OSPEEDR2Port x configuration bits (y = 0..15)42OSPEEDR1Port x configuration bits (y = 0..15)22OSPEEDR0Port x configuration bits (y = 0..15)02PUPDRPUPDRGPIO port pull-up/pull-down register0xC0x20read-write0x00000000PUPDR4Port x configuration bits (y = 0..15)82PUPDR3Port x configuration bits (y = 0..15)62PUPDR2Port x configuration bits (y = 0..15)42PUPDR1Port x configuration bits (y = 0..15)22PUPDR0Port x configuration bits (y = 0..15)02IDRIDRGPIO port input data register0x100x20read-only0x00000000IDR4Port input data (y = 0..15)41IDR3Port input data (y = 0..15)31IDR2Port input data (y = 0..15)21IDR1Port input data (y = 0..15)11IDR0Port input data (y = 0..15)01ODRODRGPIO port output data register0x140x20read-write0x00000000ODR4Port output data (y = 0..15)41ODR3Port output data (y = 0..15)31ODR2Port output data (y = 0..15)21ODR1Port output data (y = 0..15)11ODR0Port output data (y = 0..15)01BSRRBSRRGPIO port bit set/reset register0x180x20write-only0x00000000BR4Port x reset bit y (y = 0..15)201BR3Port x reset bit y (y = 0..15)191BR2Port x reset bit y (y = 0..15)181BR1Port x reset bit y (y = 0..15)171BR0Port x set bit y (y= 0..15)161BS4Port x set bit y (y= 0..15)41BS3Port x set bit y (y= 0..15)31BS2Port x set bit y (y= 0..15)21BS1Port x set bit y (y= 0..15)11BS0Port x set bit y (y= 0..15)01LCKRLCKRGPIO port configuration lock register0x1C0x20read-write0x00000000LCKKPort x lock bit y (y= 0..15)161LCK4Port x lock bit y (y= 0..15)41LCK3Port x lock bit y (y= 0..15)31LCK2Port x lock bit y (y= 0..15)21LCK1Port x lock bit y (y= 0..15)11LCK0Port x lock bit y (y= 0..15)01AFRLAFRLGPIO alternate function low register0x200x20read-write0x00000000AFSEL4Alternate function selection for port x bit y (y = 0..7)164AFSEL3Alternate function selection for port x bit y (y = 0..7)124AFSEL2Alternate function selection for port x bit y (y = 0..7)84AFSEL1Alternate function selection for port x bit y (y = 0..7)44AFSEL0Alternate function selection for port x bit y (y = 0..7)04AFRHAFRHGPIO alternate function high register0x240x20read-write0x00000000AFSEL15Alternate function selection for port x bit y (y = 8..15)284AFSEL14Alternate function selection for port x bit y (y = 8..15)244AFSEL13Alternate function selection for port x bit y (y = 8..15)204AFSEL12Alternate function selection for port x bit y (y = 8..15)164AFSEL11Alternate function selection for port x bit y (y = 8..15)124AFSEL10Alternate function selection for port x bit y (y = 8..15)84AFSEL9Alternate function selection for port x bit y (y = 8..15)44AFSEL8Alternate function selection for port x bit y (y = 8..15)04BRRBRRport bit reset register0x280x20write-only0x00000000BR0Port Reset bit01BR1Port Reset bit11BR2Port Reset bit21BR3Port Reset bit31BR4Port Reset bit41GPIOHGeneral-purpose I/OsGPIO0x48001C000x00x400registersMODERMODERGPIO port mode register0x00x20read-write0x000000CFMODER3Port x configuration bits (y = 0..15)62MODER1Port x configuration bits (y = 0..15)22MODER0Port x configuration bits (y = 0..15)02OTYPEROTYPERGPIO port output type register0x40x20read-write0x00000000OT3Port x configuration bits (y = 0..15)31OT1Port x configuration bits (y = 0..15)11OT0Port x configuration bits (y = 0..15)01OSPEEDROSPEEDRGPIO port output speed register0x80x20read-write0x00000000OSPEEDR3Port x configuration bits (y = 0..15)62OSPEEDR1Port x configuration bits (y = 0..15)22OSPEEDR0Port x configuration bits (y = 0..15)02PUPDRPUPDRGPIO port pull-up/pull-down register0xC0x20read-write0x00000000PUPDR3Port x configuration bits (y = 0..15)62PUPDR1Port x configuration bits (y = 0..15)22PUPDR0Port x configuration bits (y = 0..15)02IDRIDRGPIO port input data register0x100x20read-only0x00000000IDR3Port input data (y = 0..15)31IDR1Port input data (y = 0..15)11IDR0Port input data (y = 0..15)01ODRODRGPIO port output data register0x140x20read-write0x00000000ODR3Port output data (y = 0..15)31ODR1Port output data (y = 0..15)11ODR0Port output data (y = 0..15)01BSRRBSRRGPIO port bit set/reset register0x180x20write-only0x00000000BR3Port x reset bit y (y = 0..15)191BR1Port x reset bit y (y = 0..15)171BR0Port x set bit y (y= 0..15)161BS3Port x set bit y (y= 0..15)31BS1Port x set bit y (y= 0..15)11BS0Port x set bit y (y= 0..15)01LCKRLCKRGPIO port configuration lock register0x1C0x20read-write0x00000000LCKKPort x lock bit y (y= 0..15)161LCK3Port x lock bit y (y= 0..15)31LCK1Port x lock bit y (y= 0..15)11LCK0Port x lock bit y (y= 0..15)01AFRLAFRLGPIO alternate function low register0x200x20read-write0x00000000AFSEL3Alternate function selection for port x bit y (y = 0..7)124AFSEL1Alternate function selection for port x bit y (y = 0..7)44AFSEL0Alternate function selection for port x bit y (y = 0..7)04AFRHAFRHGPIO alternate function high register0x240x20read-write0x00000000AFSEL15Alternate function selection for port x bit y (y = 8..15)284AFSEL14Alternate function selection for port x bit y (y = 8..15)244AFSEL13Alternate function selection for port x bit y (y = 8..15)204AFSEL12Alternate function selection for port x bit y (y = 8..15)164AFSEL11Alternate function selection for port x bit y (y = 8..15)124AFSEL10Alternate function selection for port x bit y (y = 8..15)84AFSEL9Alternate function selection for port x bit y (y = 8..15)44AFSEL8Alternate function selection for port x bit y (y = 8..15)04BRRBRRport bit reset register0x280x20write-only0x00000000BR0Port Reset bit01BR1Port Reset bit11BR3Port Reset bit31SAI1Serial audio interfaceSAI0x400154000x00x400registersSAI1SAI1 global interrupt38GCRGCRGlobal configuration register0x00x20read-write0x00000000SYNCOUTSynchronization outputs42SYNCINSynchronization inputs02BCR1BCR1BConfiguration register 10x240x20read-write0x00000040MCKENMaster clock generation enable271OSROversampling ratio for master clock261MCJDIVMaster clock divider206NODIVNo divider191DMAENDMA enable171SAIBENAudio block B enable161OutDriOutput drive131MONOMono mode121SYNCENSynchronization enable102CKSTRClock strobing edge91LSBFIRSTLeast significant bit first81DSData size53PRTCFGProtocol configuration22MODEAudio block mode02BCR2BCR2BConfiguration register 20x280x20read-write0x00000000COMPCompanding mode142CPLComplement bit131MUTECNMute counter76MUTEVALMute value61MUTEMute51TRISTristate management on data line41FFLUSFIFO flush31FTHFIFO threshold03BFRCRBFRCRBFRCR0x2C0x20read-write0x00000007FSOFFFrame synchronization offset181FSPOLFrame synchronization polarity171FSDEFFrame synchronization definition161FSALLFrame synchronization active level length87FRLFrame length08BSLOTRBSLOTRBSlot register0x300x20read-write0x00000000SLOTENSlot enable1616NBSLOTNumber of slots in an audio frame84SLOTSZSlot size62FBOFFFirst bit offset05BIMBIMBInterrupt mask register20x340x20read-write0x00000000LFSDETIELate frame synchronization detection interrupt enable61AFSDETIEAnticipated frame synchronization detection interrupt enable51CNRDYIECodec not ready interrupt enable41FREQIEFIFO request interrupt enable31WCKCFGWrong clock configuration interrupt enable21MUTEDETMute detection interrupt enable11OVRUDRIEOverrun/underrun interrupt enable01BSRBSRBStatus register0x380x20read-only0x00000008FLVLFIFO level threshold163LFSDETLate frame synchronization detection61AFSDETAnticipated frame synchronization detection51CNRDYCodec not ready41FREQFIFO request31WCKCFGWrong clock configuration flag21MUTEDETMute detection11OVRUDROverrun / underrun01BCLRFRBCLRFRBClear flag register0x3C0x20write-only0x00000000LFSDETClear late frame synchronization detection flag61CAFSDETClear anticipated frame synchronization detection flag51CNRDYClear codec not ready flag41WCKCFGClear wrong clock configuration flag21MUTEDETMute detection flag11OVRUDRClear overrun / underrun01BDRBDRBData register0x400x20read-write0x00000000DATAData032ACR1ACR1AConfiguration register 10x40x20read-write0x00000040MCKENMaster clock generation enable271OSROversampling ratio for master clock261MCJDIVMaster clock divider206NODIVNo divider191DMAENDMA enable171SAIBENAudio block B enable161OutDriOutput drive131MONOMono mode121SYNCENSynchronization enable102CKSTRClock strobing edge91LSBFIRSTLeast significant bit first81DSData size53PRTCFGProtocol configuration22MODEAudio block mode02ACR2ACR2AConfiguration register 20x80x20read-write0x00000000COMPCompanding mode142CPLComplement bit131MUTECNMute counter76MUTEVALMute value61MUTEMute51TRISTristate management on data line41FFLUSFIFO flush31FTHFIFO threshold03AFRCRAFRCRAFRCR0xC0x20read-write0x00000007FSOFFFrame synchronization offset181FSPOLFrame synchronization polarity171FSDEFFrame synchronization definition161FSALLFrame synchronization active level length87FRLFrame length08ASLOTRASLOTRASlot register0x100x20read-write0x00000000SLOTENSlot enable1616NBSLOTNumber of slots in an audio frame84SLOTSZSlot size62FBOFFFirst bit offset05AIMAIMAInterrupt mask register20x140x20read-write0x00000000LFSDETLate frame synchronization detection interrupt enable61AFSDETIEAnticipated frame synchronization detection interrupt enable51CNRDYIECodec not ready interrupt enable41FREQIEFIFO request interrupt enable31WCKCFGWrong clock configuration interrupt enable21MUTEDETMute detection interrupt enable11OVRUDRIEOverrun/underrun interrupt enable01ASRASRAStatus register0x180x20read-only0x00000008FLVLFIFO level threshold163LFSDETLate frame synchronization detection61AFSDETAnticipated frame synchronization detection51CNRDYCodec not ready41FREQFIFO request31WCKCFGWrong clock configuration flag. This bit is read only21MUTEDETMute detection11OVRUDROverrun / underrun01ACLRFRACLRFRAClear flag register0x1C0x20write-only0x00000000LFSDETClear late frame synchronization detection flag61CAFSDETClear anticipated frame synchronization detection flag51CNRDYClear codec not ready flag41WCKCFGClear wrong clock configuration flag21MUTEDETMute detection flag11OVRUDRClear overrun / underrun01ADRADRAData register0x200x20read-write0x00000000DATAData032PDMCRPDMCRPDM control register0x440x20read-write0x00000000CKEN4Clock enable of bitstream clock number 4111CKEN3Clock enable of bitstream clock number 3101CKEN2Clock enable of bitstream clock number 291CKEN1Clock enable of bitstream clock number 181MICNBRNumber of microphones42PDMENPDM enable01PDMDLYPDMDLYPDM delay register0x480x20read-write0x00000000DLYM4RDelay line for second microphone of pair 4283DLYM4LDelay line for first microphone of pair 4243DLYM3RDelay line for second microphone of pair 3203DLYM3LDelay line for first microphone of pair 3163DLYM2RDelay line for second microphone of pair 2123DLYM2LDelay line for first microphone of pair 283DLYM1RDelay line for second microphone of pair 143DLYM1LDelay line for first microphone of pair 103TIM2General-purpose-timersTIM0x400000000x00x400registersTIM2TIM2 global interrupt28CR1CR1control register 10x00x20read-write0x0000UIFREMAPUIF status bit remapping111CKDClock division82ARPEAuto-reload preload enable71CMSCenter-aligned mode selection52DIRDirection41OPMOne-pulse mode31URSUpdate request source21UDISUpdate disable11CENCounter enable01CR2CR2control register 20x40x20read-write0x0000TI1STI1 selection71MMSMaster mode selection43CCDSCapture/compare DMA selection31SMCRSMCRslave mode control register0x80x20read-write0x0000SMS_3Slave mode selection - bit 3161ETPExternal trigger polarity151ECEExternal clock enable141ETPSExternal trigger prescaler122ETFExternal trigger filter84MSMMaster/Slave mode71TSTrigger selection43OCCSOCREF clear selection31SMSSlave mode selection03DIERDIERDMA/Interrupt enable register0xC0x20read-write0x0000CC4DECapture/Compare 4 DMA request enable121CC3DECapture/Compare 3 DMA request enable111CC2DECapture/Compare 2 DMA request enable101CC1DECapture/Compare 1 DMA request enable91UDEUpdate DMA request enable81TIETrigger interrupt enable61CC4IECapture/Compare 4 interrupt enable41CC3IECapture/Compare 3 interrupt enable31CC2IECapture/Compare 2 interrupt enable21CC1IECapture/Compare 1 interrupt enable11UIEUpdate interrupt enable01SRSRstatus register0x100x20read-write0x0000CC4OFCapture/Compare 4 overcapture flag121CC3OFCapture/Compare 3 overcapture flag111CC2OFCapture/compare 2 overcapture flag101CC1OFCapture/Compare 1 overcapture flag91TIFTrigger interrupt flag61CC4IFCapture/Compare 4 interrupt flag41CC3IFCapture/Compare 3 interrupt flag31CC2IFCapture/Compare 2 interrupt flag21CC1IFCapture/compare 1 interrupt flag11UIFUpdate interrupt flag01EGREGRevent generation register0x140x20write-only0x0000TGTrigger generation61CC4GCapture/compare 4 generation41CC3GCapture/compare 3 generation31CC2GCapture/compare 2 generation21CC1GCapture/compare 1 generation11UGUpdate generation01CCMR1_OutputCCMR1_Outputcapture/compare mode register 1 (output mode)0x180x20read-write0x00000000OC2M_3Output Compare 2 mode - bit 3241OC1M_3Output Compare 1 mode - bit 3161OC2CEOutput compare 2 clear enable151OC2MOutput compare 2 mode123OC2PEOutput compare 2 preload enable111OC2FEOutput compare 2 fast enable101CC2SCapture/Compare 2 selection82OC1CEOutput compare 1 clear enable71OC1MOutput compare 1 mode43OC1PEOutput compare 1 preload enable31OC1FEOutput compare 1 fast enable21CC1SCapture/Compare 1 selection02CCMR1_InputCCMR1_Inputcapture/compare mode register 1 (input mode)CCMR1_Output0x180x20read-write0x00000000IC2FInput capture 2 filter124IC2PSCInput capture 2 prescaler102CC2SCapture/compare 2 selection82IC1FInput capture 1 filter44IC1PSCInput capture 1 prescaler22CC1SCapture/Compare 1 selection02CCMR2_OutputCCMR2_Outputcapture/compare mode register 2 (output mode)0x1C0x20read-write0x00000000OC4M_3Output Compare 4 mode - bit 3241OC3M_3Output Compare 3 mode - bit 3161OC4CEOutput compare 4 clear enable151OC4MOutput compare 4 mode123OC4PEOutput compare 4 preload enable111OC4FEOutput compare 4 fast enable101CC4SCapture/Compare 4 selection82OC3CEOutput compare 3 clear enable71OC3MOutput compare 3 mode43OC3PEOutput compare 3 preload enable31OC3FEOutput compare 3 fast enable21CC3SCapture/Compare 3 selection02CCMR2_InputCCMR2_Inputcapture/compare mode register 2 (input mode)CCMR2_Output0x1C0x20read-write0x00000000IC4FInput capture 4 filter124IC4PSCInput capture 4 prescaler102CC4SCapture/Compare 4 selection82IC3FInput capture 3 filter44IC3PSCInput capture 3 prescaler22CC3SCapture/Compare 3 selection02CCERCCERcapture/compare enable register0x200x20read-write0x0000CC4NPCapture/Compare 4 output Polarity151CC4PCapture/Compare 3 output Polarity131CC4ECapture/Compare 4 output enable121CC3NPCapture/Compare 3 output Polarity111CC3PCapture/Compare 3 output Polarity91CC3ECapture/Compare 3 output enable81CC2NPCapture/Compare 2 output Polarity71CC2PCapture/Compare 2 output Polarity51CC2ECapture/Compare 2 output enable41CC1NPCapture/Compare 1 output Polarity31CC1PCapture/Compare 1 output Polarity11CC1ECapture/Compare 1 output enable01CNTCNTcounter0x240x200x00000000CNT_HHigh counter value (TIM2 only)1615read-writeCNT_LLow counter value016read-writeUIFCPYValue depends on IUFREMAP in TIM2_CR1.311read-onlyPSCPSCprescaler0x280x20read-write0x0000PSCPrescaler value016ARRARRauto-reload register0x2C0x20read-write0x00000000ARR_HHigh Auto-reload value (TIM2 only)1616ARR_LLow Auto-reload value016CCR1CCR1capture/compare register 10x340x20read-write0x00000000CCR1_HHigh Capture/Compare 1 value (TIM2 only)1616CCR1_LLow Capture/Compare 1 value016CCR2CCR2capture/compare register 20x380x20read-write0x00000000CCR2_HHigh Capture/Compare 2 value (TIM2 only)1616CCR2_LLow Capture/Compare 2 value016CCR3CCR3capture/compare register 30x3C0x20read-write0x00000000CCR3_HHigh Capture/Compare value (TIM2 only)1616CCR3_LLow Capture/Compare value016CCR4CCR4capture/compare register 40x400x20read-write0x00000000CCR4_HHigh Capture/Compare value (TIM2 only)1616CCR4_LLow Capture/Compare value016DCRDCRDMA control register0x480x20read-write0x0000DBLDMA burst length85DBADMA base address05DMARDMARDMA address for full transfer0x4C0x20read-write0x0000DMABDMA register for burst accesses016ORORTIM2 option register0x500x20read-write0x0000TI4_RMPInput capture 4 remap22ETR_RMPExternal trigger remap11ITR_RMPInternal trigger remap01AFAFTIM2 alternate function option register 10x600x20read-write0x0000ETRSELExternal trigger source selection143TIM16General purpose timersTIM0x400144000x00x400registersCR1CR1control register 10x00x20read-write0x0000CENCounter enable01UDISUpdate disable11URSUpdate request source21OPMOne-pulse mode31ARPEAuto-reload preload enable71CKDClock division82UIFREMAPUIF status bit remapping111CR2CR2control register 20x40x20read-write0x0000OIS1NOutput Idle state 191OIS1Output Idle state 181CCDSCapture/compare DMA selection31CCUSCapture/compare control update selection21CCPCCapture/compare preloaded control01DIERDIERDMA/Interrupt enable register0xC0x20read-write0x0000UIEUpdate interrupt enable01CC1IECapture/Compare 1 interrupt enable11COMIECOM interrupt enable51BIEBreak interrupt enable71UDEUpdate DMA request enable81CC1DECapture/Compare 1 DMA request enable91SRSRstatus register0x100x20read-write0x0000CC1OFCapture/Compare 1 overcapture flag91BIFBreak interrupt flag71COMIFCOM interrupt flag51CC1IFCapture/compare 1 interrupt flag11UIFUpdate interrupt flag01EGREGRevent generation register0x140x20write-only0x0000BGBreak generation71COMGCapture/Compare control update generation51CC1GCapture/compare 1 generation11UGUpdate generation01CCMR1_OutputCCMR1_Outputcapture/compare mode register (output mode)0x180x20read-write0x00000000OC1M_3Output Compare 1 mode161OC1MOutput Compare 1 mode43OC1PEOutput Compare 1 preload enable31OC1FEOutput Compare 1 fast enable21CC1SCapture/Compare 1 selection02CCMR1_InputCCMR1_Inputcapture/compare mode register 1 (input mode)CCMR1_Output0x180x20read-write0x00000000IC1FInput capture 1 filter44IC1PSCInput capture 1 prescaler22CC1SCapture/Compare 1 selection02CCERCCERcapture/compare enable register0x200x20read-write0x0000CC1NPCapture/Compare 1 output Polarity31CC1NECapture/Compare 1 complementary output enable21CC1PCapture/Compare 1 output Polarity11CC1ECapture/Compare 1 output enable01CNTCNTcounter0x240x200x00000000CNTcounter value016read-writeUIFCPYUIF Copy311read-onlyPSCPSCprescaler0x280x20read-write0x0000PSCPrescaler value016ARRARRauto-reload register0x2C0x20read-write0xFFFFARRAuto-reload value016RCRRCRrepetition counter register0x300x20read-write0x0000REPRepetition counter value08CCR1CCR1capture/compare register 10x340x20read-write0x00000000CCR1Capture/Compare 1 value016BDTRBDTRbreak and dead-time register0x440x20read-write0x0000DTGDead-time generator setup08LOCKLock configuration82OSSIOff-state selection for Idle mode101OSSROff-state selection for Run mode111BKEBreak enable121BKPBreak polarity131AOEAutomatic output enable141MOEMain output enable151BKDSRMBreak Disarm261BKBIDBreak Bidirectional281DCRDCRDMA control register0x480x20read-write0x0000DBLDMA burst length85DBADMA base address05DMARDMARDMA address for full transfer0x4C0x20read-write0x0000DMABDMA register for burst accesses016OR1OR1TIM option register 10x500x20read-write0x0000TI1_RMPInput capture 1 remap02AF1AF1alternate function register 10x600x20read-write0x00000001BKINEBRK BKIN input enable01BKCMP1EBRK COMP1 enable11BKCMP2EBRK COMP2 enable21BKINPBRK BKIN input polarity91BKCMP1PBRK COMP1 input polarity101BKCMP2PBRK COMP2 input polarit111TISELTISELinput selection register0x680x20read-write0x00000000TI1SELselects TI1[0] to TI1[15] input04TIM17General purpose timersTIM0x400148000x00x400registersCR1CR1control register 10x00x20read-write0x0000CENCounter enable01UDISUpdate disable11URSUpdate request source21OPMOne-pulse mode31ARPEAuto-reload preload enable71CKDClock division82UIFREMAPUIF status bit remapping111CR2CR2control register 20x40x20read-write0x0000OIS1NOutput Idle state 191OIS1Output Idle state 181CCDSCapture/compare DMA selection31CCUSCapture/compare control update selection21CCPCCapture/compare preloaded control01DIERDIERDMA/Interrupt enable register0xC0x20read-write0x0000UIEUpdate interrupt enable01CC1IECapture/Compare 1 interrupt enable11COMIECOM interrupt enable51BIEBreak interrupt enable71UDEUpdate DMA request enable81CC1DECapture/Compare 1 DMA request enable91SRSRstatus register0x100x20read-write0x0000CC1OFCapture/Compare 1 overcapture flag91BIFBreak interrupt flag71COMIFCOM interrupt flag51CC1IFCapture/compare 1 interrupt flag11UIFUpdate interrupt flag01EGREGRevent generation register0x140x20write-only0x0000BGBreak generation71COMGCapture/Compare control update generation51CC1GCapture/compare 1 generation11UGUpdate generation01CCMR1_OutputCCMR1_Outputcapture/compare mode register (output mode)0x180x20read-write0x00000000OC1M_3Output Compare 1 mode161OC1MOutput Compare 1 mode43OC1PEOutput Compare 1 preload enable31OC1FEOutput Compare 1 fast enable21CC1SCapture/Compare 1 selection02CCMR1_InputCCMR1_Inputcapture/compare mode register 1 (input mode)CCMR1_Output0x180x20read-write0x00000000IC1FInput capture 1 filter44IC1PSCInput capture 1 prescaler22CC1SCapture/Compare 1 selection02CCERCCERcapture/compare enable register0x200x20read-write0x0000CC1NPCapture/Compare 1 output Polarity31CC1NECapture/Compare 1 complementary output enable21CC1PCapture/Compare 1 output Polarity11CC1ECapture/Compare 1 output enable01CNTCNTcounter0x240x200x00000000CNTcounter value016read-writeUIFCPYUIF Copy311read-onlyPSCPSCprescaler0x280x20read-write0x0000PSCPrescaler value016ARRARRauto-reload register0x2C0x20read-write0xFFFFARRAuto-reload value016RCRRCRrepetition counter register0x300x20read-write0x0000REPRepetition counter value08CCR1CCR1capture/compare register 10x340x20read-write0x00000000CCR1Capture/Compare 1 value016BDTRBDTRbreak and dead-time register0x440x20read-write0x0000DTGDead-time generator setup08LOCKLock configuration82OSSIOff-state selection for Idle mode101OSSROff-state selection for Run mode111BKEBreak enable121BKPBreak polarity131AOEAutomatic output enable141MOEMain output enable151BKDSRMBreak Disarm261BKBIDBreak Bidirectional281DCRDCRDMA control register0x480x20read-write0x0000DBLDMA burst length85DBADMA base address05DMARDMARDMA address for full transfer0x4C0x20read-write0x0000DMABDMA register for burst accesses016OR1OR1TIM option register 10x500x20read-write0x0000TI1_RMPInput capture 1 remap02AF1AF1alternate function register 10x600x20read-write0x00000001BKINEBRK BKIN input enable01BKCMP1EBRK COMP1 enable11BKCMP2EBRK COMP2 enable21BKINPBRK BKIN input polarity91BKCMP1PBRK COMP1 input polarity101BKCMP2PBRK COMP2 input polarit111TISELTISELinput selection register0x680x20read-write0x00000000TI1SELselects TI1[0] to TI1[15] input04TIM1Advanced-timersTIM0x40012C000x00x400registersTIM1_BRKTimer 1 break interrupt24TIM1_UPTimer 1 Update25TIM1_TRG_COM_TIM17TIM1 Trigger and Commutation interrupts and + TIM17 global interrupt26TIM1_CCTIM1 Capture Compare interrupt27CR1CR1control register 10x00x20read-write0x0000CENCounter enable01OPMOne-pulse mode31UDISUpdate disable11URSUpdate request source21DIRDirection41CMSCenter-aligned mode selection52ARPEAuto-reload preload enable71CKDClock division82UIFREMAPUIF status bit remapping111CR2CR2control register 20x40x20read-write0x0000MMS2Master mode selection 2204OIS6Output Idle state 6 (OC6 output)181OIS5Output Idle state 5 (OC5 output)161OIS4Output Idle state 4141OIS3NOutput Idle state 3131OIS3Output Idle state 3121OIS2NOutput Idle state 2111OIS2Output Idle state 2101OIS1NOutput Idle state 191OIS1Output Idle state 181TI1STI1 selection71MMSMaster mode selection43CCDSCapture/compare DMA selection31CCUSCapture/compare control update selection21CCPCCapture/compare preloaded control01SMCRSMCRslave mode control register0x80x20read-write0x0000SMSSlave mode selection03OCCSOCREF clear selection31TSTrigger selection43MSMMaster/Slave mode71ETFExternal trigger filter84ETPSExternal trigger prescaler122ECEExternal clock enable141ETPExternal trigger polarity151SMS_3Slave mode selection - bit 3161DIERDIERDMA/Interrupt enable register0xC0x20read-write0x0000UIEUpdate interrupt enable01CC1IECapture/Compare 1 interrupt enable11CC2IECapture/Compare 2 interrupt enable21CC3IECapture/Compare 3 interrupt enable31CC4IECapture/Compare 4 interrupt enable41COMIECOM interrupt enable51TIETrigger interrupt enable61BIEBreak interrupt enable71UDEUpdate DMA request enable81CC1DECapture/Compare 1 DMA request enable91CC2DECapture/Compare 2 DMA request enable101CC3DECapture/Compare 3 DMA request enable111CC4DECapture/Compare 4 DMA request enable121COMDECOM DMA request enable131TDETrigger DMA request enable141SRSRstatus register0x100x20read-write0x0000UIFUpdate interrupt flag01CC1IFCapture/compare 1 interrupt flag11CC2IFCapture/Compare 2 interrupt flag21CC3IFCapture/Compare 3 interrupt flag31CC4IFCapture/Compare 4 interrupt flag41COMIFCOM interrupt flag51TIFTrigger interrupt flag61BIFBreak interrupt flag71B2IFBreak 2 interrupt flag81CC1OFCapture/Compare 1 overcapture flag91CC2OFCapture/compare 2 overcapture flag101CC3OFCapture/Compare 3 overcapture flag111CC4OFCapture/Compare 4 overcapture flag121SBIFSystem Break interrupt flag131CC5IFCompare 5 interrupt flag161CC6IFCompare 6 interrupt flag171EGREGRevent generation register0x140x20write-only0x0000UGUpdate generation01CC1GCapture/compare 1 generation11CC2GCapture/compare 2 generation21CC3GCapture/compare 3 generation31CC4GCapture/compare 4 generation41COMGCapture/Compare control update generation51TGTrigger generation61BGBreak generation71B2GBreak 2 generation81CCMR1_InputCCMR1_Inputcapture/compare mode register 1 (output mode)0x180x20read-write0x00000000CC1SCapture/Compare 1 selection02IC1PSCInput capture 1 prescaler22C1FInput capture 1 filter44CC2Scapture/Compare 2 selection82IC2PSCInput capture 2 prescaler102IC2FInput capture 2 filter124CCMR1_OutputCCMR1_Outputcapture/compare mode register 1 (output mode)CCMR1_Input0x180x20read-write0x00000000CC1SCapture/Compare 1 selection02OC1FEOutput Compare 1 fast enable21OC1PEOutput Compare 1 preload enable31OC1MOutput Compare 1 mode43OC1CEOutput Compare 1 clear enable71CC2SCapture/Compare 2 selection82OC2FEOutput Compare 2 fast enable101OC2PEOutput Compare 2 preload enable111OC2MOutput Compare 2 mode123OC2CEOutput Compare 2 clear enable151OC1M_3Output Compare 1 mode - bit 3161OC2M_3Output Compare 2 mode - bit 3241CCMR2_OutputCCMR2_Outputcapture/compare mode register 2 (output mode)0x1C0x20read-write0x00000000CC3SCapture/Compare 3 selection02OC3FEOutput compare 3 fast enable21OC3PEOutput compare 3 preload enable31OC3MOutput compare 3 mode43OC3CEOutput compare 3 clear enable71CC4SCapture/Compare 4 selection82OC4FEOutput compare 4 fast enable101OC4PEOutput compare 4 preload enable111OC4MOutput compare 4 mode123OC4CEOutput compare 4 clear enable151OC3M_3Output Compare 3 mode - bit 3161OC4M_3Output Compare 4 mode - bit 3241CCMR2_InputCCMR2_Inputcapture/compare mode register 2 (output mode)CCMR2_Output0x1C0x20read-write0x00000000CC3SCapture/Compare 3 selection02C3PSCInput capture 3 prescaler22IC3FInput capture 3 filter44CC4SCapture/Compare 4 selection82IC4PSCInput capture 4 prescaler102IC4FInput capture 4 filter124CCERCCERcapture/compare enable register0x200x20read-write0x0000CC1ECapture/Compare 1 output enable01CC1PCapture/Compare 1 output Polarity11CC1NECapture/Compare 1 complementary output enable21CC1NPCapture/Compare 1 output Polarity31CC2ECapture/Compare 2 output enable41CC2PCapture/Compare 2 output Polarity51CC2NECapture/Compare 2 complementary output enable61CC2NPCapture/Compare 2 output Polarity71CC3ECapture/Compare 3 output enable81CC3PCapture/Compare 3 output Polarity91CC3NECapture/Compare 3 complementary output enable101CC3NPCapture/Compare 3 output Polarity111CC4ECapture/Compare 4 output enable121CC4PCapture/Compare 3 output Polarity131CC4NPCapture/Compare 4 complementary output polarity151CC5ECapture/Compare 5 output enable161CC5PCapture/Compare 5 output polarity171CC6ECapture/Compare 6 output enable201CC6PCapture/Compare 6 output polarity211CNTCNTcounter0x240x200x00000000CNTcounter value016read-writeUIFCPYUIF copy311read-onlyPSCPSCprescaler0x280x20read-write0x0000PSCPrescaler value016ARRARRauto-reload register0x2C0x20read-write0x0000FFFFARRAuto-reload value016RCRRCRrepetition counter register0x300x20read-write0x0000REPRepetition counter value016CCR1CCR1capture/compare register 10x340x20read-write0x00000000CCR1Capture/Compare 1 value016CCR2CCR2capture/compare register 20x380x20read-write0x00000000CCR2Capture/Compare 2 value016CCR3CCR3capture/compare register 30x3C0x20read-write0x00000000CCR3Capture/Compare value016CCR4CCR4capture/compare register 40x400x20read-write0x00000000CCR4Capture/Compare value016BDTRBDTRbreak and dead-time register0x440x20read-write0x0000DTGDead-time generator setup08LOCKLock configuration82OSSIOff-state selection for Idle mode101OSSROff-state selection for Run mode111BKEBreak enable121BKPBreak polarity131AOEAutomatic output enable141MOEMain output enable151BKFBreak filter164BK2FBreak 2 filter204BK2EBreak 2 enable241BK2PBreak 2 polarity251DCRDCRDMA control register0x480x20read-write0x0000DBLDMA burst length85DBADMA base address05DMARDMARDMA address for full transfer0x4C0x20read-write0x0000DMABDMA register for burst accesses016ORORDMA address for full transfer0x500x20read-write0x0000TIM1_ETR_ADC1_RMPTIM1_ETR_ADC1 remapping capability02TI1_RMPInput Capture 1 remap41CCMR3_OutputCCMR3_Outputcapture/compare mode register 2 (output mode)0x540x20read-write0x00000000OC6M_bit3Output Compare 6 mode bit 3241OC5M_bit3Output Compare 5 mode bit 3161OC6CEOutput compare 6 clear enable151OC6MOutput compare 6 mode123OC6PEOutput compare 6 preload enable111OC6FEOutput compare 6 fast enable101OC5CEOutput compare 5 clear enable71OC5MOutput compare 5 mode43OC5PEOutput compare 5 preload enable31OC5FEOutput compare 5 fast enable21CCR5CCR5capture/compare register 40x580x20read-write0x00000000CCR5Capture/Compare value016GC5C1Group Channel 5 and Channel 1291GC5C2Group Channel 5 and Channel 2301GC5C3Group Channel 5 and Channel 3311CCR6CCR6capture/compare register 40x5C0x20read-write0x00000000CCR6Capture/Compare value016AF1AF1DMA address for full transfer0x600x20read-write0x00000001BKINEBRK BKIN input enable01BKCMP1EBRK COMP1 enable11BKCMP2EBRK COMP2 enable21BKINPBRK BKIN input polarity91BKCMP1PBRK COMP1 input polarity101BKCMP2PBRK COMP2 input polarity111ETRSELETR source selection143AF2AF2DMA address for full transfer0x640x20read-write0x00000001BK2INEBRK2 BKIN input enable01BK2CMP1EBRK2 COMP1 enable11BK2CMP2EBRK2 COMP2 enable21BK2DFBK0EBRK2 DFSDM_BREAK0 enable81BK2INPBRK2 BKIN input polarity91BK2CMP1PBRK2 COMP1 input polarity101BK2CMP2PBRK2 COMP2 input polarity111LPTIM1Low power timerLPTIM0x40007C000x00x400registersLPTIM1LPtimer 1 global interrupt47ISRISRInterrupt and Status Register0x00x20read-only0x00000000DOWNCounter direction change up to down61UPCounter direction change down to up51ARROKAutoreload register update OK41CMPOKCompare register update OK31EXTTRIGExternal trigger edge event21ARRMAutoreload match11CMPMCompare match01ICRICRInterrupt Clear Register0x40x20write-only0x00000000DOWNCFDirection change to down Clear Flag61UPCFDirection change to UP Clear Flag51ARROKCFAutoreload register update OK Clear Flag41CMPOKCFCompare register update OK Clear Flag31EXTTRIGCFExternal trigger valid edge Clear Flag21ARRMCFAutoreload match Clear Flag11CMPMCFcompare match Clear Flag01IERIERInterrupt Enable Register0x80x20read-write0x00000000DOWNIEDirection change to down Interrupt Enable61UPIEDirection change to UP Interrupt Enable51ARROKIEAutoreload register update OK Interrupt Enable41CMPOKIECompare register update OK Interrupt Enable31EXTTRIGIEExternal trigger valid edge Interrupt Enable21ARRMIEAutoreload match Interrupt Enable11CMPMIECompare match Interrupt Enable01CFGRCFGRConfiguration Register0xC0x20read-write0x00000000ENCEncoder mode enable241COUNTMODEcounter mode enabled231PRELOADRegisters update mode221WAVPOLWaveform shape polarity211WAVEWaveform shape201TIMOUTTimeout enable191TRIGENTrigger enable and polarity172TRIGSELTrigger selector133PRESCClock prescaler93TRGFLTConfigurable digital filter for trigger62CKFLTConfigurable digital filter for external clock32CKPOLClock Polarity12CKSELClock selector01CRCRControl Register0x100x20read-write0x00000000RSTAREReset after read enable41COUNTRSTCounter reset31CNTSTRTTimer start in continuous mode21SNGSTRTLPTIM start in single mode11ENABLELPTIM Enable01CMPCMPCompare Register0x140x20read-write0x00000000CMPCompare value016ARRARRAutoreload Register0x180x20read-write0x00000001ARRAuto reload value016CNTCNTCounter Register0x1C0x20read-only0x00000000CNTCounter value016OROROption Register0x200x20read-write0x00000000OR1Option register bit 101OR2Option register bit 211LPTIM20x40009400LPTIM2LPtimer 2 global interrupt48USART1Universal synchronous asynchronous receiver transmitterUSART0x400138000x00x400registersUSART1USART1 global interrupt36CR1CR1Control register 10x00x20read-write0x0000RXFFIERXFIFO Full interrupt enable311TXFEIETXFIFO empty interrupt enable301FIFOENFIFO mode enable291M1Word length281EOBIEEnd of Block interrupt enable271RTOIEReceiver timeout interrupt enable261DEAT4Driver Enable assertion time251DEAT3DEAT3241DEAT2DEAT2231DEAT1DEAT1221DEAT0DEAT0211DEDT4Driver Enable de-assertion time201DEDT3DEDT3191DEDT2DEDT2181DEDT1DEDT1171DEDT0DEDT0161OVER8Oversampling mode151CMIECharacter match interrupt enable141MMEMute mode enable131M0Word length121WAKEReceiver wakeup method111PCEParity control enable101PSParity selection91PEIEPE interrupt enable81TXEIEinterrupt enable71TCIETransmission complete interrupt enable61RXNEIERXNE interrupt enable51IDLEIEIDLE interrupt enable41TETransmitter enable31REReceiver enable21UESMUSART enable in Stop mode11UEUSART enable01CR2CR2Control register 20x40x20read-write0x0000ADD4_7Address of the USART node284ADD0_3Address of the USART node244RTOENReceiver timeout enable231ABRMOD1Auto baud rate mode221ABRMOD0ABRMOD0211ABRENAuto baud rate enable201MSBFIRSTMost significant bit first191TAINVBinary data inversion181TXINVTX pin active level inversion171RXINVRX pin active level inversion161SWAPSwap TX/RX pins151LINENLIN mode enable141STOPSTOP bits122CLKENClock enable111CPOLClock polarity101CPHAClock phase91LBCLLast bit clock pulse81LBDIELIN break detection interrupt enable61LBDLLIN break detection length51ADDM77-bit Address Detection/4-bit Address Detection41DIS_NSSWhen the DSI_NSS bit is set, the NSS pin input will be ignored31SLVENSynchronous Slave mode enable01CR3CR3Control register 30x80x20read-write0x0000TXFTCFGTXFIFO threshold configuration293RXFTIERXFIFO threshold interrupt enable281RXFTCFGReceive FIFO threshold configuration253TCBGTIETr Complete before guard time, interrupt enable241TXFTIEthreshold interrupt enable231WUFIEWakeup from Stop mode interrupt enable221WUSWakeup from Stop mode interrupt flag selection202SCARCNTSmartcard auto-retry count173DEPDriver enable polarity selection151DEMDriver enable mode141DDREDMA Disable on Reception Error131OVRDISOverrun Disable121ONEBITOne sample bit method enable111CTSIECTS interrupt enable101CTSECTS enable91RTSERTS enable81DMATDMA enable transmitter71DMARDMA enable receiver61SCENSmartcard mode enable51NACKSmartcard NACK enable41HDSELHalf-duplex selection31IRLPIr low-power21IRENIr mode enable11EIEError interrupt enable01BRRBRRBaud rate register0xC0x20read-write0x0000BRRBRR_4_15016GTPRGTPRGuard time and prescaler register0x100x20read-write0x0000GTGuard time value88PSCPrescaler value08RTORRTORReceiver timeout register0x140x20read-write0x0000BLENBlock Length248RTOReceiver timeout value024RQRRQRRequest register0x180x20write-only0x0000TXFRQTransmit data flush request41RXFRQReceive data flush request31MMRQMute mode request21SBKRQSend break request11ABRRQAuto baud rate request01ISRISRInterrupt & status register0x1C0x20read-only0x00C0TXFTTXFIFO threshold flag271RXFTRXFIFO threshold flag261TCBGTTransmission complete before guard time flag251RXFFRXFIFO Full241TXFETXFIFO Empty231REACKREACK221TEACKTEACK211WUFWUF201RWURWU191SBKFSBKF181CMFCMF171BUSYBUSY161ABRFABRF151ABREABRE141UDRSPI slave underrun error flag131EOBFEOBF121RTOFRTOF111CTSCTS101CTSIFCTSIF91LBDFLBDF81TXETXE71TCTC61RXNERXNE51IDLEIDLE41OREORE31NFNF21FEFE11PEPE01ICRICRInterrupt flag clear register0x200x20write-only0x0000WUCFWakeup from Stop mode clear flag201CMCFCharacter match clear flag171UDRCFSPI slave underrun clear flag131EOBCFEnd of block clear flag121RTOCFReceiver timeout clear flag111CTSCFCTS clear flag91LBDCFLIN break detection clear flag81TCBGTCFTransmission complete before Guard time clear flag71TCCFTransmission complete clear flag61TXFECFTXFIFO empty clear flag51IDLECFIdle line detected clear flag41ORECFOverrun error clear flag31NCFNoise detected clear flag21FECFFraming error clear flag11PECFParity error clear flag01RDRRDRReceive data register0x240x20read-only0x0000RDRReceive data value09TDRTDRTransmit data register0x280x20read-write0x0000TDRTransmit data value09PRESCPRESCPrescaler register0x2C0x20read-write0x0000PRESCALERClock prescaler04LPUART10x40008000LPUART1LPUART1 global interrupt37SPI1Serial peripheral interface/Inter-IC soundSPI0x400130000x00x400registersSPI1SPI 1 global interrupt34CR1CR1control register 10x00x20read-write0x00000000BIDIMODEBidirectional data mode enable151BIDIOEOutput enable in bidirectional mode141CRCENHardware CRC calculation enable131CRCNEXTCRC transfer next121CRCLCRC length111RXONLYReceive only101SSMSoftware slave management91SSIInternal slave select81LSBFIRSTFrame format71SPESPI enable61BRBaud rate control33MSTRMaster selection21CPOLClock polarity11CPHAClock phase01CR2CR2control register 20x40x20read-write0x00000700RXDMAENRx buffer DMA enable01TXDMAENTx buffer DMA enable11SSOESS output enable21NSSPNSS pulse management31FRFFrame format41ERRIEError interrupt enable51RXNEIERX buffer not empty interrupt enable61TXEIETx buffer empty interrupt enable71DSData size84FRXTHFIFO reception threshold121LDMA_RXLast DMA transfer for reception131LDMA_TXLast DMA transfer for transmission141SRSRstatus register0x80x200x00000002RXNEReceive buffer not empty01read-onlyTXETransmit buffer empty11read-onlyCRCERRCRC error flag41read-writeMODFMode fault51read-onlyOVROverrun flag61read-onlyBSYBusy flag71read-onlyTIFRFETI frame format error81read-onlyFRLVLFIFO reception level92read-onlyFTLVLFIFO transmission level112read-onlyDRDRdata register0xC0x20read-write0x00000000DRData register016CRCPRCRCPRCRC polynomial register0x100x20read-write0x00000007CRCPOLYCRC polynomial register016RXCRCRRXCRCRRX CRC register0x140x20read-only0x00000000RxCRCRx CRC register016TXCRCRTXCRCRTX CRC register0x180x20read-only0x00000000TxCRCTx CRC register016SPI20x40003800SPI2SPI1 global interrupt35RTCReal-time clockRTC0x400028000x00x400registersRTC_TAMPRTC/TAMP/CSS on LSE through EXTI line 19 interrupt2RTC_WKUPRTC wakeup interrupt through EXTI[19]3RTC_ALARMRTC Alarms (A and B) interrupt through + AIEC41TRTRtime register0x00x20read-write0x00000000PMAM/PM notation221HTHour tens in BCD format202HUHour units in BCD format164MNTMinute tens in BCD format123MNUMinute units in BCD format84STSecond tens in BCD format43SUSecond units in BCD format04DRDRdate register0x40x20read-write0x00002101YTYear tens in BCD format204YUYear units in BCD format164WDUWeek day units133MTMonth tens in BCD format121MUMonth units in BCD format84DTDate tens in BCD format42DUDate units in BCD format04CRCRcontrol register0x80x20read-write0x00000000WCKSELWakeup clock selection03TSEDGETime-stamp event active edge31REFCKONReference clock detection enable (50 or 60 Hz)41BYPSHADBypass the shadow registers51FMTHour format61ALRAEAlarm A enable81ALRBEAlarm B enable91WUTEWakeup timer enable101TSETime stamp enable111ALRAIEAlarm A interrupt enable121ALRBIEAlarm B interrupt enable131WUTIEWakeup timer interrupt enable141TSIETime-stamp interrupt enable151ADD1HAdd 1 hour (summer time change)161SUB1HSubtract 1 hour (winter time change)171BKPBackup181COSELCalibration output selection191POLOutput polarity201OSELOutput selection212COECalibration output enable231ITSEtimestamp on internal event enable241ISRISRinitialization and status register0xC0x200x00000007ALRAWFAlarm A write flag01read-onlyALRBWFAlarm B write flag11read-onlyWUTWFWakeup timer write flag21read-onlySHPFShift operation pending31read-writeINITSInitialization status flag41read-onlyRSFRegisters synchronization flag51read-writeINITFInitialization flag61read-onlyINITInitialization mode71read-writeALRAFAlarm A flag81read-writeALRBFAlarm B flag91read-writeWUTFWakeup timer flag101read-writeTSFTime-stamp flag111read-writeTSOVFTime-stamp overflow flag121read-writeTAMP1FTamper detection flag131read-writeTAMP2FRTC_TAMP2 detection flag141read-writeTAMP3FRTC_TAMP3 detection flag151read-writeRECALPFRecalibration pending Flag161read-onlyITSFINTERNAL TIME-STAMP FLAG171read-writePRERPRERprescaler register0x100x20read-write0x007F00FFPREDIV_AAsynchronous prescaler factor167PREDIV_SSynchronous prescaler factor015WUTRWUTRwakeup timer register0x140x20read-write0x0000FFFFWUTWakeup auto-reload value bits016ALRMARALRMARalarm A register0x1C0x20read-write0x00000000MSK4Alarm A date mask311WDSELWeek day selection301DTDate tens in BCD format282DUDate units or day in BCD format244MSK3Alarm A hours mask231PMAM/PM notation221HTHour tens in BCD format202HUHour units in BCD format164MSK2Alarm A minutes mask151MNTMinute tens in BCD format123MNUMinute units in BCD format84MSK1Alarm A seconds mask71STSecond tens in BCD format43SUSecond units in BCD format04ALRMBRALRMBRalarm B register0x200x20read-write0x00000000MSK4Alarm B date mask311WDSELWeek day selection301DTDate tens in BCD format282DUDate units or day in BCD format244MSK3Alarm B hours mask231PMAM/PM notation221HTHour tens in BCD format202HUHour units in BCD format164MSK2Alarm B minutes mask151MNTMinute tens in BCD format123MNUMinute units in BCD format84MSK1Alarm B seconds mask71STSecond tens in BCD format43SUSecond units in BCD format04WPRWPRwrite protection register0x240x20write-only0x00000000KEYWrite protection key08SSRSSRsub second register0x280x20read-only0x00000000SSSub second value016SHIFTRSHIFTRshift control register0x2C0x20write-only0x00000000ADD1SAdd one second311SUBFSSubtract a fraction of a second015TSTRTSTRtime stamp time register0x300x20read-only0x00000000SUSecond units in BCD format04STSecond tens in BCD format43MNUMinute units in BCD format84MNTMinute tens in BCD format123HUHour units in BCD format164HTHour tens in BCD format202PMAM/PM notation221TSDRTSDRtime stamp date register0x340x20read-only0x00000000WDUWeek day units133MTMonth tens in BCD format121MUMonth units in BCD format84DTDate tens in BCD format42DUDate units in BCD format04TSSSRTSSSRtimestamp sub second register0x380x20read-only0x00000000SSSub second value016CALRCALRcalibration register0x3C0x20read-write0x00000000CALPIncrease frequency of RTC by 488.5 ppm151CALW8Use an 8-second calibration cycle period141CALW16Use a 16-second calibration cycle period131CALMCalibration minus09TAMPCRTAMPCRtamper configuration register0x400x20read-write0x00000000TAMP1ETamper 1 detection enable01TAMP1TRGActive level for tamper 111TAMPIETamper interrupt enable21TAMP2ETamper 2 detection enable31TAMP2TRGActive level for tamper 241TAMP3ETamper 3 detection enable51TAMP3TRGActive level for tamper 361TAMPTSActivate timestamp on tamper detection event71TAMPFREQTamper sampling frequency83TAMPFLTTamper filter count112TAMPPRCHTamper precharge duration132TAMPPUDISTAMPER pull-up disable151TAMP1IETamper 1 interrupt enable161TAMP1NOERASETamper 1 no erase171TAMP1MFTamper 1 mask flag181TAMP2IETamper 2 interrupt enable191TAMP2NOERASETamper 2 no erase201TAMP2MFTamper 2 mask flag211TAMP3IETamper 3 interrupt enable221TAMP3NOERASETamper 3 no erase231TAMP3MFTamper 3 mask flag241ALRMASSRALRMASSRalarm A sub second register0x440x20read-write0x00000000MASKSSMask the most-significant bits starting at this bit244SSSub seconds value015ALRMBSSRALRMBSSRalarm B sub second register0x480x20read-write0x00000000MASKSSMask the most-significant bits starting at this bit244SSSub seconds value015ORORoption register0x4C0x20read-write0x00000000RTC_ALARM_TYPERTC_ALARM on PC13 output type01RTC_OUT_RMPRTC_OUT remap11BKP0RBKP0Rbackup register0x500x20read-write0x00000000BKPBKP032BKP1RBKP1Rbackup register0x540x20read-write0x00000000BKPBKP032BKP2RBKP2Rbackup register0x580x20read-write0x00000000BKPBKP032BKP3RBKP3Rbackup register0x5C0x20read-write0x00000000BKPBKP032BKP4RBKP4Rbackup register0x600x20read-write0x00000000BKPBKP032BKP5RBKP5Rbackup register0x640x20read-write0x00000000BKPBKP032BKP6RBKP6Rbackup register0x680x20read-write0x00000000BKPBKP032BKP7RBKP7Rbackup register0x6C0x20read-write0x00000000BKPBKP032BKP8RBKP8Rbackup register0x700x20read-write0x00000000BKPBKP032BKP9RBKP9Rbackup register0x740x20read-write0x00000000BKPBKP032BKP10RBKP10Rbackup register0x780x20read-write0x00000000BKPBKP032BKP11RBKP11Rbackup register0x7C0x20read-write0x00000000BKPBKP032BKP12RBKP12Rbackup register0x800x20read-write0x00000000BKPBKP032BKP13RBKP13Rbackup register0x840x20read-write0x00000000BKPBKP032BKP14RBKP14Rbackup register0x880x20read-write0x00000000BKPBKP032BKP15RBKP15Rbackup register0x8C0x20read-write0x00000000BKPBKP032BKP16RBKP16Rbackup register0x900x20read-write0x00000000BKPBKP032BKP17RBKP17Rbackup register0x940x20read-write0x00000000BKPBKP032BKP18RBKP18Rbackup register0x980x20read-write0x00000000BKPBKP032BKP19RBKP19Rbackup register0x9C0x20read-write0x00000000BKPBKP032DBGMCUDebug supportDBGMCU0xE00420000x00x400registersIDCODEIDCODEMCU Device ID Code Register0x00x20read-only0x0DEV_IDDevice Identifier012REV_IDRevision Identifier1616CRCRDebug MCU Configuration Register0x40x20read-write0x0DBG_SLEEPDebug Sleep Mode01DBG_STOPDebug Stop Mode11DBG_STANDBYDebug Standby Mode21TRACE_IOENTrace port and clock enable51TRGOENExternal trigger output enable281APB1FZR1APB1FZR1APB1 Low Freeze Register CPU10x3C0x20read-write0x0DBG_TIMER2_STOPDebug Timer 2 stopped when Core is halted01DBG_RTC_STOPRTC counter stopped when core is halted101DBG_WWDG_STOPWWDG counter stopped when core is halted111DBG_IWDG_STOPIWDG counter stopped when core is halted121DBG_I2C1_STOPDebug I2C1 SMBUS timeout stopped when Core is halted211DBG_I2C3_STOPDebug I2C3 SMBUS timeout stopped when core is halted231DBG_LPTIM1_STOPDebug LPTIM1 stopped when Core is halted311C2AP_B1FZR1C2AP_B1FZR1APB1 Low Freeze Register CPU20x400x20read-write0x0DBG_LPTIM2_STOPLPTIM2 counter stopped when core is halted01DBG_RTC_STOPRTC counter stopped when core is halted101DBG_IWDG_STOPIWDG stopped when core is halted121DBG_I2C1_STOPI2C1 SMBUS timeout stopped when core is halted211DBG_I2C3_STOPI2C3 SMBUS timeout stopped when core is halted231DBG_LPTIM1_STOPLPTIM1 counter stopped when core is halted311APB1FZR2APB1FZR2APB1 High Freeze Register CPU10x440x20read-write0x0DBG_LPTIM2_STOPLPTIM2 counter stopped when core is halted51C2APB1FZR2C2APB1FZR2APB1 High Freeze Register CPU20x480x20read-write0x0DBG_LPTIM2_STOPLPTIM2 counter stopped when core is halted51APB2FZRAPB2FZRAPB2 Freeze Register CPU10x4C0x20read-write0x0DBG_TIM1_STOPTIM1 counter stopped when core is halted111DBG_TIM16_STOPTIM16 counter stopped when core is halted171DBG_TIM17_STOPTIM17 counter stopped when core is halted181C2APB2FZRC2APB2FZRAPB2 Freeze Register CPU2C2APB1FZR20x480x20read-write0x0DBG_TIM1_STOPTIM1 counter stopped when core is halted111DBG_TIM16_STOPTIM16 counter stopped when core is halted171DBG_TIM17_STOPTIM17 counter stopped when core is halted181PKAPKAPKA0x580020000x00x2000registersPKAPrivate key accelerator + interrupt29CRCRControl register0x00x20read-write0x00000000ADDRERRIEAddress error interrupt enable201RAMERRIERAM error interrupt enable191PROCENDIEEnd of operation interrupt enable171MODEPKA Operation Mode86SECLVLSecurity Enable21STARTStart the operation11ENPeripheral Enable01SRSRPKA status register0x40x20read-only0x00000000ADDRERRFAddress error flag201RAMERRFRAM error flag191PROCENDFPKA End of Operation flag171BUSYPKA Operation in progress161CLRFRCLRFRPKA clear flag register0x80x20read-write0x00000000ADDRERRFCClear Address error flag201RAMERRFCClear RAM error flag191PROCENDFCClear PKA End of Operation flag171VERRVERRPKA version register0x1FF40x20read-only0x00000010MINREVMinor revision04MAJREVMajor revision44IPIDRIPIDRPKA identification register0x1FF80x20read-only0x00170061IDIdentification Code032SIDRSIDRPKA size ID register0x1FFC0x20read-only0xA3C5DD08SIDSide Identification Code032IPCCIPCCIPCC0x58000C000x00x400registersIPCC_C1_RX_ITIPCC CPU1 RX occupied interrupt44IPCC_C1_TX_ITIPCC CPU1 TX free interrupt45C1CRC1CRControl register CPU10x00x20read-write0x00000000TXFIEprocessor 1 Transmit channel free interrupt enable161RXOIEprocessor 1 Receive channel occupied interrupt enable01C1MRC1MRMask register CPU10x40x20read-write0xFFFFFFFFCH6FMprocessor 1 Transmit channel 6 free interrupt mask211CH5FMprocessor 1 Transmit channel 5 free interrupt mask201CH4FMprocessor 1 Transmit channel 4 free interrupt mask191CH3FMprocessor 1 Transmit channel 3 free interrupt mask181CH2FMprocessor 1 Transmit channel 2 free interrupt mask171CH1FMprocessor 1 Transmit channel 1 free interrupt mask161CH6OMprocessor 1 Receive channel 6 occupied interrupt enable51CH5OMprocessor 1 Receive channel 5 occupied interrupt enable41CH4OMprocessor 1 Receive channel 4 occupied interrupt enable31CH3OMprocessor 1 Receive channel 3 occupied interrupt enable21CH2OMprocessor 1 Receive channel 2 occupied interrupt enable11CH1OMprocessor 1 Receive channel 1 occupied interrupt enable01C1SCRC1SCRStatus Set or Clear register CPU10x80x20write-only0x00000000CH6Sprocessor 1 Transmit channel 6 status set211CH5Sprocessor 1 Transmit channel 5 status set201CH4Sprocessor 1 Transmit channel 4 status set191CH3Sprocessor 1 Transmit channel 3 status set181CH2Sprocessor 1 Transmit channel 2 status set171CH1Sprocessor 1 Transmit channel 1 status set161CH6Cprocessor 1 Receive channel 6 status clear51CH5Cprocessor 1 Receive channel 5 status clear41CH4Cprocessor 1 Receive channel 4 status clear31CH3Cprocessor 1 Receive channel 3 status clear21CH2Cprocessor 1 Receive channel 2 status clear11CH1Cprocessor 1 Receive channel 1 status clear01C1TO2SRC1TO2SRCPU1 to CPU2 status register0xC0x20read-only0x00000000CH6Fprocessor 1 transmit to process 2 Receive channel 6 status flag51CH5Fprocessor 1 transmit to process 2 Receive channel 5 status flag41CH4Fprocessor 1 transmit to process 2 Receive channel 4 status flag31CH3Fprocessor 1 transmit to process 2 Receive channel 3 status flag21CH2Fprocessor 1 transmit to process 2 Receive channel 2 status flag11CH1Fprocessor 1 transmit to process 2 Receive channel 1 status flag01C2CRC2CRControl register CPU20x100x20read-write0x00000000TXFIEprocessor 2 Transmit channel free interrupt enable161RXOIEprocessor 2 Receive channel occupied interrupt enable01C2MRC2MRMask register CPU20x140x20read-write0xFFFFFFFFCH6FMprocessor 2 Transmit channel 6 free interrupt mask211CH5FMprocessor 2 Transmit channel 5 free interrupt mask201CH4FMprocessor 2 Transmit channel 4 free interrupt mask191CH3FMprocessor 2 Transmit channel 3 free interrupt mask181CH2FMprocessor 2 Transmit channel 2 free interrupt mask171CH1FMprocessor 2 Transmit channel 1 free interrupt mask161CH6OMprocessor 2 Receive channel 6 occupied interrupt enable51CH5OMprocessor 2 Receive channel 5 occupied interrupt enable41CH4OMprocessor 2 Receive channel 4 occupied interrupt enable31CH3OMprocessor 2 Receive channel 3 occupied interrupt enable21CH2OMprocessor 2 Receive channel 2 occupied interrupt enable11CH1OMprocessor 2 Receive channel 1 occupied interrupt enable01C2SCRC2SCRStatus Set or Clear register CPU20x180x20write-only0x00000000CH6Sprocessor 2 Transmit channel 6 status set211CH5Sprocessor 2 Transmit channel 5 status set201CH4Sprocessor 2 Transmit channel 4 status set191CH3Sprocessor 2 Transmit channel 3 status set181CH2Sprocessor 2 Transmit channel 2 status set171CH1Sprocessor 2 Transmit channel 1 status set161CH6Cprocessor 2 Receive channel 6 status clear51CH5Cprocessor 2 Receive channel 5 status clear41CH4Cprocessor 2 Receive channel 4 status clear31CH3Cprocessor 2 Receive channel 3 status clear21CH2Cprocessor 2 Receive channel 2 status clear11CH1Cprocessor 2 Receive channel 1 status clear01C2TOC1SRC2TOC1SRCPU2 to CPU1 status register0x1C0x20read-only0x00000000CH6Fprocessor 2 transmit to process 1 Receive channel 6 status flag51CH5Fprocessor 2 transmit to process 1 Receive channel 5 status flag41CH4Fprocessor 2 transmit to process 1 Receive channel 4 status flag31CH3Fprocessor 2 transmit to process 1 Receive channel 3 status flag21CH2Fprocessor 2 transmit to process 1 Receive channel 2 status flag11CH1Fprocessor 2 transmit to process 1 Receive channel 1 status flag01HWCFGRHWCFGRIPCC Hardware configuration register0x3F00x20read-only0x00000006CHANNELSNumber of channels per CPU supported by the IP, range 1 to 1608VERRVERRIPCC version register0x3F40x20read-only0x00000010MAJREVMajor Revision44MINREVMinor Revision04IPIDRIPIDRIPCC indentification register0x3F80x20read-only0x00100071IPIDIdentification Code032SIDRSIDRIPCC size indentification register0x3FC0x20read-only0xA3C5DD01SIDSize Identification Code032EXTIExternal interrupt/event controllerEXTI0x580008000x00x400registersPVDPVD through EXTI[16] (C1IMR2[20])1EXTI0EXTI line 0 interrupt through + EXTI[0]6EXTI1EXTI line 0 interrupt through + EXTI[1]7EXTI2EXTI line 0 interrupt through + EXTI[2]8EXTI3EXTI line 0 interrupt through + EXTI[3]9EXTI4EXTI line 0 interrupt through + EXTI[4]10C2SEVCPU2 SEV through EXTI[40]21EXTI5_9EXTI line [9:5] interrupt through + EXTI[9:5]23EXTI10_15EXTI line [15:10] interrupt through + EXTI[15:10]40RTSR1RTSR1rising trigger selection register0x00x20read-write0x00000000RTRising trigger event configuration bit of Configurable Event input022RT_31Rising trigger event configuration bit of Configurable Event input311FTSR1FTSR1falling trigger selection register0x40x20read-write0x00000000FTFalling trigger event configuration bit of Configurable Event input022FT_31Falling trigger event configuration bit of Configurable Event input311SWIER1SWIER1software interrupt event register0x80x20read-write0x00000000SWISoftware interrupt on event022SWI_31Software interrupt on event311PR1PR1EXTI pending register0xC0x20read-write0x00000000PIFConfigurable event inputs Pending bit022PIF_31Configurable event inputs Pending bit311RTSR2RTSR2rising trigger selection register0x200x20read-write0x00000000RT33Rising trigger event configuration bit of Configurable Event input11RT40_41Rising trigger event configuration bit of Configurable Event input82FTSR2FTSR2falling trigger selection register0x240x20read-write0x00000000FT33Falling trigger event configuration bit of Configurable Event input11FT40_41Falling trigger event configuration bit of Configurable Event input82SWIER2SWIER2software interrupt event register0x280x20read-write0x00000000SWI33Software interrupt on event11SWI40_41Software interrupt on event82PR2PR2pending register0x2C0x20read-write0x00000000PIF33Configurable event inputs x+32 Pending bit.11PIF40_41Configurable event inputs x+32 Pending bit.82C1IMR1C1IMR1CPUm wakeup with interrupt mask register0x800x20read-write0x7FC00000IMCPU(m) wakeup with interrupt Mask on Event input032C2IMR1C2IMR1CPUm wakeup with interrupt mask register0xC00x20read-write0x7FC00000IMCPU(m) wakeup with interrupt Mask on Event input032C1EMR1C1EMR1CPUm wakeup with event mask register0x840x20read-write0x00000000EM0_15CPU(m) Wakeup with event generation Mask on Event input016EM17_21CPU(m) Wakeup with event generation Mask on Event input175C2EMR1C2EMR1CPUm wakeup with event mask register0xC40x20read-write0x00000000EM0_15CPU(m) Wakeup with event generation Mask on Event input016EM17_21CPU(m) Wakeup with event generation Mask on Event input175C1IMR2C1IMR2CPUm wakeup with interrupt mask register0x900x20read-write0x0001FCFDIMCPUm Wakeup with interrupt Mask on Event input017C2IMR2C2IMR2CPUm wakeup with interrupt mask register0xD00x20read-write0x0001FCFDIMCPUm Wakeup with interrupt Mask on Event input017C1EMR2C1EMR2CPUm wakeup with event mask register0x940x20read-write0x00000000EMCPU(m) Wakeup with event generation Mask on Event input82C2EMR2C2EMR2CPUm wakeup with event mask register0xD40x20read-write0x00000000EMCPU(m) Wakeup with event generation Mask on Event input82HWCFGR5HWCFGR5Hardware configuration registers0x3E00x20read-only0x003EFFFFCPUEVENTHW configuration CPU event generation032HWCFGR6HWCFGR6Hardware configuration registers0x3DC0x20read-only0x00000300CPUEVENTHW configuration CPU event generation032HWCFGR7HWCFGR7EXTI Hardware configuration registers0x3D80x20read-only0x00000000CPUEVENTHW configuration CPU event generation032HWCFGR2HWCFGR2Hardware configuration registers0x3EC0x20read-only0x803FFFFFEVENT_TRGHW configuration event trigger type032HWCFGR3HWCFGR3Hardware configuration registers0x3E80x20read-only0x00000302EVENT_TRGHW configuration event trigger type032HWCFGR4HWCFGR4Hardware configuration registers0x3E40x20read-only0x00000000EVENT_TRGHW configuration event trigger type032HWCFGR1HWCFGR1Hardware configuration register 10x3F00x20read-only0x00003130NBEVENTSHW configuration number of event08NBCPUSHW configuration number of CPUs84CPUEVTENHW configuration of CPU(m) event output enable124VERRVERREXTI IP Version register0x3F40x20read-only0X00000020MINREVMinor Revision number04MAJREVMajor Revision number44IPIDRIPIDRIdentification register0x3F80x20read-only0x000E0001IPIDIP Identification032SIDRSIDRSize ID register0x3FC0x20read-only0xA3C5DD01SIDSize Identification032CRSClock recovery systemCRS0x400060000x00x400registersCRS_ITCRS interrupt42CRCRCRS control register0x00x20read-write0x00002000SYNCOKIESYNC event OK interrupt enable01SYNCWARNIESYNC warning interrupt enable11ERRIESynchronization or trimming error interrupt enable21ESYNCIEExpected SYNC interrupt enable31CENFrequency error counter enable51AUTOTRIMENAutomatic trimming enable61SWSYNCAutomatic trimming enable71TRIMHSI48 oscillator smooth trimming86CFGRCFGRCRS configuration register0x40x20read-write0x2022BB7FRELOADCounter reload value016FELIMFrequency error limit168SYNCDIVSYNCDIV243SYNCSRCSYNC signal source selection282SYNCPOLSYNC polarity selection311ISRISRCRS interrupt and status register0x80x20read-only0x00000000SYNCOKFSYNC event OK flag01SYNCWARNFSYNC warning flag11ERRFError flag21ESYNCFExpected SYNC flag31SYNCERRSYNC error81SYNCMISSSYNC missed91TRIMOVFTrimming overflow or underflow101FEDIRFrequency error direction151FECAPFrequency error capture1616ICRICRCRS interrupt flag clear register0xC0x20read-write0x00000000SYNCOKCSYNC event OK clear flag01SYNCWARNCwarning clear flag11ERRCError clear flag21ESYNCCExpected SYNC clear flag31USBUniversal serial bus full-speed device interfaceUSB0x400068000x00x800registersUSB_HPUSB high priority interrupt19USB_LPUSB low priority interrupt (including USB + wakeup)20EP0REP0Rendpoint 0 register0x00x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP1REP1Rendpoint 1 register0x40x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP2REP2Rendpoint 2 register0x80x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP3REP3Rendpoint 3 register0xC0x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP4REP4Rendpoint 4 register0x100x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP5REP5Rendpoint 5 register0x140x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP6REP6Rendpoint 6 register0x180x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151EP7REP7Rendpoint 7 register0x1C0x10read-write0x00000000EAEndpoint address04STAT_TXStatus bits, for transmission transfers42DTOG_TXData Toggle, for transmission transfers61CTR_TXCorrect Transfer for transmission71EP_KINDEndpoint kind81EP_TYPEEndpoint type92SETUPSetup transaction completed111STAT_RXStatus bits, for reception transfers122DTOG_RXData Toggle, for reception transfers141CTR_RXCorrect transfer for reception151CNTRCNTRcontrol register0x400x10read-write0x00000003FRESForce USB Reset01PDWNPower down11LPMODELow-power mode21FSUSPForce suspend31RESUMEResume request41L1RESUMELPM L1 Resume request51L1REQMLPM L1 state request interrupt mask71ESOFMExpected start of frame interrupt mask81SOFMStart of frame interrupt mask91RESETMUSB reset interrupt mask101SUSPMSuspend mode interrupt mask111WKUPMWakeup interrupt mask121ERRMError interrupt mask131PMAOVRMPacket memory area over / underrun interrupt mask141CTRMCorrect transfer interrupt mask151ISTRISTRinterrupt status register0x440x100x00000000EP_IDEndpoint Identifier04read-onlyDIRDirection of transaction41read-onlyL1REQLPM L1 state request71read-writeESOFExpected start frame81read-writeSOFstart of frame91read-writeRESETreset request101read-writeSUSPSuspend mode request111read-writeWKUPWakeup121read-writeERRError131read-writePMAOVRPacket memory area over / underrun141read-writeCTRCorrect transfer151read-onlyFNRFNRframe number register0x480x10read-only0x0000FNFrame number011LSOFLost SOF112LCKLocked131RXDMReceive data - line status141RXDPReceive data + line status151DADDRDADDRdevice address0x4C0x10read-write0x0000ADDDevice address07EFEnable function71BTABLEBTABLEBuffer table address0x500x10read-write0x0000BTABLEBuffer table313COUNT0_TXCOUNT0_TXTransmission byte count 00x520x10read-write0x0000COUNT0_TXTransmission byte count010COUNT1_TXCOUNT1_TXTransmission byte count 00x5A0x10read-write0x0000COUNT1_TXTransmission byte count010COUNT2_TXCOUNT2_TXTransmission byte count 00x620x10read-write0x0000COUNT2_TXTransmission byte count010COUNT3_TXCOUNT3_TXTransmission byte count 00x6A0x10read-write0x0000COUNT3_TXTransmission byte count010COUNT4_TXCOUNT4_TXTransmission byte count 00x720x10read-write0x0000COUNT4_TXTransmission byte count010COUNT5_TXCOUNT5_TXTransmission byte count 00x7A0x10read-write0x0000COUNT5_TXTransmission byte count010COUNT6_TXCOUNT6_TXTransmission byte count 00x820x10read-write0x0000COUNT6_TXTransmission byte count010COUNT7_TXCOUNT7_TXTransmission byte count 00x8A0x10read-write0x0000COUNT7_TXTransmission byte count010ADDR0_RXADDR0_RXReception buffer address 00x540x10read-write0x0000ADDR0_RXReception buffer address115ADDR1_RXADDR1_RXReception buffer address 00x5C0x10read-write0x0000ADDR1_RXReception buffer address115ADDR2_RXADDR2_RXReception buffer address 00x640x10read-write0x0000ADDR2_RXReception buffer address115ADDR3_RXADDR3_RXReception buffer address 00x6C0x10read-write0x0000ADDR3_RXReception buffer address115ADDR4_RXADDR4_RXReception buffer address 00x740x10read-write0x0000ADDR4_RXReception buffer address115ADDR5_RXADDR5_RXReception buffer address 00x7C0x10read-write0x0000ADDR5_RXReception buffer address115ADDR6_RXADDR6_RXReception buffer address 00x840x10read-write0x0000ADDR6_RXReception buffer address115ADDR7_RXADDR7_RXReception buffer address 00x8C0x10read-write0x0000ADDR7_RXReception buffer address115COUNT0_RXCOUNT0_RXReception byte count 00x560x100x0000COUNT0_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT1_RXCOUNT1_RXReception byte count 00x5E0x100x0000COUNT1_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT2_RXCOUNT2_RXReception byte count 00x660x100x0000COUNT2_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT3_RXCOUNT3_RXReception byte count 00x6E0x100x0000COUNT3_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT4_RXCOUNT4_RXReception byte count 00x760x100x0000COUNT4_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT5_RXCOUNT5_RXReception byte count 00x7E0x100x0000COUNT5_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT6_RXCOUNT6_RXReception byte count 00x860x100x0000COUNT6_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeCOUNT7_RXCOUNT7_RXReception byte count 00x8E0x100x0000COUNT7_RXReception byte count010read-onlyNUM_BLOCKNumber of blocks105read-writeBL_SIZEBlock size151read-writeLPMCSRLPMCSRcontrol and status registerADDR0_RX0x540x100x0000LPMENLPM support enable01read-writeLPMACKLPM Token acknowledge enable11read-writeREMWAKERemoteWake value31read-writeBESLBESL value44read-onlyBCDRBCDRBattery charging detector(0x580x100x0000BCDENBattery charging detector (BCD) enable01read-writeDCDENData contact detection (DCD) mode enable11read-writePDENPrimary detection (PD) mode enable21read-writeSDENSecondary detection (SD) mode enable31read-writeDCDETData contact detection (DCD) status41read-onlyPDETPrimary detection (PD) status51read-onlySDETSecondary detection (SD) status61read-onlyPS2DETDM pull-up detection status71read-onlyDPPUDP pull-up control151read-writeSCBSystem control blockSCB0xE000ED000x00x41registersCPUIDCPUIDCPUID base register0x00x20read-only0x410FC241RevisionRevision number04PartNoPart number of the processor412ConstantReads as 0xF164VariantVariant number204ImplementerImplementer code248ICSRICSRInterrupt control and state register0x40x20read-write0x00000000VECTACTIVEActive vector09RETTOBASEReturn to base level111VECTPENDINGPending vector127ISRPENDINGInterrupt pending flag221PENDSTCLRSysTick exception clear-pending bit251PENDSTSETSysTick exception set-pending bit261PENDSVCLRPendSV clear-pending bit271PENDSVSETPendSV set-pending bit281NMIPENDSETNMI set-pending bit.311VTORVTORVector table offset register0x80x20read-write0x00000000TBLOFFVector table base offset field921AIRCRAIRCRApplication interrupt and reset control register0xC0x20read-write0x00000000VECTRESETVECTRESET01VECTCLRACTIVEVECTCLRACTIVE11SYSRESETREQSYSRESETREQ21PRIGROUPPRIGROUP83ENDIANESSENDIANESS151VECTKEYSTATRegister key1616SCRSCRSystem control register0x100x20read-write0x00000000SLEEPONEXITSLEEPONEXIT11SLEEPDEEPSLEEPDEEP21SEVEONPENDSend Event on Pending bit41CCRCCRConfiguration and control register0x140x20read-write0x00000000NONBASETHRDENAConfigures how the processor enters Thread mode01USERSETMPENDUSERSETMPEND11UNALIGN__TRPUNALIGN_ TRP31DIV_0_TRPDIV_0_TRP41BFHFNMIGNBFHFNMIGN81STKALIGNSTKALIGN91SHPR1SHPR1System handler priority registers0x180x20read-write0x00000000PRI_4Priority of system handler 408PRI_5Priority of system handler 588PRI_6Priority of system handler 6168SHPR2SHPR2System handler priority registers0x1C0x20read-write0x00000000PRI_11Priority of system handler 11248SHPR3SHPR3System handler priority registers0x200x20read-write0x00000000PRI_14Priority of system handler 14168PRI_15Priority of system handler 15248SHCSRSHCSRSystem handler control and state register0x240x20read-write0x00000000MEMFAULTACTMemory management fault exception active bit01BUSFAULTACTBus fault exception active bit11USGFAULTACTUsage fault exception active bit31SVCALLACTSVC call active bit71MONITORACTDebug monitor active bit81PENDSVACTPendSV exception active bit101SYSTICKACTSysTick exception active bit111USGFAULTPENDEDUsage fault exception pending bit121MEMFAULTPENDEDMemory management fault exception pending bit131BUSFAULTPENDEDBus fault exception pending bit141SVCALLPENDEDSVC call pending bit151MEMFAULTENAMemory management fault enable bit161BUSFAULTENABus fault enable bit171USGFAULTENAUsage fault enable bit181CFSR_UFSR_BFSR_MMFSRCFSR_UFSR_BFSR_MMFSRConfigurable fault status register0x280x20read-write0x00000000IACCVIOLInstruction access violation flag11MUNSTKERRMemory manager fault on unstacking for a return from exception31MSTKERRMemory manager fault on stacking for exception entry.41MLSPERRMLSPERR51MMARVALIDMemory Management Fault Address Register (MMAR) valid flag71IBUSERRInstruction bus error81PRECISERRPrecise data bus error91IMPRECISERRImprecise data bus error101UNSTKERRBus fault on unstacking for a return from exception111STKERRBus fault on stacking for exception entry121LSPERRBus fault on floating-point lazy state preservation131BFARVALIDBus Fault Address Register (BFAR) valid flag151UNDEFINSTRUndefined instruction usage fault161INVSTATEInvalid state usage fault171INVPCInvalid PC load usage fault181NOCPNo coprocessor usage fault.191UNALIGNEDUnaligned access usage fault241DIVBYZERODivide by zero usage fault251HFSRHFSRHard fault status register0x2C0x20read-write0x00000000VECTTBLVector table hard fault11FORCEDForced hard fault301DEBUG_VTReserved for Debug use311MMFARMMFARMemory management fault address register0x340x20read-write0x00000000MMFARMemory management fault address032BFARBFARBus fault address register0x380x20read-write0x00000000BFARBus fault address032AFSRAFSRAuxiliary fault status register0x3C0x20read-write0x00000000IMPDEFImplementation defined032STKSysTick timerSTK0xE000E0100x00x11registersCTRLCTRLSysTick control and status register0x00x20read-write0X00000000ENABLECounter enable01TICKINTSysTick exception request enable11CLKSOURCEClock source selection21COUNTFLAGCOUNTFLAG161LOADLOADSysTick reload value register0x40x20read-write0X00000000RELOADRELOAD value024VALVALSysTick current value register0x80x20read-write0X00000000CURRENTCurrent counter value024CALIBCALIBSysTick calibration value register0xC0x20read-write0X00000000TENMSCalibration value024SKEWSKEW flag: Indicates whether the TENMS value is exact301NOREFNOREF flag. Reads as zero311MPUMemory protection unitMPU0xE000ED900x00x15registersMPU_TYPERMPU_TYPERMPU type register0x00x20read-only0X00000800SEPARATESeparate flag01DREGIONNumber of MPU data regions88IREGIONNumber of MPU instruction regions168MPU_CTRLMPU_CTRLMPU control register0x40x20read-only0X00000000ENABLEEnables the MPU01HFNMIENAEnables the operation of MPU during hard fault11PRIVDEFENAEnable priviliged software access to default memory map21MPU_RNRMPU_RNRMPU region number register0x80x20read-write0X00000000REGIONMPU region08MPU_RBARMPU_RBARMPU region base address register0xC0x20read-write0X00000000REGIONMPU region field04VALIDMPU region number valid41ADDRRegion base address field527MPU_RASRMPU_RASRMPU region attribute and size register0x100x20read-write0X00000000ENABLERegion enable bit.01SIZESize of the MPU protection region15SRDSubregion disable bits88Bmemory attribute161Cmemory attribute171SShareable memory attribute181TEXmemory attribute193APAccess permission243XNInstruction access disable bit281FPUFloting point unitFPU0xE000EF340x00xDregistersFPUFloating point unit interrupt54FPCCRFPCCRFloating-point context control register0x00x20read-write0x00000000LSPACTLSPACT01USERUSER11THREADTHREAD31HFRDYHFRDY41MMRDYMMRDY51BFRDYBFRDY61MONRDYMONRDY81LSPENLSPEN301ASPENASPEN311FPCARFPCARFloating-point context address register0x40x20read-write0x00000000ADDRESSLocation of unpopulated floating-point329FPSCRFPSCRFloating-point status control register0x80x20read-write0x00000000IOCInvalid operation cumulative exception bit01DZCDivision by zero cumulative exception bit.11OFCOverflow cumulative exception bit21UFCUnderflow cumulative exception bit31IXCInexact cumulative exception bit41IDCInput denormal cumulative exception bit.71RModeRounding Mode control field222FZFlush-to-zero mode control bit:241DNDefault NaN mode control bit251AHPAlternative half-precision control bit261VOverflow condition code flag281CCarry condition code flag291ZZero condition code flag301NNegative condition code flag311NVICNested Vectored Interrupt ControllerNVIC0xE000E1000x00x351registersISER0ISER0Interrupt Set-Enable Register0x00x20read-write0x00000000SETENASETENA032ISER1ISER1Interrupt Set-Enable Register0x40x20read-write0x00000000SETENASETENA032ICER0ICER0Interrupt Clear-Enable Register0x800x20read-write0x00000000CLRENACLRENA032ICER1ICER1Interrupt Clear-Enable Register0x840x20read-write0x00000000CLRENACLRENA032ISPR0ISPR0Interrupt Set-Pending Register0x1000x20read-write0x00000000SETPENDSETPEND032ISPR1ISPR1Interrupt Set-Pending Register0x1040x20read-write0x00000000SETPENDSETPEND032ICPR0ICPR0Interrupt Clear-Pending Register0x1800x20read-write0x00000000CLRPENDCLRPEND032ICPR1ICPR1Interrupt Clear-Pending Register0x1840x20read-write0x00000000CLRPENDCLRPEND032IABR0IABR0Interrupt Active Bit Register0x2000x20read-only0x00000000ACTIVEACTIVE032IABR1IABR1Interrupt Active Bit Register0x2040x20read-only0x00000000ACTIVEACTIVE032IPR0IPR0Interrupt Priority Register0x3000x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR1IPR1Interrupt Priority Register0x3040x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR2IPR2Interrupt Priority Register0x3080x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR3IPR3Interrupt Priority Register0x30C0x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR4IPR4Interrupt Priority Register0x3100x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR5IPR5Interrupt Priority Register0x3140x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR6IPR6Interrupt Priority Register0x3180x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR7IPR7Interrupt Priority Register0x31C0x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR8IPR8Interrupt Priority Register0x3200x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR9IPR9Interrupt Priority Register0x3240x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR10IPR10Interrupt Priority Register0x3280x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR11IPR11Interrupt Priority Register0x32C0x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR12IPR12Interrupt Priority Register0x3300x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR13IPR13Interrupt Priority Register0x3340x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR14IPR14Interrupt Priority Register0x3380x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR15IPR15Interrupt Priority Register0x33C0x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR16IPR16Interrupt Priority Register0x3400x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248IPR17IPR17Interrupt Priority Register0x3440x20read-write0x00000000IPR_N0IPR_N008IPR_N1IPR_N188IPR_N2IPR_N2168IPR_N3IPR_N3248NVIC_STIRNested vectored interrupt controllerNVIC0xE000EF000x00x5registersSTIRSTIRSoftware trigger interrupt register0x00x20read-write0x00000000INTIDSoftware generated interrupt ID09SCB_ACTRLSystem control block ACTLRSCB0xE000E0080x00x5registersACTRLACTRLAuxiliary control register0x00x20read-write0x00000000DISMCYCINTDISMCYCINT01DISDEFWBUFDISDEFWBUF11DISFOLDDISFOLD21DISFPCADISFPCA81DISOOFPDISOOFP91FPU_CPACRFloating point unit CPACRFPU0xE000ED880x00x5registersCPACRCPACRCoprocessor access control register0x00x20read-write0x0000000CPCP204 diff --git a/docker/Dockerfile b/docker/Dockerfile index f130305e..d987d75a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,6 +15,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-instal libxml2-dev \ libxslt1-dev \ zlib1g-dev \ + jq \ wget && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/firmware/targets/f6/Inc/FreeRTOSConfig.h b/firmware/targets/f6/Inc/FreeRTOSConfig.h index 228b530d..1ad20993 100644 --- a/firmware/targets/f6/Inc/FreeRTOSConfig.h +++ b/firmware/targets/f6/Inc/FreeRTOSConfig.h @@ -57,7 +57,7 @@ #endif /* CMSIS_device_header */ #define configENABLE_FPU 1 -#define configENABLE_MPU 1 +#define configENABLE_MPU 0 #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 0 @@ -101,6 +101,9 @@ #define configTIMER_TASK_PRIORITY ( 2 ) #define configTIMER_QUEUE_LENGTH 32 #define configTIMER_TASK_STACK_DEPTH 256 +#define configTIMER_SERVICE_TASK_NAME "TimersSrv" + +#define configIDLE_TASK_NAME "(-_-)" /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ diff --git a/firmware/targets/f6/Inc/stm32_assert.h b/firmware/targets/f6/Inc/stm32_assert.h new file mode 100644 index 00000000..9f6261ff --- /dev/null +++ b/firmware/targets/f6/Inc/stm32_assert.h @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * @file stm32_assert.h + * @brief STM32 assert file. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2019 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32_ASSERT_H +#define __STM32_ASSERT_H + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef USE_FULL_ASSERT + #define assert_param(expr) ((expr) ? (void)0U : assert_failed()) + void assert_failed(); +#else + #define assert_param(expr) ((void)0U) +#endif /* USE_FULL_ASSERT */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32_ASSERT_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f6/Inc/stm32wbxx_hal_conf.h b/firmware/targets/f6/Inc/stm32wbxx_hal_conf.h index ab7b2953..4d5ad791 100644 --- a/firmware/targets/f6/Inc/stm32wbxx_hal_conf.h +++ b/firmware/targets/f6/Inc/stm32wbxx_hal_conf.h @@ -184,7 +184,7 @@ * @brief Uncomment the line below to expanse the "assert_param" macro in the * HAL drivers code */ -/* #define USE_FULL_ASSERT 1U */ +#define USE_FULL_ASSERT 1U /* ################## SPI peripheral configuration ########################## */ @@ -329,17 +329,8 @@ /* Exported macro ------------------------------------------------------------*/ #ifdef USE_FULL_ASSERT -/** - * @brief The assert_param macro is used for function's parameters check. - * @param expr If expr is false, it calls assert_failed function - * which reports the name of the source file and the source - * line number of the call that failed. - * If expr is true, it returns no value. - * @retval None - */ - #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) -/* Exported functions ------------------------------------------------------- */ - void assert_failed(uint8_t* file, uint32_t line); + #define assert_param(expr) ((expr) ? (void)0U : assert_failed()) + void assert_failed(); #else #define assert_param(expr) ((void)0U) #endif /* USE_FULL_ASSERT */ diff --git a/firmware/targets/f6/Src/main.c b/firmware/targets/f6/Src/main.c index ea6d8943..a546086f 100644 --- a/firmware/targets/f6/Src/main.c +++ b/firmware/targets/f6/Src/main.c @@ -1,11 +1,11 @@ #include "main.h" -#include "fatfs/fatfs.h" - #include #include #include +#define TAG "Main" + int main(void) { // Initialize FURI layer furi_init(); @@ -16,13 +16,9 @@ int main(void) { // Flipper FURI HAL furi_hal_init(); - // 3rd party - MX_FATFS_Init(); - FURI_LOG_I("HAL", "FATFS OK"); - // CMSIS initialization osKernelInitialize(); - FURI_LOG_I("HAL", "KERNEL OK"); + FURI_LOG_I(TAG, "KERNEL OK"); // Init flipper flipper_init(); @@ -47,9 +43,6 @@ void Error_Handler(void) { * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { - /* USER CODE BEGIN 6 */ - /* User can add his own implementation to report the file name and line number, - tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ - /* USER CODE END 6 */ + furi_crash("HAL assert failed"); } #endif /* USE_FULL_ASSERT */ diff --git a/firmware/targets/f6/ble-glue/app_conf.h b/firmware/targets/f6/ble-glue/app_conf.h index e3820e11..eebdbbdb 100644 --- a/firmware/targets/f6/ble-glue/app_conf.h +++ b/firmware/targets/f6/ble-glue/app_conf.h @@ -427,16 +427,5 @@ typedef enum #define DBG_TRACE_MSG_QUEUE_SIZE 4096 #define MAX_DBG_TRACE_MSG_SIZE 1024 -/****************************************************************************** - * FreeRTOS - ******************************************************************************/ -#define CFG_SHCI_USER_EVT_PROCESS_NAME "ble_shci_evt" -#define CFG_SHCI_USER_EVT_PROCESS_ATTR_BITS (0) -#define CFG_SHCI_USER_EVT_PROCESS_CB_MEM (0) -#define CFG_SHCI_USER_EVT_PROCESS_CB_SIZE (0) -#define CFG_SHCI_USER_EVT_PROCESS_STACK_MEM (0) -#define CFG_SHCI_USER_EVT_PROCESS_PRIORITY osPriorityNone -#define CFG_SHCI_USER_EVT_PROCESS_STACK_SIZE (128 * 7) - #define CFG_OTP_BASE_ADDRESS OTP_AREA_BASE #define CFG_OTP_END_ADRESS OTP_AREA_END_ADDR diff --git a/firmware/targets/f6/ble-glue/app_entry.c b/firmware/targets/f6/ble-glue/app_entry.c deleted file mode 100644 index 5c8c0b2a..00000000 --- a/firmware/targets/f6/ble-glue/app_entry.c +++ /dev/null @@ -1,180 +0,0 @@ -#include "app_common.h" -#include "main.h" -#include "app_entry.h" -#include "ble_app.h" -#include "ble.h" -#include "tl.h" -#include "cmsis_os.h" -#include "shci_tl.h" -#include "app_debug.h" -#include - -extern RTC_HandleTypeDef hrtc; - -#define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) - -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t EvtPool[POOL_SIZE]; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t SystemCmdBuffer; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t SystemSpareEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t BleSpareEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; - -osMutexId_t MtxShciId; -osSemaphoreId_t SemShciId; -osThreadId_t ShciUserEvtProcessId; - -volatile static BleGlueStatus ble_glue_status = BleGlueStatusUninitialized; - -const osThreadAttr_t ShciUserEvtProcess_attr = { - .name = CFG_SHCI_USER_EVT_PROCESS_NAME, - .attr_bits = CFG_SHCI_USER_EVT_PROCESS_ATTR_BITS, - .cb_mem = CFG_SHCI_USER_EVT_PROCESS_CB_MEM, - .cb_size = CFG_SHCI_USER_EVT_PROCESS_CB_SIZE, - .stack_mem = CFG_SHCI_USER_EVT_PROCESS_STACK_MEM, - .priority = CFG_SHCI_USER_EVT_PROCESS_PRIORITY, - .stack_size = CFG_SHCI_USER_EVT_PROCESS_STACK_SIZE -}; - -static void ShciUserEvtProcess(void *argument); -static void SystemPower_Config( void ); -static void appe_Tl_Init( void ); -static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status ); -static void APPE_SysUserEvtRx( void * pPayload ); - -BleGlueStatus APPE_Status() { - return ble_glue_status; -} - -void APPE_Init() { - ble_glue_status = BleGlueStatusStartup; - SystemPower_Config(); /**< Configure the system Power Mode */ - - // APPD_Init(); - furi_hal_power_insomnia_enter(); - - appe_Tl_Init(); /* Initialize all transport layers */ - - /** - * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) - * received on the system channel before starting the Stack - * This system event is received with APPE_SysUserEvtRx() - */ -} - -/************************************************************* - * - * LOCAL FUNCTIONS - * - *************************************************************/ - -/** - * @brief Configure the system for power optimization - * - * @note This API configures the system to be ready for low power mode - * - * @param None - * @retval None - */ -static void SystemPower_Config(void) { - // Select HSI as system clock source after Wake Up from Stop mode - LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); - - /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); -} - -static void appe_Tl_Init( void ) { - TL_MM_Config_t tl_mm_config; - SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf; - /**< Reference table initialization */ - TL_Init(); - - MtxShciId = osMutexNew( NULL ); - SemShciId = osSemaphoreNew( 1, 0, NULL ); /*< Create the semaphore and make it busy at initialization */ - - /** FreeRTOS system task creation */ - ShciUserEvtProcessId = osThreadNew(ShciUserEvtProcess, NULL, &ShciUserEvtProcess_attr); - - /**< System channel initialization */ - SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&SystemCmdBuffer; - SHci_Tl_Init_Conf.StatusNotCallBack = APPE_SysStatusNot; - shci_init(APPE_SysUserEvtRx, (void*) &SHci_Tl_Init_Conf); - - /**< Memory Manager channel initialization */ - tl_mm_config.p_BleSpareEvtBuffer = BleSpareEvtBuffer; - tl_mm_config.p_SystemSpareEvtBuffer = SystemSpareEvtBuffer; - tl_mm_config.p_AsynchEvtPool = EvtPool; - tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; - TL_MM_Init( &tl_mm_config ); - - TL_Enable(); -} - -static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status ) { - switch (status) { - case SHCI_TL_CmdBusy: - osMutexAcquire( MtxShciId, osWaitForever ); - break; - case SHCI_TL_CmdAvailable: - osMutexRelease( MtxShciId ); - break; - default: - break; - } -} - -/** - * The type of the payload for a system user event is tSHCI_UserEvtRxParam - * When the system event is both : - * - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY) - * - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING) - * The buffer shall not be released - * ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) - * When the status is not filled, the buffer is released by default - */ -static void APPE_SysUserEvtRx( void * pPayload ) { - UNUSED(pPayload); - /* Traces channel initialization */ - // APPD_EnableCPU2( ); - - if(ble_app_init()) { - FURI_LOG_I("Core2", "BLE stack started"); - ble_glue_status = BleGlueStatusStarted; - } else { - FURI_LOG_E("Core2", "BLE stack startup failed"); - ble_glue_status = BleGlueStatusBroken; - } - furi_hal_power_insomnia_exit(); -} - -/************************************************************* - * - * FREERTOS WRAPPER FUNCTIONS - * -*************************************************************/ -static void ShciUserEvtProcess(void *argument) { - UNUSED(argument); - for(;;) { - osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); - shci_user_evt_proc(); - } -} - -/************************************************************* - * - * WRAP FUNCTIONS - * - *************************************************************/ -void shci_notify_asynch_evt(void* pdata) { - UNUSED(pdata); - osThreadFlagsSet( ShciUserEvtProcessId, 1 ); -} - -void shci_cmd_resp_release(uint32_t flag) { - UNUSED(flag); - osSemaphoreRelease( SemShciId ); -} - -void shci_cmd_resp_wait(uint32_t timeout) { - UNUSED(timeout); - osSemaphoreAcquire( SemShciId, osWaitForever ); -} diff --git a/firmware/targets/f6/ble-glue/app_entry.h b/firmware/targets/f6/ble-glue/app_entry.h deleted file mode 100644 index f1ce6038..00000000 --- a/firmware/targets/f6/ble-glue/app_entry.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - BleGlueStatusUninitialized, - BleGlueStatusStartup, - BleGlueStatusBroken, - BleGlueStatusStarted -} BleGlueStatus; - -void APPE_Init(); - -BleGlueStatus APPE_Status(); - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/firmware/targets/f6/ble-glue/battery_service.c b/firmware/targets/f6/ble-glue/battery_service.c index 1dd8c5a0..2a5dad5e 100644 --- a/firmware/targets/f6/ble-glue/battery_service.c +++ b/firmware/targets/f6/ble-glue/battery_service.c @@ -4,7 +4,7 @@ #include -#define BATTERY_SERVICE_TAG "battery service" +#define TAG "BtBatterySvc" typedef struct { uint16_t svc_handle; @@ -23,7 +23,7 @@ void battery_svc_start() { // Add Battery service status = aci_gatt_add_service(UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 4, &battery_svc->svc_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to add Battery service: %d", status); + FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); } // Add Battery level characteristic status = aci_gatt_add_char(battery_svc->svc_handle, @@ -37,7 +37,7 @@ void battery_svc_start() { CHAR_VALUE_LEN_CONSTANT, &battery_svc->char_level_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to add Battery level characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); } } @@ -47,12 +47,12 @@ void battery_svc_stop() { // Delete Battery level characteristic status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->char_level_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to delete Battery level characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to delete Battery level characteristic: %d", status); } // Delete Battery service status = aci_gatt_del_service(battery_svc->svc_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to delete Battery service: %d", status); + FURI_LOG_E(TAG, "Failed to delete Battery service: %d", status); } free(battery_svc); battery_svc = NULL; @@ -65,14 +65,14 @@ bool battery_svc_update_level(uint8_t battery_charge) { return false; } // Update battery level characteristic - FURI_LOG_I(BATTERY_SERVICE_TAG, "Updating battery level characteristic"); + FURI_LOG_I(TAG, "Updating battery level characteristic"); tBleStatus result = aci_gatt_update_char_value(battery_svc->svc_handle, battery_svc->char_level_handle, 0, 1, &battery_charge); if(result) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed updating RX characteristic: %d", result); + FURI_LOG_E(TAG, "Failed updating RX characteristic: %d", result); } return result != BLE_STATUS_SUCCESS; } diff --git a/firmware/targets/f6/ble-glue/ble_app.c b/firmware/targets/f6/ble-glue/ble_app.c index e28facd6..40b34679 100644 --- a/firmware/targets/f6/ble-glue/ble_app.c +++ b/firmware/targets/f6/ble-glue/ble_app.c @@ -8,9 +8,10 @@ #include -#define BLE_APP_TAG "ble app" +#define TAG "Bt" PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; typedef struct { osMutexId_t hci_mtx; @@ -26,13 +27,13 @@ static void ble_app_hci_event_handler(void * pPayload); static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status); bool ble_app_init() { + SHCI_CmdStatus_t status; ble_app = furi_alloc(sizeof(BleApp)); - // Allocate semafore and mutex for ble command buffer access ble_app->hci_mtx = osMutexNew(NULL); ble_app->hci_sem = osSemaphoreNew(1, 0, NULL); // HCI transport layer thread to handle user asynch events - ble_app->hci_thread_attr.name = "ble hci"; + ble_app->hci_thread_attr.name = "BleHciWorker"; ble_app->hci_thread_attr.stack_size = 1024; ble_app->hci_thread_id = osThreadNew(ble_app_hci_thread, NULL, &ble_app->hci_thread_attr); @@ -43,6 +44,18 @@ bool ble_app_init() { }; hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config); + // Configure NVM store for pairing data + SHCI_C2_CONFIG_Cmd_Param_t config_param = { + .PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE, + .Config1 =SHCI_C2_CONFIG_CONFIG1_BIT0_BLE_NVM_DATA_TO_SRAM, + .BleNvmRamAddress = (uint32_t)ble_app_nvm, + .EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE, + }; + status = SHCI_C2_Config(&config_param); + if(status) { + FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); + } + // Start ble stack on 2nd core SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { .Header = {{0,0,0}}, // Header unused @@ -67,13 +80,18 @@ bool ble_app_init() { 0, } }; - SHCI_CmdStatus_t status = SHCI_C2_BLE_Init(&ble_init_cmd_packet); + status = SHCI_C2_BLE_Init(&ble_init_cmd_packet); if(status) { - FURI_LOG_E(BLE_APP_TAG, "Failed to start ble stack: %d", status); + FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); } return status == SHCI_Success; } +void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) { + *addr = (uint8_t*)ble_app_nvm; + *size = sizeof(ble_app_nvm); +} + static void ble_app_hci_thread(void *arg) { while(1) { osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); diff --git a/firmware/targets/f6/ble-glue/ble_app.h b/firmware/targets/f6/ble-glue/ble_app.h index 17d8dc5f..64000bde 100644 --- a/firmware/targets/f6/ble-glue/ble_app.h +++ b/firmware/targets/f6/ble-glue/ble_app.h @@ -5,8 +5,10 @@ extern "C" { #endif #include +#include bool ble_app_init(); +void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); #ifdef __cplusplus } diff --git a/firmware/targets/f6/ble-glue/ble_glue.c b/firmware/targets/f6/ble-glue/ble_glue.c new file mode 100644 index 00000000..45503683 --- /dev/null +++ b/firmware/targets/f6/ble-glue/ble_glue.c @@ -0,0 +1,230 @@ +#include "ble_glue.h" +#include "app_common.h" +#include "main.h" +#include "ble_app.h" +#include "ble.h" +#include "tl.h" +#include "shci.h" +#include "cmsis_os.h" +#include "shci_tl.h" +#include "app_debug.h" +#include + +#define TAG "Core2" + +#define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) + +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_system_cmd_buff; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_system_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; + +typedef enum { + // Stage 1: core2 startup and FUS + BleGlueStatusStartup, + BleGlueStatusBroken, + BleGlueStatusFusStarted, + // Stage 2: radio stack + BleGlueStatusRadioStackStarted, + BleGlueStatusRadioStackMissing +} BleGlueStatus; + +typedef struct { + osMutexId_t shci_mtx; + osSemaphoreId_t shci_sem; + osThreadId_t shci_user_event_thread_id; + osThreadAttr_t shci_user_event_thread_attr; + BleGlueStatus status; + BleGlueKeyStorageChangedCallback callback; + void* context; +} BleGlue; + +static BleGlue* ble_glue = NULL; + +static void ble_glue_user_event_thread(void *argument); +static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status); +static void ble_glue_sys_user_event_callback(void* pPayload); + +void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback callback, void* context) { + furi_assert(ble_glue); + furi_assert(callback); + ble_glue->callback = callback; + ble_glue->context = context; +} + +void ble_glue_init() { + ble_glue = furi_alloc(sizeof(BleGlue)); + ble_glue->status = BleGlueStatusStartup; + ble_glue->shci_user_event_thread_attr.name = "BleShciWorker"; + ble_glue->shci_user_event_thread_attr.stack_size = 1024; + + // Configure the system Power Mode + // Select HSI as system clock source after Wake Up from Stop mode + LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); + /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + furi_hal_power_insomnia_enter(); + + // APPD_Init(); + + // Initialize all transport layers + TL_MM_Config_t tl_mm_config; + SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf; + // Reference table initialization + TL_Init(); + + ble_glue->shci_mtx = osMutexNew(NULL); + ble_glue->shci_sem = osSemaphoreNew(1, 0, NULL); + + // FreeRTOS system task creation + ble_glue->shci_user_event_thread_id = osThreadNew(ble_glue_user_event_thread, NULL, &ble_glue->shci_user_event_thread_attr); + + // System channel initialization + SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff; + SHci_Tl_Init_Conf.StatusNotCallBack = ble_glue_sys_status_not_callback; + shci_init(ble_glue_sys_user_event_callback, (void*) &SHci_Tl_Init_Conf); + + /**< Memory Manager channel initialization */ + tl_mm_config.p_BleSpareEvtBuffer = ble_glue_ble_spare_event_buff; + tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_system_spare_event_buff; + tl_mm_config.p_AsynchEvtPool = ble_glue_event_pool; + tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; + TL_MM_Init( &tl_mm_config ); + TL_Enable(); + + /* + * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) + * received on the system channel before starting the Stack + * This system event is received with ble_glue_sys_user_event_callback() + */ +} + +static bool ble_glue_wait_status(BleGlueStatus status) { + bool ret = false; + size_t countdown = 1000; + while (countdown > 0) { + if (ble_glue->status == status) { + ret = true; + break; + } + countdown--; + osDelay(1); + } + return ret; +} + +bool ble_glue_start() { + furi_assert(ble_glue); + + if (!ble_glue_wait_status(BleGlueStatusFusStarted)) { + // shutdown core2 power + FURI_LOG_E(TAG, "Core2 catastrophic failure, cutting its power"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue->status = BleGlueStatusBroken; + furi_hal_power_insomnia_exit(); + return false; + } + + bool ret = false; + furi_hal_power_insomnia_enter(); + if(ble_app_init()) { + FURI_LOG_I(TAG, "Radio stack started"); + ble_glue->status = BleGlueStatusRadioStackStarted; + ret = true; + if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { + FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); + } else { + FURI_LOG_E(TAG, "Failed to switch flash activity control to SEM7"); + } + } else { + FURI_LOG_E(TAG, "Radio stack startup failed"); + ble_glue->status = BleGlueStatusRadioStackMissing; + } + furi_hal_power_insomnia_exit(); + + return ret; +} + +bool ble_glue_is_alive() { + if(!ble_glue) { + return false; + } + + return ble_glue->status >= BleGlueStatusFusStarted; +} + +bool ble_glue_is_radio_stack_ready() { + if(!ble_glue) { + return false; + } + + return ble_glue->status == BleGlueStatusRadioStackStarted; +} + +static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { + switch (status) { + case SHCI_TL_CmdBusy: + osMutexAcquire( ble_glue->shci_mtx, osWaitForever ); + break; + case SHCI_TL_CmdAvailable: + osMutexRelease( ble_glue->shci_mtx ); + break; + default: + break; + } +} + +/* + * The type of the payload for a system user event is tSHCI_UserEvtRxParam + * When the system event is both : + * - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY) + * - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING) + * The buffer shall not be released + * ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) + * When the status is not filled, the buffer is released by default + */ +static void ble_glue_sys_user_event_callback( void * pPayload ) { + UNUSED(pPayload); + /* Traces channel initialization */ + // APPD_EnableCPU2( ); + + TL_AsynchEvt_t *p_sys_event = (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); + + if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { + FURI_LOG_I(TAG, "Fus started"); + ble_glue->status = BleGlueStatusFusStarted; + furi_hal_power_insomnia_exit(); + } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { + FURI_LOG_E(TAG, "Error during initialization"); + furi_hal_power_insomnia_exit(); + } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE) { + SHCI_C2_BleNvmRamUpdate_Evt_t* p_sys_ble_nvm_ram_update_event = (SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload; + if(ble_glue->callback) { + ble_glue->callback((uint8_t*)p_sys_ble_nvm_ram_update_event->StartAddress, p_sys_ble_nvm_ram_update_event->Size, ble_glue->context); + } + } +} + +// Wrap functions +static void ble_glue_user_event_thread(void *argument) { + UNUSED(argument); + for(;;) { + osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); + shci_user_evt_proc(); + } +} + +void shci_notify_asynch_evt(void* pdata) { + UNUSED(pdata); + osThreadFlagsSet(ble_glue->shci_user_event_thread_id, 1); +} + +void shci_cmd_resp_release(uint32_t flag) { + UNUSED(flag); + osSemaphoreRelease(ble_glue->shci_sem); +} + +void shci_cmd_resp_wait(uint32_t timeout) { + UNUSED(timeout); + osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); +} diff --git a/firmware/targets/f6/ble-glue/ble_glue.h b/firmware/targets/f6/ble-glue/ble_glue.h new file mode 100644 index 00000000..ac668c42 --- /dev/null +++ b/firmware/targets/f6/ble-glue/ble_glue.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void(*BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); + + +/** Initialize start core2 and initialize transport */ +void ble_glue_init(); + +/** Start Core2 Radio stack + * + * @return true on success + */ +bool ble_glue_start(); + +/** Is core2 alive and at least FUS is running + * + * @return true if core2 is alive + */ +bool ble_glue_is_alive(); + +/** Is core2 radio stack present and ready + * + * @return true if present and ready + */ +bool ble_glue_is_radio_stack_ready(); + +/** Set callback for NVM in RAM changes + * + * @param[in] callback The callback to call on NVM change + * @param context The context for callback + */ +void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback callback, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f6/ble-glue/dev_info_service.c b/firmware/targets/f6/ble-glue/dev_info_service.c index 7ce2647c..64ff0509 100644 --- a/firmware/targets/f6/ble-glue/dev_info_service.c +++ b/firmware/targets/f6/ble-glue/dev_info_service.c @@ -4,7 +4,7 @@ #include -#define DEV_INFO_SVC_TAG "dev info service" +#define TAG "BtDevInfoSvc" typedef struct { uint16_t service_handle; @@ -29,7 +29,7 @@ void dev_info_svc_start() { uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; status = aci_gatt_add_service(UUID_TYPE_16, (Service_UUID_t*)&uuid, PRIMARY_SERVICE, 9, &dev_info_svc->service_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add Device Information Service: %d", status); + FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status); } // Add characteristics @@ -45,7 +45,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->man_name_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add manufacturer name char: %d", status); + FURI_LOG_E(TAG, "Failed to add manufacturer name char: %d", status); } uuid = SERIAL_NUMBER_UUID; status = aci_gatt_add_char(dev_info_svc->service_handle, @@ -59,7 +59,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->serial_num_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add serial number char: %d", status); + FURI_LOG_E(TAG, "Failed to add serial number char: %d", status); } uuid = FIRMWARE_REVISION_UUID; status = aci_gatt_add_char(dev_info_svc->service_handle, @@ -73,7 +73,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->firmware_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add firmware revision char: %d", status); + FURI_LOG_E(TAG, "Failed to add firmware revision char: %d", status); } uuid = SOFTWARE_REVISION_UUID; status = aci_gatt_add_char(dev_info_svc->service_handle, @@ -87,7 +87,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->software_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add software revision char: %d", status); + FURI_LOG_E(TAG, "Failed to add software revision char: %d", status); } // Update characteristics @@ -97,7 +97,7 @@ void dev_info_svc_start() { strlen(dev_info_man_name), (uint8_t*)dev_info_man_name); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update manufacturer name char: %d", status); + FURI_LOG_E(TAG, "Failed to update manufacturer name char: %d", status); } status = aci_gatt_update_char_value(dev_info_svc->service_handle, dev_info_svc->serial_num_char_handle, @@ -105,7 +105,7 @@ void dev_info_svc_start() { strlen(dev_info_serial_num), (uint8_t*)dev_info_serial_num); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update serial number char: %d", status); + FURI_LOG_E(TAG, "Failed to update serial number char: %d", status); } status = aci_gatt_update_char_value(dev_info_svc->service_handle, dev_info_svc->firmware_rev_char_handle, @@ -113,7 +113,7 @@ void dev_info_svc_start() { strlen(dev_info_firmware_rev_num), (uint8_t*)dev_info_firmware_rev_num); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update firmware revision char: %d", status); + FURI_LOG_E(TAG, "Failed to update firmware revision char: %d", status); } status = aci_gatt_update_char_value(dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle, @@ -121,7 +121,7 @@ void dev_info_svc_start() { strlen(dev_info_software_rev_num), (uint8_t*)dev_info_software_rev_num); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update software revision char: %d", status); + FURI_LOG_E(TAG, "Failed to update software revision char: %d", status); } } @@ -131,24 +131,24 @@ void dev_info_svc_stop() { // Delete service characteristics status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->man_name_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete manufacturer name char: %d", status); + FURI_LOG_E(TAG, "Failed to delete manufacturer name char: %d", status); } status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->serial_num_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete serial number char: %d", status); + FURI_LOG_E(TAG, "Failed to delete serial number char: %d", status); } status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->firmware_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete firmware revision char: %d", status); + FURI_LOG_E(TAG, "Failed to delete firmware revision char: %d", status); } status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete software revision char: %d", status); + FURI_LOG_E(TAG, "Failed to delete software revision char: %d", status); } // Delete service status = aci_gatt_del_service(dev_info_svc->service_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete device info service: %d", status); + FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); } free(dev_info_svc); dev_info_svc = NULL; diff --git a/firmware/targets/f6/ble-glue/gap.c b/firmware/targets/f6/ble-glue/gap.c index eae5f87c..2fd29f10 100644 --- a/firmware/targets/f6/ble-glue/gap.c +++ b/firmware/targets/f6/ble-glue/gap.c @@ -1,6 +1,5 @@ #include "gap.h" -#include "app_entry.h" #include "ble.h" #include "cmsis_os.h" @@ -11,7 +10,7 @@ #include -#define GAP_TAG "BLE" +#define TAG "BtGap" #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 @@ -81,7 +80,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) if (disconnection_complete_event->Connection_Handle == gap->gap_svc.connection_handle) { gap->gap_svc.connection_handle = 0; gap->state = GapStateIdle; - FURI_LOG_I(GAP_TAG, "Disconnect from client. Reason: %d", disconnection_complete_event->Reason); + FURI_LOG_I(TAG, "Disconnect from client. Reason: %d", disconnection_complete_event->Reason); } if(gap->enable_adv) { // Restart advertising @@ -97,28 +96,28 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) meta_evt = (evt_le_meta_event*) event_pckt->data; switch (meta_evt->subevent) { case EVT_LE_CONN_UPDATE_COMPLETE: - FURI_LOG_D(GAP_TAG, "Connection update event"); + FURI_LOG_D(TAG, "Connection update event"); break; case EVT_LE_PHY_UPDATE_COMPLETE: evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data; if(evt_le_phy_update_complete->Status) { - FURI_LOG_E(GAP_TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); + FURI_LOG_E(TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); } else { - FURI_LOG_I(GAP_TAG, "Update PHY succeed"); + FURI_LOG_I(TAG, "Update PHY succeed"); } ret = hci_le_read_phy(gap->gap_svc.connection_handle,&tx_phy,&rx_phy); if(ret) { - FURI_LOG_E(GAP_TAG, "Read PHY failed, status: %d", ret); + FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); } else { - FURI_LOG_I(GAP_TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); + FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); } break; case EVT_LE_CONN_COMPLETE: furi_hal_power_insomnia_enter(); hci_le_connection_complete_event_rp0* connection_complete_event = (hci_le_connection_complete_event_rp0 *) meta_evt->data; - FURI_LOG_I(GAP_TAG, "Connection complete for connection handle 0x%x", connection_complete_event->Connection_Handle); + FURI_LOG_I(TAG, "Connection complete for connection handle 0x%x", connection_complete_event->Connection_Handle); // Stop advertising as connection completed osTimerStop(gap->advertise_timer); @@ -142,7 +141,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_gap_pairing_complete_event_rp0 *pairing_complete; case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: - FURI_LOG_I(GAP_TAG, "Limited discoverable event"); + FURI_LOG_I(TAG, "Limited discoverable event"); break; case EVT_BLUE_GAP_PASS_KEY_REQUEST: @@ -150,39 +149,39 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) // Generate random PIN code uint32_t pin = rand() % 999999; aci_gap_pass_key_resp(gap->gap_svc.connection_handle, pin); - FURI_LOG_I(GAP_TAG, "Pass key request event. Pin: %d", pin); + FURI_LOG_I(TAG, "Pass key request event. Pin: %d", pin); BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); } break; case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: - FURI_LOG_I(GAP_TAG, "Authorization request event"); + FURI_LOG_I(TAG, "Authorization request event"); break; case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: - FURI_LOG_I(GAP_TAG, "Slave security initiated"); + FURI_LOG_I(TAG, "Slave security initiated"); break; case EVT_BLUE_GAP_BOND_LOST: - FURI_LOG_I(GAP_TAG, "Bond lost event. Start rebonding"); + FURI_LOG_I(TAG, "Bond lost event. Start rebonding"); aci_gap_allow_rebond(gap->gap_svc.connection_handle); break; case EVT_BLUE_GAP_DEVICE_FOUND: - FURI_LOG_I(GAP_TAG, "Device found event"); + FURI_LOG_I(TAG, "Device found event"); break; case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: - FURI_LOG_I(GAP_TAG, "Address not resolved event"); + FURI_LOG_I(TAG, "Address not resolved event"); break; case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: - FURI_LOG_I(GAP_TAG, "Key press notification event"); + FURI_LOG_I(TAG, "Key press notification event"); break; case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: - FURI_LOG_I(GAP_TAG, "Hex_value = %lx", + FURI_LOG_I(TAG, "Hex_value = %lx", ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); aci_gap_numeric_comparison_value_confirm_yesno(gap->gap_svc.connection_handle, 1); break; @@ -190,17 +189,17 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) case EVT_BLUE_GAP_PAIRING_CMPLT: pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; if (pairing_complete->Status) { - FURI_LOG_E(GAP_TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); + FURI_LOG_E(TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); aci_gap_terminate(gap->gap_svc.connection_handle, 5); } else { - FURI_LOG_I(GAP_TAG, "Pairing complete"); + FURI_LOG_I(TAG, "Pairing complete"); BleEvent event = {.type = BleEventTypeConnected}; gap->on_event_cb(event, gap->context); } break; case EVT_BLUE_GAP_PROCEDURE_COMPLETE: - FURI_LOG_I(GAP_TAG, "Procedure complete event"); + FURI_LOG_I(TAG, "Procedure complete event"); break; } default: @@ -287,11 +286,11 @@ static void gap_init_svc(Gap* gap) { // Set GAP characteristics status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); if (status) { - FURI_LOG_E(GAP_TAG, "Failed updating name characteristic: %d", status); + FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); } status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.appearance_char_handle, 0, 2, gap_appearence_char_uuid); if(status) { - FURI_LOG_E(GAP_TAG, "Failed updating appearence characteristic: %d", status); + FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); } // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); @@ -323,7 +322,7 @@ static void gap_advertise_start(GapState new_state) // Stop advertising status = aci_gap_set_non_discoverable(); if (status) { - FURI_LOG_E(GAP_TAG, "Stop Advertising Failed, result: %d", status); + FURI_LOG_E(TAG, "Stop Advertising Failed, result: %d", status); } } // Configure advertising @@ -332,7 +331,7 @@ static void gap_advertise_start(GapState new_state) strlen(name), (uint8_t*)name, gap->gap_svc.adv_svc_uuid_len, gap->gap_svc.adv_svc_uuid, 0, 0); if(status) { - FURI_LOG_E(GAP_TAG, "Set discoverable err: %d", status); + FURI_LOG_E(TAG, "Set discoverable err: %d", status); } gap->state = new_state; BleEvent event = {.type = BleEventTypeStartAdvertising}; @@ -356,14 +355,14 @@ static void gap_advertise_stop() { } void gap_start_advertising() { - FURI_LOG_I(GAP_TAG, "Start advertising"); + FURI_LOG_I(TAG, "Start advertising"); gap->enable_adv = true; GapCommand command = GapCommandAdvFast; furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); } void gap_stop_advertising() { - FURI_LOG_I(GAP_TAG, "Stop advertising"); + FURI_LOG_I(TAG, "Stop advertising"); gap->enable_adv = false; GapCommand command = GapCommandAdvStop; furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); @@ -375,7 +374,7 @@ static void gap_advetise_timer_callback(void* context) { } bool gap_init(BleEventCallback on_event_cb, void* context) { - if (APPE_Status() != BleGlueStatusStarted) { + if (!ble_glue_is_radio_stack_ready()) { return false; } @@ -394,7 +393,7 @@ bool gap_init(BleEventCallback on_event_cb, void* context) { gap->enable_adv = true; // Thread configuration - gap->thread_attr.name = "BLE advertising"; + gap->thread_attr.name = "BleGapWorker"; gap->thread_attr.stack_size = 1024; gap->thread_id = osThreadNew(gap_app, NULL, &gap->thread_attr); diff --git a/firmware/targets/f6/ble-glue/hw_conf.h b/firmware/targets/f6/ble-glue/hw_conf.h index dcda0176..9545238b 100644 --- a/firmware/targets/f6/ble-glue/hw_conf.h +++ b/firmware/targets/f6/ble-glue/hw_conf.h @@ -29,6 +29,37 @@ * Semaphores * THIS SHALL NO BE CHANGED AS THESE SEMAPHORES ARE USED AS WELL ON THE CM0+ *****************************************************************************/ +/** +* Index of the semaphore used the prevent conflicts after standby sleep. +* Each CPUs takes this semaphore at standby wakeup until conclicting elements are restored. +*/ +#define CFG_HW_PWR_STANDBY_SEMID 10 +/** +* The CPU2 may be configured to store the Thread persistent data either in internal NVM storage on CPU2 or in +* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() +* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. +* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: +* + CPU1 takes CFG_HW_THREAD_NVM_SRAM_SEMID semaphore +* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) +* + CPU1 releases CFG_HW_THREAD_NVM_SRAM_SEMID semaphore +* CFG_HW_THREAD_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. +* There is no timing constraint on how long this semaphore can be kept. +*/ +#define CFG_HW_THREAD_NVM_SRAM_SEMID 9 + +/** +* The CPU2 may be configured to store the BLE persistent data either in internal NVM storage on CPU2 or in +* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() +* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. +* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: +* + CPU1 takes CFG_HW_BLE_NVM_SRAM_SEMID semaphore +* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) +* + CPU1 releases CFG_HW_BLE_NVM_SRAM_SEMID semaphore +* CFG_HW_BLE_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. +* There is no timing constraint on how long this semaphore can be kept. +*/ +#define CFG_HW_BLE_NVM_SRAM_SEMID 8 + /** * Index of the semaphore used by CPU2 to prevent the CPU1 to either write or erase data in flash * The CPU1 shall not either write or erase in flash when this semaphore is taken by the CPU2 diff --git a/firmware/targets/f6/ble-glue/serial_service.c b/firmware/targets/f6/ble-glue/serial_service.c index 7de3529c..c7ea6db2 100644 --- a/firmware/targets/f6/ble-glue/serial_service.c +++ b/firmware/targets/f6/ble-glue/serial_service.c @@ -4,22 +4,27 @@ #include -#define SERIAL_SERVICE_TAG "serial service" +#define TAG "BtSerialSvc" typedef struct { uint16_t svc_handle; uint16_t rx_char_handle; uint16_t tx_char_handle; + uint16_t flow_ctrl_char_handle; + osMutexId_t buff_size_mtx; + uint32_t buff_size; + uint16_t bytes_ready_to_receive; SerialSvcDataReceivedCallback on_received_cb; SerialSvcDataSentCallback on_sent_cb; void* context; } SerialSvc; -static SerialSvc* serial_svc; +static SerialSvc* serial_svc = NULL; static const uint8_t service_uuid[] = {0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f}; -static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; static const uint8_t char_tx_uuid[] = {0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; +static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; +static const uint8_t flow_ctrl_uuid[] = {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; @@ -32,16 +37,26 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 2) { // Descriptor handle ret = SVCCTL_EvtAckFlowEnable; - FURI_LOG_D(SERIAL_SERVICE_TAG, "RX descriptor event"); + FURI_LOG_D(TAG, "RX descriptor event"); } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) { - FURI_LOG_D(SERIAL_SERVICE_TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); + FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); if(serial_svc->on_received_cb) { - serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context); + furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK); + if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) { + FURI_LOG_W( + TAG, "Received %d, while was ready to receive %d bytes. Can lead to buffer overflow!", + attribute_modified->Attr_Data_Length, serial_svc->bytes_ready_to_receive); + } + serial_svc->bytes_ready_to_receive -= MIN(serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length); + uint32_t buff_free_size = + serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context); + FURI_LOG_D(TAG, "Available buff size: %d", buff_free_size); + furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK); } ret = SVCCTL_EvtAckFlowEnable; } } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { - FURI_LOG_D(SERIAL_SERVICE_TAG, "Ack received", blecore_evt->ecode); + FURI_LOG_D(TAG, "Ack received", blecore_evt->ecode); if(serial_svc->on_sent_cb) { serial_svc->on_sent_cb(serial_svc->context); } @@ -58,9 +73,9 @@ void serial_svc_start() { SVCCTL_RegisterSvcHandler(serial_svc_event_handler); // Add service - status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 6, &serial_svc->svc_handle); + status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 10, &serial_svc->svc_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Serial service: %d", status); + FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); } // Add RX characteristics @@ -73,12 +88,12 @@ void serial_svc_start() { CHAR_VALUE_LEN_VARIABLE, &serial_svc->rx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add RX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to add RX characteristic: %d", status); } // Add TX characteristic status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)char_tx_uuid, - SERIAL_SVC_DATA_LEN_MAX, + SERIAL_SVC_DATA_LEN_MAX, CHAR_PROP_READ | CHAR_PROP_INDICATE, ATTR_PERMISSION_AUTHEN_READ, GATT_DONT_NOTIFY_EVENTS, @@ -86,14 +101,47 @@ void serial_svc_start() { CHAR_VALUE_LEN_VARIABLE, &serial_svc->tx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add TX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to add TX characteristic: %d", status); } + // Add Flow Control characteristic + status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)flow_ctrl_uuid, + sizeof(uint32_t), + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_AUTHEN_READ, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_CONSTANT, + &serial_svc->flow_ctrl_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add Flow Control characteristic: %d", status); + } + // Allocate buffer size mutex + serial_svc->buff_size_mtx = osMutexNew(NULL); } -void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { +void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { + furi_assert(serial_svc); serial_svc->on_received_cb = on_received_cb; serial_svc->on_sent_cb = on_sent_cb; serial_svc->context = context; + serial_svc->buff_size = buff_size; + serial_svc->bytes_ready_to_receive = buff_size; + uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); + aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed); +} + +void serial_svc_notify_buffer_is_empty() { + furi_assert(serial_svc); + furi_assert(serial_svc->buff_size_mtx); + + furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK); + if(serial_svc->bytes_ready_to_receive == 0) { + FURI_LOG_D(TAG, "Buffer is empty. Notifying client"); + serial_svc->bytes_ready_to_receive = serial_svc->buff_size; + uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); + aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed); + } + furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK); } void serial_svc_stop() { @@ -102,17 +150,23 @@ void serial_svc_stop() { // Delete characteristics status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->tx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete TX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to delete TX characteristic: %d", status); } status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete RX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to delete RX characteristic: %d", status); + } + status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Flow Control characteristic: %d", status); } // Delete service status = aci_gatt_del_service(serial_svc->svc_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Serial service: %d", status); + FURI_LOG_E(TAG, "Failed to delete Serial service: %d", status); } + // Delete buffer size mutex + osMutexDelete(serial_svc->buff_size_mtx); free(serial_svc); serial_svc = NULL; } @@ -122,14 +176,13 @@ bool serial_svc_update_tx(uint8_t* data, uint8_t data_len) { if(data_len > SERIAL_SVC_DATA_LEN_MAX) { return false; } - FURI_LOG_D(SERIAL_SERVICE_TAG, "Updating char %d len", data_len); tBleStatus result = aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->tx_char_handle, 0, data_len, data); if(result) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed updating TX characteristic: %d", result); + FURI_LOG_E(TAG, "Failed updating TX characteristic: %d", result); } return result != BLE_STATUS_SUCCESS; } diff --git a/firmware/targets/f6/ble-glue/serial_service.h b/firmware/targets/f6/ble-glue/serial_service.h index b5a1c078..0aa4c79f 100644 --- a/firmware/targets/f6/ble-glue/serial_service.h +++ b/firmware/targets/f6/ble-glue/serial_service.h @@ -9,12 +9,14 @@ extern "C" { #endif -typedef void(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context); +typedef uint16_t(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context); typedef void(*SerialSvcDataSentCallback)(void* context); void serial_svc_start(); -void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); +void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); + +void serial_svc_notify_buffer_is_empty(); void serial_svc_stop(); diff --git a/firmware/targets/f6/Src/fatfs/fatfs.c b/firmware/targets/f6/fatfs/fatfs.c similarity index 100% rename from firmware/targets/f6/Src/fatfs/fatfs.c rename to firmware/targets/f6/fatfs/fatfs.c diff --git a/firmware/targets/f6/Src/fatfs/fatfs.h b/firmware/targets/f6/fatfs/fatfs.h similarity index 100% rename from firmware/targets/f6/Src/fatfs/fatfs.h rename to firmware/targets/f6/fatfs/fatfs.h diff --git a/firmware/targets/f6/Src/fatfs/ffconf.h b/firmware/targets/f6/fatfs/ffconf.h similarity index 100% rename from firmware/targets/f6/Src/fatfs/ffconf.h rename to firmware/targets/f6/fatfs/ffconf.h diff --git a/firmware/targets/f6/Src/fatfs/spi_sd_hal.c b/firmware/targets/f6/fatfs/spi_sd_hal.c similarity index 100% rename from firmware/targets/f6/Src/fatfs/spi_sd_hal.c rename to firmware/targets/f6/fatfs/spi_sd_hal.c diff --git a/firmware/targets/f6/Src/fatfs/stm32_adafruit_sd.c b/firmware/targets/f6/fatfs/stm32_adafruit_sd.c similarity index 100% rename from firmware/targets/f6/Src/fatfs/stm32_adafruit_sd.c rename to firmware/targets/f6/fatfs/stm32_adafruit_sd.c diff --git a/firmware/targets/f6/Src/fatfs/stm32_adafruit_sd.h b/firmware/targets/f6/fatfs/stm32_adafruit_sd.h similarity index 100% rename from firmware/targets/f6/Src/fatfs/stm32_adafruit_sd.h rename to firmware/targets/f6/fatfs/stm32_adafruit_sd.h diff --git a/firmware/targets/f6/Src/fatfs/syscall.c b/firmware/targets/f6/fatfs/syscall.c similarity index 100% rename from firmware/targets/f6/Src/fatfs/syscall.c rename to firmware/targets/f6/fatfs/syscall.c diff --git a/firmware/targets/f6/Src/fatfs/user_diskio.c b/firmware/targets/f6/fatfs/user_diskio.c similarity index 100% rename from firmware/targets/f6/Src/fatfs/user_diskio.c rename to firmware/targets/f6/fatfs/user_diskio.c diff --git a/firmware/targets/f6/Src/fatfs/user_diskio.h b/firmware/targets/f6/fatfs/user_diskio.h similarity index 100% rename from firmware/targets/f6/Src/fatfs/user_diskio.h rename to firmware/targets/f6/fatfs/user_diskio.h diff --git a/firmware/targets/f6/furi-hal/furi-hal-boot.c b/firmware/targets/f6/furi-hal/furi-hal-bootloader.c similarity index 57% rename from firmware/targets/f6/furi-hal/furi-hal-boot.c rename to firmware/targets/f6/furi-hal/furi-hal-bootloader.c index 978711c5..e8ea913e 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-boot.c +++ b/firmware/targets/f6/furi-hal/furi-hal-bootloader.c @@ -1,31 +1,33 @@ -#include +#include #include #include +#define TAG "FuriHalBoot" + // Boot request enum #define BOOT_REQUEST_TAINTED 0x00000000 #define BOOT_REQUEST_CLEAN 0xDADEDADE #define BOOT_REQUEST_DFU 0xDF00B000 -void furi_hal_boot_init() { +void furi_hal_bootloader_init() { #ifndef DEBUG LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_TAINTED); #endif - FURI_LOG_I("FuriHalBoot", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_boot_set_mode(FuriHalBootMode mode) { - if (mode == FuriHalBootModeNormal) { +void furi_hal_bootloader_set_mode(FuriHalBootloaderMode mode) { + if (mode == FuriHalBootloaderModeNormal) { LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_CLEAN); - } else if (mode == FuriHalBootModeDFU) { + } else if (mode == FuriHalBootloaderModeDFU) { LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_DFU); } } -void furi_hal_boot_set_flags(FuriHalBootFlag flags) { +void furi_hal_bootloader_set_flags(FuriHalBootloaderFlag flags) { LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR2, flags); } -FuriHalBootFlag furi_hal_boot_get_flags() { +FuriHalBootloaderFlag furi_hal_bootloader_get_flags() { return LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR2); } \ No newline at end of file diff --git a/firmware/targets/f6/furi-hal/furi-hal-bt.c b/firmware/targets/f6/furi-hal/furi-hal-bt.c index 97db87c7..b74a9e29 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-bt.c +++ b/firmware/targets/f6/furi-hal/furi-hal-bt.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -7,11 +6,42 @@ #include +#define TAG "FuriHalBt" + +osMutexId_t furi_hal_bt_core2_mtx = NULL; + void furi_hal_bt_init() { + furi_hal_bt_core2_mtx = osMutexNew(NULL); + furi_assert(furi_hal_bt_core2_mtx); + // Explicitly tell that we are in charge of CLK48 domain HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); - // Start Core2, init HCI and start GAP/GATT - APPE_Init(); + + // Start Core2 + ble_glue_init(); +} + +void furi_hal_bt_lock_core2() { + furi_assert(furi_hal_bt_core2_mtx); + furi_check(osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever) == osOK); +} + +void furi_hal_bt_unlock_core2() { + furi_assert(furi_hal_bt_core2_mtx); + furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); +} + +bool furi_hal_bt_start_core2() { + furi_assert(furi_hal_bt_core2_mtx); + + osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); + // Explicitly tell that we are in charge of CLK48 domain + HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + // Start Core2 + bool ret = ble_glue_start(); + osMutexRelease(furi_hal_bt_core2_mtx); + + return ret; } bool furi_hal_bt_init_app(BleEventCallback event_cb, void* context) { @@ -34,8 +64,12 @@ void furi_hal_bt_stop_advertising() { } } -void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { - serial_svc_set_callbacks(on_received_cb, on_sent_cb, context); +void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { + serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context); +} + +void furi_hal_bt_notify_buffer_is_empty() { + serial_svc_notify_buffer_is_empty(); } bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { @@ -45,9 +79,27 @@ bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { return serial_svc_update_tx(data, size); } +void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { + ble_app_get_key_storage_buff(key_buff_addr, key_buff_size); +} + +void furi_hal_bt_set_key_storage_change_callback(BleGlueKeyStorageChangedCallback callback, void* context) { + furi_assert(callback); + ble_glue_set_key_storage_changed_callback(callback, context); +} + +void furi_hal_bt_nvm_sram_sem_acquire() { + while(HAL_HSEM_FastTake(CFG_HW_BLE_NVM_SRAM_SEMID) != HAL_OK) { + osDelay(1); + } +} + +void furi_hal_bt_nvm_sram_sem_release() { + HAL_HSEM_Release(CFG_HW_BLE_NVM_SRAM_SEMID, 0); +} + void furi_hal_bt_dump_state(string_t buffer) { - BleGlueStatus status = APPE_Status(); - if (status == BleGlueStatusStarted) { + if (furi_hal_bt_is_alive()) { uint8_t HCI_Version; uint16_t HCI_Revision; uint8_t LMP_PAL_Version; @@ -68,58 +120,13 @@ void furi_hal_bt_dump_state(string_t buffer) { } bool furi_hal_bt_is_alive() { - BleGlueStatus status = APPE_Status(); - return (status == BleGlueStatusBroken) || (status == BleGlueStatusStarted); + return ble_glue_is_alive(); } bool furi_hal_bt_is_active() { return gap_get_state() > GapStateIdle; } -bool furi_hal_bt_wait_startup() { - uint16_t counter = 0; - while (!(APPE_Status() == BleGlueStatusStarted || APPE_Status() == BleGlueStatusBroken)) { - osDelay(10); - counter++; - if (counter > 1000) { - return false; - } - } - return true; -} - -bool furi_hal_bt_lock_flash(bool erase_flag) { - if (!furi_hal_bt_wait_startup()) { - return false; - } - - while (HAL_HSEM_FastTake(CFG_HW_FLASH_SEMID) != HAL_OK) { - osDelay(1); - } - - HAL_FLASH_Unlock(); - - if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); - - while(LL_FLASH_IsActiveFlag_OperationSuspended()) { - osDelay(1); - }; - - __disable_irq(); - - return true; -} - -void furi_hal_bt_unlock_flash(bool erase_flag) { - __enable_irq(); - - if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); - - HAL_FLASH_Lock(); - - HAL_HSEM_Release(CFG_HW_FLASH_SEMID, HSEM_CPU1_COREID); -} - void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) { aci_hal_set_tx_power_level(0, power); aci_hal_tone_start(channel, 0); diff --git a/firmware/targets/f6/furi-hal/furi-hal-clock.c b/firmware/targets/f6/furi-hal/furi-hal-clock.c index 2544c769..7a124049 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-clock.c +++ b/firmware/targets/f6/furi-hal/furi-hal-clock.c @@ -5,6 +5,8 @@ #include #include +#define TAG "FuriHalClock" + #define HS_CLOCK_IS_READY() (LL_RCC_HSE_IsReady() && LL_RCC_HSI_IsReady()) #define LS_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) @@ -84,6 +86,7 @@ void furi_hal_clock_init() { LL_RCC_EnableRTC(); LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK2); + LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_PLLSAI1); LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_CLK48); @@ -117,11 +120,12 @@ void furi_hal_clock_init() { // APB1 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); + LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPUART1); // APB2 LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1); - FURI_LOG_I("FuriHalClock", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_clock_switch_to_hsi() { diff --git a/firmware/targets/f6/furi-hal/furi-hal-compress.c b/firmware/targets/f6/furi-hal/furi-hal-compress.c index 9b7678f5..eb6e9d51 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-compress.c +++ b/firmware/targets/f6/furi-hal/furi-hal-compress.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalCompress" + #define FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE (512) #define FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE (1024) @@ -46,7 +48,7 @@ void furi_hal_compress_icon_init() { FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); heatshrink_decoder_reset(icon_decoder->decoder); memset(icon_decoder->decoded_buff, 0, sizeof(icon_decoder->decoded_buff)); - FURI_LOG_I("FuriHalCompress", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff) { diff --git a/firmware/targets/f6/furi-hal/furi-hal-console.c b/firmware/targets/f6/furi-hal/furi-hal-console.c index 552f9e77..993b498e 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-console.c +++ b/firmware/targets/f6/furi-hal/furi-hal-console.c @@ -1,131 +1,64 @@ #include -#include +#include #include #include #include #include +#include + #include +#define TAG "FuriHalConsole" + #define CONSOLE_BAUDRATE 230400 volatile bool furi_hal_console_alive = false; -static void (*irq_cb)(uint8_t ev, uint8_t data); - void furi_hal_console_init() { - LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = LL_GPIO_PIN_6|LL_GPIO_PIN_7; - GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; - GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - GPIO_InitStruct.Alternate = LL_GPIO_AF_7; - LL_GPIO_Init(GPIOB, &GPIO_InitStruct); - - LL_USART_InitTypeDef USART_InitStruct = {0}; - USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; - USART_InitStruct.BaudRate = CONSOLE_BAUDRATE; - USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; - USART_InitStruct.StopBits = LL_USART_STOPBITS_1; - USART_InitStruct.Parity = LL_USART_PARITY_NONE; - USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; - USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; - USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; - LL_USART_Init(USART1, &USART_InitStruct); - LL_USART_SetTXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_2); - LL_USART_EnableFIFO(USART1); - LL_USART_ConfigAsyncMode(USART1); - - LL_USART_Enable(USART1); - - while(!LL_USART_IsActiveFlag_TEACK(USART1)) ; - - LL_USART_EnableIT_RXNE_RXFNE(USART1); - LL_USART_EnableIT_IDLE(USART1); - HAL_NVIC_SetPriority(USART1_IRQn, 5, 0); - + furi_hal_uart_init(FuriHalUartIdUSART1, CONSOLE_BAUDRATE); furi_hal_console_alive = true; - FURI_LOG_I("FuriHalConsole", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_usart_init() { - furi_hal_console_alive = false; -} - -void furi_hal_usart_set_br(uint32_t baud) { - if (LL_USART_IsEnabled(USART1)) { - // Wait for transfer complete flag - while (!LL_USART_IsActiveFlag_TC(USART1)); - LL_USART_Disable(USART1); - uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); - LL_USART_SetBaudRate(USART1, uartclk, LL_USART_PRESCALER_DIV1, LL_USART_OVERSAMPLING_16, baud); - LL_USART_Enable(USART1); - } -} - -void furi_hal_usart_deinit() { +void furi_hal_console_enable() { + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL); while (!LL_USART_IsActiveFlag_TC(USART1)); - furi_hal_usart_set_br(CONSOLE_BAUDRATE); + furi_hal_uart_set_br(FuriHalUartIdUSART1, CONSOLE_BAUDRATE); furi_hal_console_alive = true; } -void furi_hal_usart_tx(const uint8_t* buffer, size_t buffer_size) { - if (LL_USART_IsEnabled(USART1) == 0) - return; - - while(buffer_size > 0) { - while (!LL_USART_IsActiveFlag_TXE(USART1)); - - LL_USART_TransmitData8(USART1, *buffer); - - buffer++; - buffer_size--; - } -} - -void furi_hal_usart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)) { - irq_cb = cb; - if (irq_cb == NULL) - NVIC_DisableIRQ(USART1_IRQn); - else - NVIC_EnableIRQ(USART1_IRQn); -} - -void USART1_IRQHandler(void) { - if (LL_USART_IsActiveFlag_RXNE_RXFNE(USART1)) { - uint8_t data = LL_USART_ReceiveData8(USART1); - irq_cb(UartIrqEventRXNE, data); - } else if (LL_USART_IsActiveFlag_IDLE(USART1)) { - irq_cb(UartIrqEventIDLE, 0); - LL_USART_ClearFlag_IDLE(USART1); - } - - //TODO: more events +void furi_hal_console_disable() { + while (!LL_USART_IsActiveFlag_TC(USART1)); + furi_hal_console_alive = false; } void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size) { if (!furi_hal_console_alive) return; + UTILS_ENTER_CRITICAL_SECTION(); // Transmit data - furi_hal_usart_tx(buffer, buffer_size); + furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size); // Wait for TC flag to be raised for last char while (!LL_USART_IsActiveFlag_TC(USART1)); + UTILS_EXIT_CRITICAL_SECTION(); } void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size) { if (!furi_hal_console_alive) return; + UTILS_ENTER_CRITICAL_SECTION(); // Transmit data - furi_hal_usart_tx(buffer, buffer_size); + furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size); // Transmit new line symbols - furi_hal_usart_tx((const uint8_t*)"\r\n", 2); + furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)"\r\n", 2); // Wait for TC flag to be raised for last char while (!LL_USART_IsActiveFlag_TC(USART1)); + UTILS_EXIT_CRITICAL_SECTION(); } void furi_hal_console_printf(const char format[], ...) { @@ -140,4 +73,4 @@ void furi_hal_console_printf(const char format[], ...) { void furi_hal_console_puts(const char *data) { furi_hal_console_tx((const uint8_t*)data, strlen(data)); -} \ No newline at end of file +} diff --git a/firmware/targets/f6/furi-hal/furi-hal-console.h b/firmware/targets/f6/furi-hal/furi-hal-console.h index 013653ba..637c17f6 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-console.h +++ b/firmware/targets/f6/furi-hal/furi-hal-console.h @@ -7,14 +7,12 @@ extern "C" { #endif -typedef enum { - UartIrqEventRXNE, - UartIrqEventIDLE, - //TODO: more events -} UartIrqEvent; - void furi_hal_console_init(); +void furi_hal_console_enable(); + +void furi_hal_console_disable(); + void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size); void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size); @@ -29,18 +27,6 @@ void furi_hal_console_printf(const char format[], ...); void furi_hal_console_puts(const char* data); - -void furi_hal_usart_init(); - -void furi_hal_usart_deinit(); - -void furi_hal_usart_set_br(uint32_t baud); - -void furi_hal_usart_tx(const uint8_t* buffer, size_t buffer_size); - -void furi_hal_usart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)); - - #ifdef __cplusplus } #endif diff --git a/firmware/targets/f6/furi-hal/furi-hal-crypto.c b/firmware/targets/f6/furi-hal/furi-hal-crypto.c index 3e4ec98f..91875d23 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-crypto.c +++ b/firmware/targets/f6/furi-hal/furi-hal-crypto.c @@ -1,35 +1,42 @@ #include +#include #include #include +#define TAG "FuriHalCrypto" + CRYP_HandleTypeDef crypt; void furi_hal_crypto_init() { - FURI_LOG_I("FuriHalCrypto", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot) { furi_assert(key); furi_assert(slot); + if(!furi_hal_bt_is_alive()) { + return false; + } + SHCI_C2_FUS_StoreUsrKey_Cmd_Param_t pParam; size_t key_data_size = 0; - if (key->type == FuriHalCryptoKeyTypeMaster) { + if(key->type == FuriHalCryptoKeyTypeMaster) { pParam.KeyType = KEYTYPE_MASTER; - } else if (key->type == FuriHalCryptoKeyTypeSimple) { + } else if(key->type == FuriHalCryptoKeyTypeSimple) { pParam.KeyType = KEYTYPE_SIMPLE; - } else if (key->type == FuriHalCryptoKeyTypeEncrypted) { + } else if(key->type == FuriHalCryptoKeyTypeEncrypted) { pParam.KeyType = KEYTYPE_ENCRYPTED; key_data_size += 12; } else { furi_crash("Incorrect key type"); } - if (key->size == FuriHalCryptoKeySize128) { + if(key->size == FuriHalCryptoKeySize128) { pParam.KeySize = KEYSIZE_16; key_data_size += 16; - } else if (key->size == FuriHalCryptoKeySize256) { + } else if(key->size == FuriHalCryptoKeySize256) { pParam.KeySize = KEYSIZE_32; key_data_size += 32; } else { @@ -44,16 +51,21 @@ bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot) { bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { furi_assert(slot > 0 && slot <= 100); + if(!furi_hal_bt_is_alive()) { + return false; + } + crypt.Instance = AES1; crypt.Init.DataType = CRYP_DATATYPE_32B; crypt.Init.KeySize = CRYP_KEYSIZE_256B; crypt.Init.Algorithm = CRYP_AES_CBC; crypt.Init.pInitVect = (uint32_t*)iv; + crypt.Init.KeyIVConfigSkip = CRYP_KEYIVCONFIG_ONCE; crypt.Init.pKey = NULL; furi_check(HAL_CRYP_Init(&crypt) == HAL_OK); - if (SHCI_C2_FUS_LoadUsrKey(slot) == SHCI_Success) { + if(SHCI_C2_FUS_LoadUsrKey(slot) == SHCI_Success) { return true; } else { furi_check(HAL_CRYP_DeInit(&crypt) == HAL_OK); @@ -62,14 +74,18 @@ bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { } bool furi_hal_crypto_store_unload_key(uint8_t slot) { + if(!furi_hal_bt_is_alive()) { + return false; + } + furi_check(HAL_CRYP_DeInit(&crypt) == HAL_OK); return SHCI_C2_FUS_UnloadUsrKey(slot) == SHCI_Success; } -bool furi_hal_crypto_encrypt(const uint8_t *input, uint8_t *output, size_t size) { - return HAL_CRYP_Encrypt(&crypt, (uint32_t*)input, size/4, (uint32_t*)output, 1000) == HAL_OK; +bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) { + return HAL_CRYP_Encrypt(&crypt, (uint32_t*)input, size / 4, (uint32_t*)output, 1000) == HAL_OK; } -bool furi_hal_crypto_decrypt(const uint8_t *input, uint8_t *output, size_t size) { - return HAL_CRYP_Decrypt(&crypt, (uint32_t*)input, size/4, (uint32_t*)output, 1000) == HAL_OK; +bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) { + return HAL_CRYP_Decrypt(&crypt, (uint32_t*)input, size / 4, (uint32_t*)output, 1000) == HAL_OK; } diff --git a/firmware/targets/f6/furi-hal/furi-hal-delay.c b/firmware/targets/f6/furi-hal/furi-hal-delay.c index 52de8715..b5f3c334 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-delay.c +++ b/firmware/targets/f6/furi-hal/furi-hal-delay.c @@ -3,6 +3,8 @@ #include #include +#define TAG "FuriHalDelay" + static uint32_t clk_per_microsecond; void furi_hal_delay_init(void) { @@ -10,7 +12,7 @@ void furi_hal_delay_init(void) { DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0U; clk_per_microsecond = SystemCoreClock / 1000000.0f; - FURI_LOG_I("FuriHalDelay", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void delay_us(float microseconds) { diff --git a/firmware/targets/f6/furi-hal/furi-hal-flash.c b/firmware/targets/f6/furi-hal/furi-hal-flash.c index c9922122..156a26a9 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-flash.c +++ b/firmware/targets/f6/furi-hal/furi-hal-flash.c @@ -1,16 +1,21 @@ #include #include -#include #include +#include +#include -/* Free flash space borders, exported by linker */ -extern const void __free_flash_start__; +#include +#define FURI_HAL_TAG "FuriHalFlash" +#define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" #define FURI_HAL_FLASH_READ_BLOCK 8 #define FURI_HAL_FLASH_WRITE_BLOCK 8 #define FURI_HAL_FLASH_PAGE_SIZE 4096 #define FURI_HAL_FLASH_CYCLES_COUNT 10000 +/* Free flash space borders, exported by linker */ +extern const void __free_flash_start__; + size_t furi_hal_flash_get_base() { return FLASH_BASE; } @@ -36,9 +41,9 @@ const void* furi_hal_flash_get_free_start_address() { } const void* furi_hal_flash_get_free_end_address() { - FLASH_OBProgramInitTypeDef pOBInit; - HAL_FLASHEx_OBGetConfig(&pOBInit); - return (const void *)pOBInit.SecureFlashStartAddr; + uint32_t sfr_reg_val = READ_REG(FLASH->SFR); + uint32_t sfsa = (READ_BIT(sfr_reg_val, FLASH_SFR_SFSA) >> FLASH_SFR_SFSA_Pos); + return (const void *)((sfsa * FLASH_PAGE_SIZE) + FLASH_BASE); } size_t furi_hal_flash_get_free_page_start_address() { @@ -56,34 +61,239 @@ size_t furi_hal_flash_get_free_page_count() { return (end-page_start) / FURI_HAL_FLASH_PAGE_SIZE; } -bool furi_hal_flash_erase(uint8_t page, uint8_t count) { - if (!furi_hal_bt_lock_flash(true)) { - return false; +static void furi_hal_flash_unlock() { + /* verify Flash is locked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U); + + /* Authorize the FLASH Registers access */ + WRITE_REG(FLASH->KEYR, FLASH_KEY1); + WRITE_REG(FLASH->KEYR, FLASH_KEY2); + + /* verify Flash is unlock */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); +} + +static void furi_hal_flash_lock(void) { + /* verify Flash is unlocked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); + + /* Set the LOCK Bit to lock the FLASH Registers access */ + /* @Note The lock and unlock procedure is done only using CR registers even from CPU2 */ + SET_BIT(FLASH->CR, FLASH_CR_LOCK); + + /* verify Flash is locked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U); +} + +static void furi_hal_flash_begin_with_core2(bool erase_flag) { + // Take flash controller ownership + while (HAL_HSEM_FastTake(CFG_HW_FLASH_SEMID) != HAL_OK) { + taskYIELD(); } - FLASH_EraseInitTypeDef erase; - erase.TypeErase = FLASH_TYPEERASE_PAGES; - erase.Page = page; - erase.NbPages = count; - uint32_t error; - HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase, &error); - furi_hal_bt_unlock_flash(true); - return status == HAL_OK; + + // Unlock flash operation + furi_hal_flash_unlock(); + + // Erase activity notification + if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); + + while(true) { + // Wait till flash controller become usable + while(LL_FLASH_IsActiveFlag_OperationSuspended()) { + taskYIELD(); + }; + + // Just a little more love + taskENTER_CRITICAL(); + + // Actually we already have mutex for it, but specification is specification + if (HAL_HSEM_IsSemTaken(CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) { + taskEXIT_CRITICAL(); + continue; + } + + // Take sempahopre and prevent core2 from anyting funky + if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { + taskEXIT_CRITICAL(); + continue; + } + + break; + } +} + +static void furi_hal_flash_begin(bool erase_flag) { + // Acquire dangerous ops mutex + furi_hal_bt_lock_core2(); + + // If Core2 is running use IPC locking + if(furi_hal_bt_is_alive()) { + furi_hal_flash_begin_with_core2(erase_flag); + } else { + furi_hal_flash_unlock(); + } +} + +static void furi_hal_flash_end_with_core2(bool erase_flag) { + // Funky ops are ok at this point + HAL_HSEM_Release(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0); + + // Task switching is ok + taskEXIT_CRITICAL(); + + // Doesn't make much sense, does it? + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) { + taskYIELD(); + } + + // Erase activity over, core2 can continue + if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); + + // Lock flash controller + furi_hal_flash_lock(); + + // Release flash controller ownership + HAL_HSEM_Release(CFG_HW_FLASH_SEMID, 0); +} + +static void furi_hal_flash_end(bool erase_flag) { + // If Core2 is running use IPC locking + if(furi_hal_bt_is_alive()) { + furi_hal_flash_end_with_core2(erase_flag); + } else { + furi_hal_flash_lock(); + } + + // Release dangerous ops mutex + furi_hal_bt_unlock_core2(); +} + +static void furi_hal_flush_cache(void) { + /* Flush instruction cache */ + if (READ_BIT(FLASH->ACR, FLASH_ACR_ICEN) == FLASH_ACR_ICEN) { + /* Disable instruction cache */ + __HAL_FLASH_INSTRUCTION_CACHE_DISABLE(); + /* Reset instruction cache */ + __HAL_FLASH_INSTRUCTION_CACHE_RESET(); + /* Enable instruction cache */ + __HAL_FLASH_INSTRUCTION_CACHE_ENABLE(); + } + + /* Flush data cache */ + if (READ_BIT(FLASH->ACR, FLASH_ACR_DCEN) == FLASH_ACR_DCEN) { + /* Disable data cache */ + __HAL_FLASH_DATA_CACHE_DISABLE(); + /* Reset data cache */ + __HAL_FLASH_DATA_CACHE_RESET(); + /* Enable data cache */ + __HAL_FLASH_DATA_CACHE_ENABLE(); + } +} + +HAL_StatusTypeDef furi_hal_flash_wait_last_operation(uint32_t timeout) { + uint32_t error = 0; + uint32_t countdown = 0; + + // Wait for the FLASH operation to complete by polling on BUSY flag to be reset. + // Even if the FLASH operation fails, the BUSY flag will be reset and an error + // flag will be set + countdown = timeout; + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) { + if(LL_SYSTICK_IsActiveCounterFlag()) { + countdown--; + } + if (countdown == 0) { + return HAL_TIMEOUT; + } + } + + /* Check FLASH operation error flags */ + error = FLASH->SR; + + /* Check FLASH End of Operation flag */ + if ((error & FLASH_FLAG_EOP) != 0U) { + /* Clear FLASH End of Operation pending bit */ + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP); + } + + /* Now update error variable to only error value */ + error &= FLASH_FLAG_SR_ERRORS; + + furi_check(error == 0); + + /* clear error flags */ + __HAL_FLASH_CLEAR_FLAG(error); + + /* Wait for control register to be written */ + countdown = timeout; + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_CFGBSY)) { + if(LL_SYSTICK_IsActiveCounterFlag()) { + countdown--; + } + if (countdown == 0) { + return HAL_TIMEOUT; + } + } + + return HAL_OK; +} + +bool furi_hal_flash_erase(uint8_t page) { + furi_hal_flash_begin(true); + + // Ensure that controller state is valid + furi_check(FLASH->SR == 0); + + /* Verify that next operation can be proceed */ + furi_check(furi_hal_flash_wait_last_operation(FLASH_TIMEOUT_VALUE) == HAL_OK); + + /* Select page and start operation */ + MODIFY_REG(FLASH->CR, FLASH_CR_PNB, ((page << FLASH_CR_PNB_Pos) | FLASH_CR_PER | FLASH_CR_STRT)); + + /* Wait for last operation to be completed */ + furi_check(furi_hal_flash_wait_last_operation(FLASH_TIMEOUT_VALUE) == HAL_OK); + + /* If operation is completed or interrupted, disable the Page Erase Bit */ + CLEAR_BIT(FLASH->CR, (FLASH_CR_PER | FLASH_CR_PNB)); + + /* Flush the caches to be sure of the data consistency */ + furi_hal_flush_cache(); + + furi_hal_flash_end(true); + + return true; } bool furi_hal_flash_write_dword(size_t address, uint64_t data) { - if (!furi_hal_bt_lock_flash(false)) { - return false; - } - HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data); - furi_hal_bt_unlock_flash(false); - return status == HAL_OK; -} + furi_hal_flash_begin(false); -bool furi_hal_flash_write_dword_from(size_t address, size_t source_address) { - if (!furi_hal_bt_lock_flash(false)) { - return false; - } - HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, address, source_address); - furi_hal_bt_unlock_flash(false); - return status == HAL_OK; + // Ensure that controller state is valid + furi_check(FLASH->SR == 0); + + /* Check the parameters */ + furi_check(IS_ADDR_ALIGNED_64BITS(address)); + furi_check(IS_FLASH_PROGRAM_ADDRESS(address)); + + /* Set PG bit */ + SET_BIT(FLASH->CR, FLASH_CR_PG); + + /* Program first word */ + *(uint32_t *)address = (uint32_t)data; + + // Barrier to ensure programming is performed in 2 steps, in right order + // (independently of compiler optimization behavior) + __ISB(); + + /* Program second word */ + *(uint32_t *)(address + 4U) = (uint32_t)(data >> 32U); + + /* Wait for last operation to be completed */ + furi_check(furi_hal_flash_wait_last_operation(FLASH_TIMEOUT_VALUE) == HAL_OK); + + /* If the program operation is completed, disable the PG or FSTPG Bit */ + CLEAR_BIT(FLASH->CR, FLASH_CR_PG); + + furi_hal_flash_end(false); + + return true; } diff --git a/firmware/targets/f6/furi-hal/furi-hal-flash.h b/firmware/targets/f6/furi-hal/furi-hal-flash.h index 583d53eb..3d8031e6 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-flash.h +++ b/firmware/targets/f6/furi-hal/furi-hal-flash.h @@ -60,18 +60,17 @@ size_t furi_hal_flash_get_free_page_count(); /** Erase Flash * - * Locking operation, uses HSEM to manage shared access. + * @warning locking operation with critical section, stales execution * - * @param page page number - * @param count page count to erase + * @param page The page to erase * * @return true on success */ -bool furi_hal_flash_erase(uint8_t page, uint8_t count); +bool furi_hal_flash_erase(uint8_t page); /** Write double word (64 bits) * - * Locking operation, uses HSEM to manage shared access. + * @warning locking operation with critical section, stales execution * * @param address destination address, must be double word aligned. * @param data data to write @@ -80,13 +79,3 @@ bool furi_hal_flash_erase(uint8_t page, uint8_t count); */ bool furi_hal_flash_write_dword(size_t address, uint64_t data); -/** Write double word (64 bits) from address - * - * Locking operation, uses HSEM to manage shared access. - * - * @param address destination address, must be block aligned - * @param source_address source address - * - * @return true on success - */ -bool furi_hal_flash_write_dword_from(size_t address, size_t source_address); diff --git a/firmware/targets/f6/furi-hal/furi-hal-i2c.c b/firmware/targets/f6/furi-hal/furi-hal-i2c.c index 196a2a7b..b1ec4711 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-i2c.c +++ b/firmware/targets/f6/furi-hal/furi-hal-i2c.c @@ -6,6 +6,8 @@ #include #include +#define TAG "FuriHalI2C" + osMutexId_t furi_hal_i2c_mutex = NULL; void furi_hal_i2c_init() { @@ -42,7 +44,7 @@ void furi_hal_i2c_init() { LL_I2C_DisableOwnAddress2(POWER_I2C); LL_I2C_DisableGeneralCall(POWER_I2C); LL_I2C_EnableClockStretching(POWER_I2C); - FURI_LOG_I("FuriHalI2C", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } bool furi_hal_i2c_tx( diff --git a/firmware/targets/f6/furi-hal/furi-hal-interrupt.c b/firmware/targets/f6/furi-hal/furi-hal-interrupt.c index 47e99c9f..2685edab 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-interrupt.c +++ b/firmware/targets/f6/furi-hal/furi-hal-interrupt.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalInterrupt" + volatile FuriHalInterruptISR furi_hal_tim_tim2_isr = NULL; volatile FuriHalInterruptISR furi_hal_tim_tim1_isr = NULL; @@ -22,7 +24,7 @@ void furi_hal_interrupt_init() { NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(DMA1_Channel1_IRQn); - FURI_LOG_I("FuriHalInterrupt", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_interrupt_set_timer_isr(TIM_TypeDef* timer, FuriHalInterruptISR isr) { @@ -161,10 +163,10 @@ void TAMP_STAMP_LSECSS_IRQHandler(void) { if (LL_RCC_IsActiveFlag_LSECSS()) { LL_RCC_ClearFlag_LSECSS(); if (!LL_RCC_LSE_IsReady()) { - FURI_LOG_E("FuriHalInterrupt", "LSE CSS fired: resetting system"); + FURI_LOG_E(TAG, "LSE CSS fired: resetting system"); NVIC_SystemReset(); } else { - FURI_LOG_E("FuriHalInterrupt", "LSE CSS fired: but LSE is alive"); + FURI_LOG_E(TAG, "LSE CSS fired: but LSE is alive"); } } } @@ -176,7 +178,7 @@ void RCC_IRQHandler(void) { void NMI_Handler(void) { if (LL_RCC_IsActiveFlag_HSECSS()) { LL_RCC_ClearFlag_HSECSS(); - FURI_LOG_E("FuriHalInterrupt", "HSE CSS fired: resetting system"); + FURI_LOG_E(TAG, "HSE CSS fired: resetting system"); NVIC_SystemReset(); } } diff --git a/firmware/targets/f6/furi-hal/furi-hal-light.c b/firmware/targets/f6/furi-hal/furi-hal-light.c index fba1bec4..ecf0d4f2 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-light.c +++ b/firmware/targets/f6/furi-hal/furi-hal-light.c @@ -1,6 +1,8 @@ #include #include +#define TAG "FuriHalLight" + #define LED_CURRENT_RED 50 #define LED_CURRENT_GREEN 50 #define LED_CURRENT_BLUE 50 @@ -21,7 +23,7 @@ void furi_hal_light_init() { lp5562_enable(); lp5562_configure(); - FURI_LOG_I("FuriHalLight", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_light_set(Light light, uint8_t value) { diff --git a/firmware/targets/f6/furi-hal/furi-hal-lock.c b/firmware/targets/f6/furi-hal/furi-hal-lock.c new file mode 100644 index 00000000..0f519380 --- /dev/null +++ b/firmware/targets/f6/furi-hal/furi-hal-lock.c @@ -0,0 +1,17 @@ +#include "furi-hal-lock.h" +#include + +#define FLIPPER_LOCKED_VALUE 0x5432FAFA + +bool furi_hal_lock_get() { + return FLIPPER_LOCKED_VALUE == LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR3); +} + +void furi_hal_lock_set(bool locked) { + if (locked) { + LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR3, FLIPPER_LOCKED_VALUE); + } else { + LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR3, 0); + } +} + diff --git a/firmware/targets/f6/furi-hal/furi-hal-lpuart.c b/firmware/targets/f6/furi-hal/furi-hal-lpuart.c deleted file mode 100644 index 31aa8b86..00000000 --- a/firmware/targets/f6/furi-hal/furi-hal-lpuart.c +++ /dev/null @@ -1,105 +0,0 @@ -#include -#include -#include -#include - -#include - -static void (*irq_cb)(uint8_t ev, uint8_t data); - -void furi_hal_lpuart_init() { - LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = PC0_Pin|PC1_Pin; - GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; - GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - GPIO_InitStruct.Alternate = LL_GPIO_AF_8; - LL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); - LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPUART1); - - LL_LPUART_InitTypeDef LPUART_InitStruct = {0}; - LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; - LPUART_InitStruct.BaudRate = 115200; - LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; - LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1; - LPUART_InitStruct.Parity = LL_LPUART_PARITY_NONE; - LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX; - LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE; - LL_LPUART_Init(LPUART1, &LPUART_InitStruct); - LL_LPUART_SetTXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); - LL_LPUART_SetRXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); - LL_LPUART_EnableFIFO(LPUART1); - - LL_LPUART_Enable(LPUART1); - - while((!(LL_LPUART_IsActiveFlag_TEACK(LPUART1))) || (!(LL_LPUART_IsActiveFlag_REACK(LPUART1)))); - - LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - LL_LPUART_EnableIT_IDLE(LPUART1); - HAL_NVIC_SetPriority(LPUART1_IRQn, 5, 0); - - FURI_LOG_I("FuriHalLpUart", "Init OK"); -} - -void furi_hal_lpuart_set_br(uint32_t baud) { - if (LL_LPUART_IsEnabled(LPUART1)) { - // Wait for transfer complete flag - while (!LL_LPUART_IsActiveFlag_TC(LPUART1)); - LL_LPUART_Disable(LPUART1); - uint32_t uartclk = LL_RCC_GetLPUARTClockFreq(LL_RCC_GetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1)); - if (uartclk/baud > 4095) { - LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV32); - LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV32, baud); - } else { - LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV1); - LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV1, baud); - } - - LL_LPUART_Enable(LPUART1); - } -} - -void furi_hal_lpuart_deinit() { - furi_hal_lpuart_set_irq_cb(NULL); - LL_GPIO_SetPinMode(GPIOC, PC0_Pin, LL_GPIO_MODE_ANALOG); - LL_GPIO_SetPinMode(GPIOC, PC1_Pin, LL_GPIO_MODE_ANALOG); - LL_LPUART_Disable(LPUART1); - LL_APB1_GRP2_DisableClock(LL_APB1_GRP2_PERIPH_LPUART1); -} - -void furi_hal_lpuart_tx(const uint8_t* buffer, size_t buffer_size) { - if (LL_LPUART_IsEnabled(LPUART1) == 0) - return; - - while(buffer_size > 0) { - while (!LL_LPUART_IsActiveFlag_TXE(LPUART1)); - - LL_LPUART_TransmitData8(LPUART1, *buffer); - - buffer++; - buffer_size--; - } -} - -void furi_hal_lpuart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)) { - irq_cb = cb; - if (irq_cb == NULL) - NVIC_DisableIRQ(LPUART1_IRQn); - else - NVIC_EnableIRQ(LPUART1_IRQn); -} - -void LPUART1_IRQHandler(void) { - if (LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1)) { - uint8_t data = LL_LPUART_ReceiveData8(LPUART1); - irq_cb(UartIrqEventRXNE, data); - } else if (LL_LPUART_IsActiveFlag_IDLE(LPUART1)) { - irq_cb(UartIrqEventIDLE, 0); - LL_LPUART_ClearFlag_IDLE(LPUART1); - } - - //TODO: more events -} diff --git a/firmware/targets/f6/furi-hal/furi-hal-lpuart.h b/firmware/targets/f6/furi-hal/furi-hal-lpuart.h deleted file mode 100644 index 118a9a9c..00000000 --- a/firmware/targets/f6/furi-hal/furi-hal-lpuart.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include "furi-hal-console.h" - -#ifdef __cplusplus -extern "C" { -#endif - - -void furi_hal_lpuart_init(); - -void furi_hal_lpuart_deinit(); - -void furi_hal_lpuart_set_br(uint32_t baud); - -void furi_hal_lpuart_tx(const uint8_t* buffer, size_t buffer_size); - -void furi_hal_lpuart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)); - -#ifdef __cplusplus -} -#endif diff --git a/firmware/targets/f6/furi-hal/furi-hal-nfc.c b/firmware/targets/f6/furi-hal/furi-hal-nfc.c index d92e2cad..4bca12a9 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-nfc.c +++ b/firmware/targets/f6/furi-hal/furi-hal-nfc.c @@ -1,15 +1,17 @@ #include "furi-hal-nfc.h" #include +#define TAG "FuriHalNfc" + static const uint32_t clocks_in_ms = 64 * 1000; void furi_hal_nfc_init() { ReturnCode ret = rfalNfcInitialize(); if(ret == ERR_NONE) { furi_hal_nfc_start_sleep(); - FURI_LOG_I("FuriHalNfc", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } else { - FURI_LOG_W("FuriHalNfc", "Initialization failed, RFAL returned: %d", ret); + FURI_LOG_W(TAG, "Initialization failed, RFAL returned: %d", ret); } } @@ -63,7 +65,7 @@ bool furi_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t ti while(state != RFAL_NFC_STATE_ACTIVATED) { rfalNfcWorker(); state = rfalNfcGetState(); - FURI_LOG_D("HAL NFC", "Current state %d", state); + FURI_LOG_D(TAG, "Current state %d", state); if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { start = DWT->CYCCNT; continue; @@ -73,7 +75,7 @@ bool furi_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t ti } if(DWT->CYCCNT - start > timeout * clocks_in_ms) { rfalNfcDeactivate(true); - FURI_LOG_D("HAL NFC", "Timeout"); + FURI_LOG_D(TAG, "Timeout"); return false; } osThreadYield(); diff --git a/firmware/targets/f6/furi-hal/furi-hal-os.c b/firmware/targets/f6/furi-hal/furi-hal-os.c index eb1811b6..aac1b10e 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-os.c +++ b/firmware/targets/f6/furi-hal/furi-hal-os.c @@ -5,6 +5,8 @@ #include +#define TAG "FuriHalOs" + #define FURI_HAL_OS_CLK_FREQUENCY 32768 #define FURI_HAL_OS_TICK_PER_SECOND 1024 #define FURI_HAL_OS_CLK_PER_TICK (FURI_HAL_OS_CLK_FREQUENCY / FURI_HAL_OS_TICK_PER_SECOND) @@ -44,7 +46,7 @@ void furi_hal_os_init() { osTimerStart(second_timer, 1024); #endif - FURI_LOG_I("FuriHalOs", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void LPTIM2_IRQHandler(void) { diff --git a/firmware/targets/f6/furi-hal/furi-hal-power.c b/firmware/targets/f6/furi-hal/furi-hal-power.c index 47ce5f4d..870cbda6 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-power.c +++ b/firmware/targets/f6/furi-hal/furi-hal-power.c @@ -15,6 +15,8 @@ #include +#define TAG "FuriHalPower" + typedef struct { volatile uint8_t insomnia; volatile uint8_t deep_insomnia; @@ -74,7 +76,7 @@ void furi_hal_power_init() { LL_PWR_SMPS_SetMode(LL_PWR_SMPS_STEP_DOWN); bq27220_init(&cedv); bq25896_init(); - FURI_LOG_I("FuriHalPower", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } uint16_t furi_hal_power_insomnia_level() { diff --git a/firmware/targets/f6/furi-hal/furi-hal-rfid.c b/firmware/targets/f6/furi-hal/furi-hal-rfid.c index a9bbc32c..d7092f88 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-rfid.c +++ b/firmware/targets/f6/furi-hal/furi-hal-rfid.c @@ -104,7 +104,7 @@ void furi_hal_rfid_tim_read(float freq, float duty_cycle) { sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; - if(HAL_TIM_OC_ConfigChannel(&LFRFID_READ_TIM, &sConfigOC, LFRFID_READ_CHANNEL) != HAL_OK) { + if(HAL_TIM_PWM_ConfigChannel(&LFRFID_READ_TIM, &sConfigOC, LFRFID_READ_CHANNEL) != HAL_OK) { Error_Handler(); } @@ -142,7 +142,6 @@ void furi_hal_rfid_tim_emulate(float freq) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; - TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; // basic PWM setup with needed freq and internal clock LFRFID_EMULATE_TIM.Init.Prescaler = prescaler; @@ -182,24 +181,6 @@ void furi_hal_rfid_tim_emulate(float freq) { HAL_OK) { Error_Handler(); } - - // no deadtime - sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; - sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; - sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; - sBreakDeadTimeConfig.DeadTime = 0; - sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; - sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; - sBreakDeadTimeConfig.BreakFilter = 0; - sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT; - sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE; - sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH; - sBreakDeadTimeConfig.Break2Filter = 0; - sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT; - sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; - if(HAL_TIMEx_ConfigBreakDeadTime(&LFRFID_EMULATE_TIM, &sBreakDeadTimeConfig) != HAL_OK) { - Error_Handler(); - } } void furi_hal_rfid_tim_emulate_start() { diff --git a/firmware/targets/f6/furi-hal/furi-hal-spi.c b/firmware/targets/f6/furi-hal/furi-hal-spi.c index c925abee..da7c63df 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-spi.c +++ b/firmware/targets/f6/furi-hal/furi-hal-spi.c @@ -9,6 +9,8 @@ #include #include +#define TAG "FuriHalSpi" + void furi_hal_spi_init() { // Spi structure is const, but mutex is not // Need some hell-ish casting to make it work @@ -33,7 +35,7 @@ void furi_hal_spi_init() { hal_gpio_init_ex(&gpio_spi_d_mosi, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn5SPI2); hal_gpio_init_ex(&gpio_spi_d_sck, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn5SPI2); - FURI_LOG_I("FuriHalSpi", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_spi_bus_lock(const FuriHalSpiBus* bus) { @@ -133,7 +135,7 @@ const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id) { furi_assert(device_id < FuriHalSpiDeviceIdMax); const FuriHalSpiDevice* device = &furi_hal_spi_devices[device_id]; - assert(device); + furi_assert(device); furi_hal_spi_bus_lock(device->bus); furi_hal_spi_device_configure(device); diff --git a/firmware/targets/f6/furi-hal/furi-hal-subghz.c b/firmware/targets/f6/furi-hal/furi-hal-subghz.c index 02b91277..276482ee 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-subghz.c +++ b/firmware/targets/f6/furi-hal/furi-hal-subghz.c @@ -10,6 +10,8 @@ #include #include +#define TAG "FuriHalSubGhz" + static volatile SubGhzState furi_hal_subghz_state = SubGhzStateInit; static volatile SubGhzRegulation furi_hal_subghz_regulation = SubGhzRegulationTxRx; @@ -303,7 +305,7 @@ void furi_hal_subghz_init() { cc1101_shutdown(device); furi_hal_spi_device_return(device); - FURI_LOG_I("FuriHalSubGhz", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_subghz_sleep() { @@ -658,6 +660,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { bool is_odd = samples % 2; LevelDuration ld = furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context); + if(level_duration_is_wait(ld)) return; if(level_duration_is_reset(ld)) { // One more even sample required to end at low level if(is_odd) { @@ -675,7 +678,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { } uint32_t duration = level_duration_get_duration(ld); - assert(duration > 0); + furi_assert(duration > 0); *buffer = duration; buffer++; samples--; diff --git a/firmware/targets/f6/furi-hal/furi-hal-uart.c b/firmware/targets/f6/furi-hal/furi-hal-uart.c new file mode 100644 index 00000000..ff5a056b --- /dev/null +++ b/firmware/targets/f6/furi-hal/furi-hal-uart.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include + +#include + +static void (*irq_cb[2])(uint8_t ev, uint8_t data); + +static void furi_hal_usart_init(uint32_t baud) { + hal_gpio_init_ex( + &gpio_usart_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + hal_gpio_init_ex( + &gpio_usart_rx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + + LL_USART_InitTypeDef USART_InitStruct = {0}; + USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; + USART_InitStruct.BaudRate = baud; + USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; + USART_InitStruct.StopBits = LL_USART_STOPBITS_1; + USART_InitStruct.Parity = LL_USART_PARITY_NONE; + USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; + USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; + USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; + LL_USART_Init(USART1, &USART_InitStruct); + LL_USART_SetTXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_2); + LL_USART_EnableFIFO(USART1); + LL_USART_ConfigAsyncMode(USART1); + + LL_USART_Enable(USART1); + + while(!LL_USART_IsActiveFlag_TEACK(USART1)); + + LL_USART_EnableIT_RXNE_RXFNE(USART1); + LL_USART_EnableIT_IDLE(USART1); + HAL_NVIC_SetPriority(USART1_IRQn, 5, 0); +} + +static void furi_hal_lpuart_init(uint32_t baud) { + hal_gpio_init_ex( + &gpio_ext_pc0, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn8LPUART1); + hal_gpio_init_ex( + &gpio_ext_pc1, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn8LPUART1); + + LL_LPUART_InitTypeDef LPUART_InitStruct = {0}; + LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; + LPUART_InitStruct.BaudRate = 115200; + LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; + LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1; + LPUART_InitStruct.Parity = LL_LPUART_PARITY_NONE; + LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX; + LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE; + LL_LPUART_Init(LPUART1, &LPUART_InitStruct); + LL_LPUART_SetTXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); + LL_LPUART_SetRXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); + LL_LPUART_EnableFIFO(LPUART1); + + LL_LPUART_Enable(LPUART1); + + while((!(LL_LPUART_IsActiveFlag_TEACK(LPUART1))) || (!(LL_LPUART_IsActiveFlag_REACK(LPUART1)))); + + furi_hal_uart_set_br(FuriHalUartIdLPUART1, baud); + + LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); + LL_LPUART_EnableIT_IDLE(LPUART1); + HAL_NVIC_SetPriority(LPUART1_IRQn, 5, 0); +} + +void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud) { + if (ch == FuriHalUartIdLPUART1) + furi_hal_lpuart_init(baud); + else if (ch == FuriHalUartIdUSART1) + furi_hal_usart_init(baud); +} + +void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud) { + if (ch == FuriHalUartIdUSART1) { + if (LL_USART_IsEnabled(USART1)) { + // Wait for transfer complete flag + while (!LL_USART_IsActiveFlag_TC(USART1)); + LL_USART_Disable(USART1); + uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); + LL_USART_SetBaudRate(USART1, uartclk, LL_USART_PRESCALER_DIV1, LL_USART_OVERSAMPLING_16, baud); + LL_USART_Enable(USART1); + } + } else if (ch == FuriHalUartIdLPUART1) { + if (LL_LPUART_IsEnabled(LPUART1)) { + // Wait for transfer complete flag + while (!LL_LPUART_IsActiveFlag_TC(LPUART1)); + LL_LPUART_Disable(LPUART1); + uint32_t uartclk = LL_RCC_GetLPUARTClockFreq(LL_RCC_LPUART1_CLKSOURCE); + if (uartclk/baud > 4095) { + LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV32); + LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV32, baud); + } else { + LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV1); + LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV1, baud); + } + LL_LPUART_Enable(LPUART1); + } + } +} + +void furi_hal_uart_deinit(FuriHalUartId ch) { + furi_hal_uart_set_irq_cb(ch, NULL); + if (ch == FuriHalUartIdUSART1) { + LL_USART_Disable(USART1); + hal_gpio_init(&gpio_usart_tx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + hal_gpio_init(&gpio_usart_rx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if (ch == FuriHalUartIdLPUART1) { + LL_LPUART_Disable(LPUART1); + hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } +} + +void furi_hal_uart_tx(FuriHalUartId ch, uint8_t* buffer, size_t buffer_size) { + if (ch == FuriHalUartIdUSART1) { + if (LL_USART_IsEnabled(USART1) == 0) + return; + + while(buffer_size > 0) { + while (!LL_USART_IsActiveFlag_TXE(USART1)); + + LL_USART_TransmitData8(USART1, *buffer); + buffer++; + buffer_size--; + } + + } else if (ch == FuriHalUartIdLPUART1) { + if (LL_LPUART_IsEnabled(LPUART1) == 0) + return; + + while(buffer_size > 0) { + while (!LL_LPUART_IsActiveFlag_TXE(LPUART1)); + + LL_LPUART_TransmitData8(LPUART1, *buffer); + + buffer++; + buffer_size--; + } + } +} + +void furi_hal_uart_set_irq_cb(FuriHalUartId ch, void (*cb)(UartIrqEvent ev, uint8_t data)) { + if (cb == NULL) { + if (ch == FuriHalUartIdUSART1) + NVIC_DisableIRQ(USART1_IRQn); + else if (ch == FuriHalUartIdLPUART1) + NVIC_DisableIRQ(LPUART1_IRQn); + irq_cb[ch] = cb; + } else { + irq_cb[ch] = cb; + if (ch == FuriHalUartIdUSART1) + NVIC_EnableIRQ(USART1_IRQn); + else if (ch == FuriHalUartIdLPUART1) + NVIC_EnableIRQ(LPUART1_IRQn); + } +} + +void LPUART1_IRQHandler(void) { + if (LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1)) { + uint8_t data = LL_LPUART_ReceiveData8(LPUART1); + irq_cb[FuriHalUartIdLPUART1](UartIrqEventRXNE, data); + } else if (LL_LPUART_IsActiveFlag_IDLE(LPUART1)) { + irq_cb[FuriHalUartIdLPUART1](UartIrqEventIDLE, 0); + LL_LPUART_ClearFlag_IDLE(LPUART1); + } else if (LL_LPUART_IsActiveFlag_ORE(LPUART1)) { + LL_LPUART_ClearFlag_ORE(LPUART1); + } + //TODO: more events +} + +void USART1_IRQHandler(void) { + if (LL_USART_IsActiveFlag_RXNE_RXFNE(USART1)) { + uint8_t data = LL_USART_ReceiveData8(USART1); + irq_cb[FuriHalUartIdUSART1](UartIrqEventRXNE, data); + } else if (LL_USART_IsActiveFlag_IDLE(USART1)) { + irq_cb[FuriHalUartIdUSART1](UartIrqEventIDLE, 0); + LL_USART_ClearFlag_IDLE(USART1); + } else if (LL_USART_IsActiveFlag_ORE(USART1)) { + LL_USART_ClearFlag_ORE(USART1); + } +} diff --git a/firmware/targets/f6/furi-hal/furi-hal-uart.h b/firmware/targets/f6/furi-hal/furi-hal-uart.h new file mode 100644 index 00000000..731a12a2 --- /dev/null +++ b/firmware/targets/f6/furi-hal/furi-hal-uart.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FuriHalUartIdUSART1, + FuriHalUartIdLPUART1, +} FuriHalUartId; + +typedef enum { + UartIrqEventRXNE, + UartIrqEventIDLE, + //TODO: more events +} UartIrqEvent; + +void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud); + +void furi_hal_uart_deinit(FuriHalUartId ch); + +void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud); + +void furi_hal_uart_tx(FuriHalUartId ch, uint8_t* buffer, size_t buffer_size); + +void furi_hal_uart_set_irq_cb(FuriHalUartId ch, void (*cb)(UartIrqEvent ev, uint8_t data)); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f6/furi-hal/furi-hal-usb-cdc.c b/firmware/targets/f6/furi-hal/furi-hal-usb-cdc.c index e643fe57..e1f5de7d 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-usb-cdc.c +++ b/firmware/targets/f6/furi-hal/furi-hal-usb-cdc.c @@ -345,6 +345,7 @@ static const struct CdcConfigDescriptorDual cdc_cfg_desc_dual = { }; static struct usb_cdc_line_coding cdc_config[IF_NUM_MAX] = {}; +static uint8_t cdc_ctrl_line_state[IF_NUM_MAX]; static void cdc_init(usbd_device* dev, struct UsbInterface* intf); static void cdc_deinit(usbd_device *dev); @@ -438,6 +439,12 @@ struct usb_cdc_line_coding* furi_hal_cdc_get_port_settings(uint8_t if_num) { return NULL; } +uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { + if (if_num < 2) + return cdc_ctrl_line_state[if_num]; + return 0; +} + void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { if (if_num == 0) usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); @@ -446,10 +453,12 @@ void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { } int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { + int32_t len = 0; if (if_num == 0) - return usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); + len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); else - return usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + return ((len < 0) ? 0 : len); } static void cdc_on_wakeup(usbd_device *dev) { @@ -463,6 +472,7 @@ static void cdc_on_wakeup(usbd_device *dev) { static void cdc_on_suspend(usbd_device *dev) { for (uint8_t i = 0; i < IF_NUM_MAX; i++) { + cdc_ctrl_line_state[i] = 0; if (callbacks[i] != NULL) { if (callbacks[i]->state_callback != NULL) callbacks[i]->state_callback(0); @@ -578,8 +588,9 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal switch(req->bRequest) { case USB_CDC_SET_CONTROL_LINE_STATE: if (callbacks[if_num] != NULL) { + cdc_ctrl_line_state[if_num] = req->wValue; if (callbacks[if_num]->ctrl_line_callback != NULL) - callbacks[if_num]->ctrl_line_callback(req->wValue); + callbacks[if_num]->ctrl_line_callback(cdc_ctrl_line_state[if_num]); } return usbd_ack; case USB_CDC_SET_LINE_CODING: diff --git a/firmware/targets/f6/furi-hal/furi-hal-usb-cdc_i.h b/firmware/targets/f6/furi-hal/furi-hal-usb-cdc_i.h index 3b181582..c4859e69 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-usb-cdc_i.h +++ b/firmware/targets/f6/furi-hal/furi-hal-usb-cdc_i.h @@ -17,6 +17,8 @@ void furi_hal_cdc_set_callbacks(uint8_t if_num, CdcCallbacks* cb); struct usb_cdc_line_coding* furi_hal_cdc_get_port_settings(uint8_t if_num); +uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num); + void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len); int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len); diff --git a/firmware/targets/f6/furi-hal/furi-hal-usb.c b/firmware/targets/f6/furi-hal/furi-hal-usb.c index 8c78eb8b..45a6177e 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-usb.c +++ b/firmware/targets/f6/furi-hal/furi-hal-usb.c @@ -5,6 +5,8 @@ #include "usb.h" +#define TAG "FuriHalUsb" + #define USB_RECONNECT_DELAY 500 extern struct UsbInterface usb_cdc_single; @@ -64,7 +66,7 @@ void furi_hal_usb_init(void) { HAL_NVIC_SetPriority(USB_LP_IRQn, 5, 0); NVIC_EnableIRQ(USB_LP_IRQn); - FURI_LOG_I("FuriHalUsb", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_usb_set_config(UsbMode new_mode) { @@ -81,7 +83,7 @@ void furi_hal_usb_set_config(UsbMode new_mode) { usb_if_modes[usb_config.mode_cur]->deinit(&udev); if (usb_if_modes[new_mode] != NULL) { usb_if_modes[new_mode]->init(&udev, usb_if_modes[new_mode]); - FURI_LOG_I("FuriHalUsb", "USB mode change %u -> %u", usb_config.mode_cur, new_mode); + FURI_LOG_I(TAG, "USB mode change %u -> %u", usb_config.mode_cur, new_mode); usb_config.enabled = true; usb_config.mode_cur = new_mode; } @@ -98,7 +100,7 @@ void furi_hal_usb_disable() { susp_evt(&udev, 0, 0); usbd_connect(&udev, false); usb_config.enabled = false; - FURI_LOG_I("FuriHalUsb", "USB Disable"); + FURI_LOG_I(TAG, "USB Disable"); } } @@ -106,7 +108,7 @@ void furi_hal_usb_enable() { if ((!usb_config.enabled) && (usb_if_modes[usb_config.mode_cur] != NULL)) { usbd_connect(&udev, true); usb_config.enabled = true; - FURI_LOG_I("FuriHalUsb", "USB Enable"); + FURI_LOG_I(TAG, "USB Enable"); } } diff --git a/firmware/targets/f6/furi-hal/furi-hal-vcp.c b/firmware/targets/f6/furi-hal/furi-hal-vcp.c index b975495d..039481f1 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-vcp.c +++ b/firmware/targets/f6/furi-hal/furi-hal-vcp.c @@ -1,22 +1,42 @@ #include - +#include #include #include -#define APP_RX_DATA_SIZE CDC_DATA_SZ -#define APP_TX_DATA_SIZE CDC_DATA_SZ -#define FURI_HAL_VCP_RX_BUFFER_SIZE (APP_RX_DATA_SIZE * 16) +#define TAG "FuriHalVcp" + +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3) + #define VCP_IF_NUM 0 +typedef enum { + VcpEvtReserved = (1 << 0), // Reserved for StreamBuffer internal event + VcpEvtConnect = (1 << 1), + VcpEvtDisconnect = (1 << 2), + VcpEvtEnable = (1 << 3), + VcpEvtDisable = (1 << 4), + VcpEvtRx = (1 << 5), + VcpEvtTx = (1 << 6), + VcpEvtStreamRx = (1 << 7), + VcpEvtStreamTx = (1 << 8), +} WorkerEvtFlags; + +#define VCP_THREAD_FLAG_ALL (VcpEvtConnect | VcpEvtDisconnect | VcpEvtEnable | VcpEvtDisable | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | VcpEvtStreamTx) + typedef struct { + FuriThread* thread; + + StreamBufferHandle_t tx_stream; + StreamBufferHandle_t rx_stream; + volatile bool connected; - StreamBufferHandle_t rx_stream; - volatile bool rx_stream_full; - - osSemaphoreId_t tx_semaphore; + uint8_t data_buffer[USB_CDC_PKT_LEN]; } FuriHalVcp; +static int32_t vcp_worker(void* context); static void vcp_on_cdc_tx_complete(); static void vcp_on_cdc_rx(); static void vcp_state_callback(uint8_t state); @@ -30,130 +50,225 @@ static CdcCallbacks cdc_cb = { NULL, }; -static FuriHalVcp* furi_hal_vcp = NULL; +static FuriHalVcp* vcp = NULL; static const uint8_t ascii_soh = 0x01; static const uint8_t ascii_eot = 0x04; -static uint8_t* vcp_rx_buf; - void furi_hal_vcp_init() { - furi_hal_vcp = furi_alloc(sizeof(FuriHalVcp)); - vcp_rx_buf = furi_alloc(APP_RX_DATA_SIZE); - furi_hal_vcp->connected = false; - - furi_hal_vcp->rx_stream = xStreamBufferCreate(FURI_HAL_VCP_RX_BUFFER_SIZE, 1); - furi_hal_vcp->rx_stream_full = false; + vcp = furi_alloc(sizeof(FuriHalVcp)); + vcp->connected = false; - furi_hal_vcp->tx_semaphore = osSemaphoreNew(1, 1, NULL); + vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1); + vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); + + vcp->thread = furi_thread_alloc(); + furi_thread_set_name(vcp->thread, "VcpWorker"); + furi_thread_set_stack_size(vcp->thread, 1024); + furi_thread_set_callback(vcp->thread, vcp_worker); + furi_thread_start(vcp->thread); + + FURI_LOG_I(TAG, "Init OK"); +} + +static int32_t vcp_worker(void* context) { + bool enabled = true; + bool tx_idle = false; + size_t missed_rx = 0; furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); - FURI_LOG_I("FuriHalVcp", "Init OK"); + while (1) { + uint32_t flags = osThreadFlagsWait(VCP_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); + furi_assert((flags & osFlagsError) == 0); + + // New data received + if((flags & VcpEvtStreamRx) && enabled && missed_rx > 0) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP StreamRx\r\n"); +#endif + if (xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { + flags |= VcpEvtRx; + missed_rx--; + } + } + + // Rx buffer was read, maybe there is enough space for new data? + if((flags & VcpEvtRx)) { + if (xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { + int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_printf("VCP Rx %d\r\n", len); +#endif + if (len > 0) { + furi_check(xStreamBufferSend(vcp->rx_stream, vcp->data_buffer, len, osWaitForever) == len); + } + } else { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Rx missed\r\n"); +#endif + missed_rx++; + } + } + + // New data in Tx buffer + if((flags & VcpEvtStreamTx) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP StreamTx\r\n"); +#endif + if (tx_idle) { + flags |= VcpEvtTx; + } + } + + // CDC write transfer done + if((flags & VcpEvtTx) && enabled) { + size_t len = xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_printf("VCP Tx %d\r\n", len); +#endif + if (len > 0) { // Some data left in Tx buffer. Sending it now + tx_idle = false; + furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); + } else { // There is nothing to send. Set flag to start next transfer instantly + tx_idle = true; + } + } + + // VCP session opened + if((flags & VcpEvtConnect) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Connect\r\n"); +#endif + if (vcp->connected == false) { + vcp->connected = true; + xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever); + } + } + + // VCP session closed + if((flags & VcpEvtDisconnect) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Disconnect\r\n"); +#endif + if (vcp->connected == true) { + vcp->connected = false; + xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever); + } + } + + // VCP enabled + if((flags & VcpEvtEnable) && !enabled){ +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Enable\r\n"); +#endif + furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); + enabled = true; + furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); // flush Rx buffer + if (furi_hal_cdc_get_ctrl_line_state(VCP_IF_NUM) & (1 << 0)) { + vcp->connected = true; + xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever); + } + } + + // VCP disabled + if((flags & VcpEvtDisable) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Disable\r\n"); +#endif + enabled = false; + vcp->connected = false; + xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever); + } + } + return 0; } void furi_hal_vcp_enable() { - furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); - furi_hal_vcp->connected = true; + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtEnable); } void furi_hal_vcp_disable() { - furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL); - furi_hal_vcp->connected = false; - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); -} - -size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { - furi_assert(furi_hal_vcp); - - size_t received = xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, portMAX_DELAY); - - if(furi_hal_vcp->rx_stream_full - && xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream) >= APP_RX_DATA_SIZE) { - furi_hal_vcp->rx_stream_full = false; - } - - return received; + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisable); } size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { - furi_assert(furi_hal_vcp); - return xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, timeout); + furi_assert(vcp); + furi_assert(buffer); + + size_t rx_cnt = 0; + + while (size > 0) { + size_t batch_size = size; + if (batch_size > VCP_RX_BUF_SIZE) + batch_size = VCP_RX_BUF_SIZE; + + size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout); + if (len == 0) + break; + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamRx); + size -= len; + buffer += len; + rx_cnt += len; + } + return rx_cnt; +} + +size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { + furi_assert(vcp); + return furi_hal_vcp_rx_with_timeout(buffer, size, osWaitForever); } void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { - furi_assert(furi_hal_vcp); - - while (size > 0 && furi_hal_vcp->connected) { - furi_check(osSemaphoreAcquire(furi_hal_vcp->tx_semaphore, osWaitForever) == osOK); - if (!furi_hal_vcp->connected) - break; + furi_assert(vcp); + furi_assert(buffer); + while (size > 0) { size_t batch_size = size; - if (batch_size > APP_TX_DATA_SIZE) { - batch_size = APP_TX_DATA_SIZE; - } + if (batch_size > VCP_TX_BUF_SIZE) + batch_size = VCP_TX_BUF_SIZE; + + xStreamBufferSend(vcp->tx_stream, buffer, batch_size, osWaitForever); + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamTx); - furi_hal_cdc_send(VCP_IF_NUM, (uint8_t*)buffer, batch_size); size -= batch_size; buffer += batch_size; } } static void vcp_state_callback(uint8_t state) { - if (state == 1) - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); - else if (furi_hal_vcp->connected) { - furi_hal_vcp->connected = false; - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP State\r\n"); +#endif + if (state == 0) { + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); } } static void vcp_on_cdc_control_line(uint8_t state) { - - BaseType_t xHigherPriorityTaskWoken = pdFALSE; // bit 0: DTR state, bit 1: RTS state - // bool dtr = state & 0b01; - bool dtr = state & 0b1; - - if (dtr) { - if (!furi_hal_vcp->connected) { - furi_hal_vcp->connected = true; - xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_soh, 1, &xHigherPriorityTaskWoken); // SOH - - } + bool dtr = state & (1 << 0); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP CtrlLine\r\n"); +#endif + if (dtr == true) { + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtConnect); } else { - if (furi_hal_vcp->connected) { - xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_eot, 1, &xHigherPriorityTaskWoken); // EOT - furi_hal_vcp->connected = false; - } + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); } - - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); - - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } -static void vcp_on_cdc_rx() { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - - uint16_t max_len = xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream); - if (max_len > 0) { - if (max_len > APP_RX_DATA_SIZE) - max_len = APP_RX_DATA_SIZE; - int32_t size = furi_hal_cdc_receive(VCP_IF_NUM, vcp_rx_buf, max_len); - - if (size > 0) { - size_t ret = xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, vcp_rx_buf, size, &xHigherPriorityTaskWoken); - furi_check(ret == size); - } - } else { - furi_hal_vcp->rx_stream_full = true; - }; - - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +static void vcp_on_cdc_rx() { + uint32_t ret = osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtRx); + furi_assert((ret & osFlagsError) == 0); } static void vcp_on_cdc_tx_complete() { - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtTx); } + +bool furi_hal_vcp_is_connected(void) { + furi_assert(vcp); + return vcp->connected; +} + diff --git a/firmware/targets/f6/furi-hal/furi-hal-version.c b/firmware/targets/f6/furi-hal/furi-hal-version.c index 7ee67c2a..8f5f26d9 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-version.c +++ b/firmware/targets/f6/furi-hal/furi-hal-version.c @@ -7,6 +7,8 @@ #include #include "ble.h" +#define TAG "FuriHalVersion" + #define FURI_HAL_VERSION_OTP_HEADER_MAGIC 0xBABE #define FURI_HAL_VERSION_OTP_ADDRESS OTP_AREA_BASE @@ -191,7 +193,7 @@ void furi_hal_version_init() { break; default: furi_crash(NULL); } - FURI_LOG_I("FuriHalVersion", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } bool furi_hal_version_do_i_belong_here() { @@ -276,7 +278,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) { return version_get(); } -const struct Version* furi_hal_version_get_boot_version(void) { +const struct Version* furi_hal_version_get_bootloader_version(void) { #ifdef NO_BOOTLOADER return 0; #else diff --git a/firmware/targets/f6/furi-hal/furi-hal-vibro.c b/firmware/targets/f6/furi-hal/furi-hal-vibro.c index 7dfddd42..7de8ad84 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-vibro.c +++ b/firmware/targets/f6/furi-hal/furi-hal-vibro.c @@ -1,10 +1,12 @@ #include #include +#define TAG "FuriHalVibro" + void furi_hal_vibro_init() { hal_gpio_init(&vibro_gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); hal_gpio_write(&vibro_gpio, false); - FURI_LOG_I("FuriHalVibro", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } diff --git a/firmware/targets/f6/furi-hal/furi-hal.c b/firmware/targets/f6/furi-hal/furi-hal.c index bcddc6e0..d435c5f2 100644 --- a/firmware/targets/f6/furi-hal/furi-hal.c +++ b/firmware/targets/f6/furi-hal/furi-hal.c @@ -5,6 +5,12 @@ #include #include +#include + +#include + +#define TAG "FuriHal" + void furi_hal_init() { furi_hal_clock_init(); furi_hal_console_init(); @@ -12,23 +18,23 @@ void furi_hal_init() { furi_hal_delay_init(); MX_GPIO_Init(); - FURI_LOG_I("HAL", "GPIO OK"); + FURI_LOG_I(TAG, "GPIO OK"); MX_RTC_Init(); - FURI_LOG_I("HAL", "RTC OK"); - furi_hal_boot_init(); + FURI_LOG_I(TAG, "RTC OK"); + furi_hal_bootloader_init(); furi_hal_version_init(); furi_hal_spi_init(); MX_TIM1_Init(); - FURI_LOG_I("HAL", "TIM1 OK"); + FURI_LOG_I(TAG, "TIM1 OK"); MX_TIM2_Init(); - FURI_LOG_I("HAL", "TIM2 OK"); + FURI_LOG_I(TAG, "TIM2 OK"); MX_TIM16_Init(); - FURI_LOG_I("HAL", "TIM16 OK"); + FURI_LOG_I(TAG, "TIM16 OK"); MX_COMP1_Init(); - FURI_LOG_I("HAL", "COMP1 OK"); + FURI_LOG_I(TAG, "COMP1 OK"); furi_hal_crypto_init(); @@ -36,7 +42,7 @@ void furi_hal_init() { furi_hal_usb_init(); furi_hal_usb_set_config(UsbModeVcpSingle); furi_hal_vcp_init(); - FURI_LOG_I("HAL", "USB OK"); + FURI_LOG_I(TAG, "USB OK"); furi_hal_i2c_init(); @@ -52,4 +58,22 @@ void furi_hal_init() { // FreeRTOS glue furi_hal_os_init(); + + // FatFS driver initialization + MX_FATFS_Init(); + FURI_LOG_I(TAG, "FATFS OK"); + + // Partial null pointer dereference protection + LL_MPU_Disable(); + LL_MPU_ConfigRegion( + LL_MPU_REGION_NUMBER0, 0x00, 0x0, + LL_MPU_REGION_SIZE_1MB + | LL_MPU_REGION_PRIV_RO_URO + | LL_MPU_ACCESS_BUFFERABLE + | LL_MPU_ACCESS_CACHEABLE + | LL_MPU_ACCESS_SHAREABLE + | LL_MPU_TEX_LEVEL1 + | LL_MPU_INSTRUCTION_ACCESS_ENABLE + ); + LL_MPU_Enable(LL_MPU_CTRL_PRIVILEGED_DEFAULT); } diff --git a/firmware/targets/f6/stm32wb55xx_flash_cm4_no_boot.ld b/firmware/targets/f6/stm32wb55xx_flash_cm4_no_bootloader.ld similarity index 100% rename from firmware/targets/f6/stm32wb55xx_flash_cm4_no_boot.ld rename to firmware/targets/f6/stm32wb55xx_flash_cm4_no_bootloader.ld diff --git a/firmware/targets/f6/stm32wb55xx_flash_cm4_boot.ld b/firmware/targets/f6/stm32wb55xx_flash_cm4_with_bootloader.ld similarity index 100% rename from firmware/targets/f6/stm32wb55xx_flash_cm4_boot.ld rename to firmware/targets/f6/stm32wb55xx_flash_cm4_with_bootloader.ld diff --git a/firmware/targets/f6/target.mk b/firmware/targets/f6/target.mk index f942ba0f..85669e8d 100644 --- a/firmware/targets/f6/target.mk +++ b/firmware/targets/f6/target.mk @@ -49,8 +49,6 @@ C_SOURCES += \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_cortex.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_cryp.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_exti.c \ - $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_flash.c \ - $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_flash_ex.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_gpio.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_hsem.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_ipcc.c \ @@ -130,6 +128,11 @@ ifeq ($(FURI_HAL_OS_DEBUG), 1) CFLAGS += -DFURI_HAL_OS_DEBUG endif +FURI_HAL_USB_VCP_DEBUG ?= 0 +ifeq ($(FURI_HAL_USB_VCP_DEBUG), 1) +CFLAGS += -DFURI_HAL_USB_VCP_DEBUG +endif + FURI_HAL_SUBGHZ_TX_GPIO ?= 0 ifneq ($(FURI_HAL_SUBGHZ_TX_GPIO), 0) CFLAGS += -DFURI_HAL_SUBGHZ_TX_GPIO=$(FURI_HAL_SUBGHZ_TX_GPIO) @@ -146,16 +149,16 @@ C_SOURCES += $(wildcard $(FURI_HAL_DIR)/*.c) # Other CFLAGS += \ -I$(MXPROJECT_DIR)/Inc \ - -I$(MXPROJECT_DIR)/Src/fatfs + -I$(MXPROJECT_DIR)/fatfs C_SOURCES += \ $(wildcard $(MXPROJECT_DIR)/Src/*.c) \ - $(wildcard $(MXPROJECT_DIR)/Src/fatfs/*.c) + $(wildcard $(MXPROJECT_DIR)/fatfs/*.c) # Linker options ifeq ($(NO_BOOTLOADER), 1) -LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_no_boot.ld +LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_no_bootloader.ld else -LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_boot.ld +LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_with_bootloader.ld endif SVD_FILE = ../debug/STM32WB55_CM4.svd diff --git a/firmware/targets/f7/Inc/FreeRTOSConfig.h b/firmware/targets/f7/Inc/FreeRTOSConfig.h index 228b530d..1ad20993 100644 --- a/firmware/targets/f7/Inc/FreeRTOSConfig.h +++ b/firmware/targets/f7/Inc/FreeRTOSConfig.h @@ -57,7 +57,7 @@ #endif /* CMSIS_device_header */ #define configENABLE_FPU 1 -#define configENABLE_MPU 1 +#define configENABLE_MPU 0 #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 0 @@ -101,6 +101,9 @@ #define configTIMER_TASK_PRIORITY ( 2 ) #define configTIMER_QUEUE_LENGTH 32 #define configTIMER_TASK_STACK_DEPTH 256 +#define configTIMER_SERVICE_TASK_NAME "TimersSrv" + +#define configIDLE_TASK_NAME "(-_-)" /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ diff --git a/firmware/targets/f7/Inc/stm32_assert.h b/firmware/targets/f7/Inc/stm32_assert.h new file mode 100644 index 00000000..9f6261ff --- /dev/null +++ b/firmware/targets/f7/Inc/stm32_assert.h @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * @file stm32_assert.h + * @brief STM32 assert file. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2019 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32_ASSERT_H +#define __STM32_ASSERT_H + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef USE_FULL_ASSERT + #define assert_param(expr) ((expr) ? (void)0U : assert_failed()) + void assert_failed(); +#else + #define assert_param(expr) ((void)0U) +#endif /* USE_FULL_ASSERT */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32_ASSERT_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/Inc/stm32wbxx_hal_conf.h b/firmware/targets/f7/Inc/stm32wbxx_hal_conf.h index ab7b2953..4d5ad791 100644 --- a/firmware/targets/f7/Inc/stm32wbxx_hal_conf.h +++ b/firmware/targets/f7/Inc/stm32wbxx_hal_conf.h @@ -184,7 +184,7 @@ * @brief Uncomment the line below to expanse the "assert_param" macro in the * HAL drivers code */ -/* #define USE_FULL_ASSERT 1U */ +#define USE_FULL_ASSERT 1U /* ################## SPI peripheral configuration ########################## */ @@ -329,17 +329,8 @@ /* Exported macro ------------------------------------------------------------*/ #ifdef USE_FULL_ASSERT -/** - * @brief The assert_param macro is used for function's parameters check. - * @param expr If expr is false, it calls assert_failed function - * which reports the name of the source file and the source - * line number of the call that failed. - * If expr is true, it returns no value. - * @retval None - */ - #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) -/* Exported functions ------------------------------------------------------- */ - void assert_failed(uint8_t* file, uint32_t line); + #define assert_param(expr) ((expr) ? (void)0U : assert_failed()) + void assert_failed(); #else #define assert_param(expr) ((void)0U) #endif /* USE_FULL_ASSERT */ diff --git a/firmware/targets/f7/Src/main.c b/firmware/targets/f7/Src/main.c index ea6d8943..a546086f 100644 --- a/firmware/targets/f7/Src/main.c +++ b/firmware/targets/f7/Src/main.c @@ -1,11 +1,11 @@ #include "main.h" -#include "fatfs/fatfs.h" - #include #include #include +#define TAG "Main" + int main(void) { // Initialize FURI layer furi_init(); @@ -16,13 +16,9 @@ int main(void) { // Flipper FURI HAL furi_hal_init(); - // 3rd party - MX_FATFS_Init(); - FURI_LOG_I("HAL", "FATFS OK"); - // CMSIS initialization osKernelInitialize(); - FURI_LOG_I("HAL", "KERNEL OK"); + FURI_LOG_I(TAG, "KERNEL OK"); // Init flipper flipper_init(); @@ -47,9 +43,6 @@ void Error_Handler(void) { * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { - /* USER CODE BEGIN 6 */ - /* User can add his own implementation to report the file name and line number, - tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ - /* USER CODE END 6 */ + furi_crash("HAL assert failed"); } #endif /* USE_FULL_ASSERT */ diff --git a/firmware/targets/f7/ble-glue/app_conf.h b/firmware/targets/f7/ble-glue/app_conf.h index e3820e11..eebdbbdb 100644 --- a/firmware/targets/f7/ble-glue/app_conf.h +++ b/firmware/targets/f7/ble-glue/app_conf.h @@ -427,16 +427,5 @@ typedef enum #define DBG_TRACE_MSG_QUEUE_SIZE 4096 #define MAX_DBG_TRACE_MSG_SIZE 1024 -/****************************************************************************** - * FreeRTOS - ******************************************************************************/ -#define CFG_SHCI_USER_EVT_PROCESS_NAME "ble_shci_evt" -#define CFG_SHCI_USER_EVT_PROCESS_ATTR_BITS (0) -#define CFG_SHCI_USER_EVT_PROCESS_CB_MEM (0) -#define CFG_SHCI_USER_EVT_PROCESS_CB_SIZE (0) -#define CFG_SHCI_USER_EVT_PROCESS_STACK_MEM (0) -#define CFG_SHCI_USER_EVT_PROCESS_PRIORITY osPriorityNone -#define CFG_SHCI_USER_EVT_PROCESS_STACK_SIZE (128 * 7) - #define CFG_OTP_BASE_ADDRESS OTP_AREA_BASE #define CFG_OTP_END_ADRESS OTP_AREA_END_ADDR diff --git a/firmware/targets/f7/ble-glue/app_entry.c b/firmware/targets/f7/ble-glue/app_entry.c deleted file mode 100644 index 5c8c0b2a..00000000 --- a/firmware/targets/f7/ble-glue/app_entry.c +++ /dev/null @@ -1,180 +0,0 @@ -#include "app_common.h" -#include "main.h" -#include "app_entry.h" -#include "ble_app.h" -#include "ble.h" -#include "tl.h" -#include "cmsis_os.h" -#include "shci_tl.h" -#include "app_debug.h" -#include - -extern RTC_HandleTypeDef hrtc; - -#define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) - -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t EvtPool[POOL_SIZE]; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t SystemCmdBuffer; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t SystemSpareEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t BleSpareEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; - -osMutexId_t MtxShciId; -osSemaphoreId_t SemShciId; -osThreadId_t ShciUserEvtProcessId; - -volatile static BleGlueStatus ble_glue_status = BleGlueStatusUninitialized; - -const osThreadAttr_t ShciUserEvtProcess_attr = { - .name = CFG_SHCI_USER_EVT_PROCESS_NAME, - .attr_bits = CFG_SHCI_USER_EVT_PROCESS_ATTR_BITS, - .cb_mem = CFG_SHCI_USER_EVT_PROCESS_CB_MEM, - .cb_size = CFG_SHCI_USER_EVT_PROCESS_CB_SIZE, - .stack_mem = CFG_SHCI_USER_EVT_PROCESS_STACK_MEM, - .priority = CFG_SHCI_USER_EVT_PROCESS_PRIORITY, - .stack_size = CFG_SHCI_USER_EVT_PROCESS_STACK_SIZE -}; - -static void ShciUserEvtProcess(void *argument); -static void SystemPower_Config( void ); -static void appe_Tl_Init( void ); -static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status ); -static void APPE_SysUserEvtRx( void * pPayload ); - -BleGlueStatus APPE_Status() { - return ble_glue_status; -} - -void APPE_Init() { - ble_glue_status = BleGlueStatusStartup; - SystemPower_Config(); /**< Configure the system Power Mode */ - - // APPD_Init(); - furi_hal_power_insomnia_enter(); - - appe_Tl_Init(); /* Initialize all transport layers */ - - /** - * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) - * received on the system channel before starting the Stack - * This system event is received with APPE_SysUserEvtRx() - */ -} - -/************************************************************* - * - * LOCAL FUNCTIONS - * - *************************************************************/ - -/** - * @brief Configure the system for power optimization - * - * @note This API configures the system to be ready for low power mode - * - * @param None - * @retval None - */ -static void SystemPower_Config(void) { - // Select HSI as system clock source after Wake Up from Stop mode - LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); - - /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); -} - -static void appe_Tl_Init( void ) { - TL_MM_Config_t tl_mm_config; - SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf; - /**< Reference table initialization */ - TL_Init(); - - MtxShciId = osMutexNew( NULL ); - SemShciId = osSemaphoreNew( 1, 0, NULL ); /*< Create the semaphore and make it busy at initialization */ - - /** FreeRTOS system task creation */ - ShciUserEvtProcessId = osThreadNew(ShciUserEvtProcess, NULL, &ShciUserEvtProcess_attr); - - /**< System channel initialization */ - SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&SystemCmdBuffer; - SHci_Tl_Init_Conf.StatusNotCallBack = APPE_SysStatusNot; - shci_init(APPE_SysUserEvtRx, (void*) &SHci_Tl_Init_Conf); - - /**< Memory Manager channel initialization */ - tl_mm_config.p_BleSpareEvtBuffer = BleSpareEvtBuffer; - tl_mm_config.p_SystemSpareEvtBuffer = SystemSpareEvtBuffer; - tl_mm_config.p_AsynchEvtPool = EvtPool; - tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; - TL_MM_Init( &tl_mm_config ); - - TL_Enable(); -} - -static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status ) { - switch (status) { - case SHCI_TL_CmdBusy: - osMutexAcquire( MtxShciId, osWaitForever ); - break; - case SHCI_TL_CmdAvailable: - osMutexRelease( MtxShciId ); - break; - default: - break; - } -} - -/** - * The type of the payload for a system user event is tSHCI_UserEvtRxParam - * When the system event is both : - * - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY) - * - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING) - * The buffer shall not be released - * ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) - * When the status is not filled, the buffer is released by default - */ -static void APPE_SysUserEvtRx( void * pPayload ) { - UNUSED(pPayload); - /* Traces channel initialization */ - // APPD_EnableCPU2( ); - - if(ble_app_init()) { - FURI_LOG_I("Core2", "BLE stack started"); - ble_glue_status = BleGlueStatusStarted; - } else { - FURI_LOG_E("Core2", "BLE stack startup failed"); - ble_glue_status = BleGlueStatusBroken; - } - furi_hal_power_insomnia_exit(); -} - -/************************************************************* - * - * FREERTOS WRAPPER FUNCTIONS - * -*************************************************************/ -static void ShciUserEvtProcess(void *argument) { - UNUSED(argument); - for(;;) { - osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); - shci_user_evt_proc(); - } -} - -/************************************************************* - * - * WRAP FUNCTIONS - * - *************************************************************/ -void shci_notify_asynch_evt(void* pdata) { - UNUSED(pdata); - osThreadFlagsSet( ShciUserEvtProcessId, 1 ); -} - -void shci_cmd_resp_release(uint32_t flag) { - UNUSED(flag); - osSemaphoreRelease( SemShciId ); -} - -void shci_cmd_resp_wait(uint32_t timeout) { - UNUSED(timeout); - osSemaphoreAcquire( SemShciId, osWaitForever ); -} diff --git a/firmware/targets/f7/ble-glue/app_entry.h b/firmware/targets/f7/ble-glue/app_entry.h deleted file mode 100644 index f1ce6038..00000000 --- a/firmware/targets/f7/ble-glue/app_entry.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - BleGlueStatusUninitialized, - BleGlueStatusStartup, - BleGlueStatusBroken, - BleGlueStatusStarted -} BleGlueStatus; - -void APPE_Init(); - -BleGlueStatus APPE_Status(); - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/firmware/targets/f7/ble-glue/battery_service.c b/firmware/targets/f7/ble-glue/battery_service.c index 1dd8c5a0..2a5dad5e 100644 --- a/firmware/targets/f7/ble-glue/battery_service.c +++ b/firmware/targets/f7/ble-glue/battery_service.c @@ -4,7 +4,7 @@ #include -#define BATTERY_SERVICE_TAG "battery service" +#define TAG "BtBatterySvc" typedef struct { uint16_t svc_handle; @@ -23,7 +23,7 @@ void battery_svc_start() { // Add Battery service status = aci_gatt_add_service(UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 4, &battery_svc->svc_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to add Battery service: %d", status); + FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); } // Add Battery level characteristic status = aci_gatt_add_char(battery_svc->svc_handle, @@ -37,7 +37,7 @@ void battery_svc_start() { CHAR_VALUE_LEN_CONSTANT, &battery_svc->char_level_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to add Battery level characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); } } @@ -47,12 +47,12 @@ void battery_svc_stop() { // Delete Battery level characteristic status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->char_level_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to delete Battery level characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to delete Battery level characteristic: %d", status); } // Delete Battery service status = aci_gatt_del_service(battery_svc->svc_handle); if(status) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to delete Battery service: %d", status); + FURI_LOG_E(TAG, "Failed to delete Battery service: %d", status); } free(battery_svc); battery_svc = NULL; @@ -65,14 +65,14 @@ bool battery_svc_update_level(uint8_t battery_charge) { return false; } // Update battery level characteristic - FURI_LOG_I(BATTERY_SERVICE_TAG, "Updating battery level characteristic"); + FURI_LOG_I(TAG, "Updating battery level characteristic"); tBleStatus result = aci_gatt_update_char_value(battery_svc->svc_handle, battery_svc->char_level_handle, 0, 1, &battery_charge); if(result) { - FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed updating RX characteristic: %d", result); + FURI_LOG_E(TAG, "Failed updating RX characteristic: %d", result); } return result != BLE_STATUS_SUCCESS; } diff --git a/firmware/targets/f7/ble-glue/ble_app.c b/firmware/targets/f7/ble-glue/ble_app.c index e28facd6..40b34679 100644 --- a/firmware/targets/f7/ble-glue/ble_app.c +++ b/firmware/targets/f7/ble-glue/ble_app.c @@ -8,9 +8,10 @@ #include -#define BLE_APP_TAG "ble app" +#define TAG "Bt" PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; typedef struct { osMutexId_t hci_mtx; @@ -26,13 +27,13 @@ static void ble_app_hci_event_handler(void * pPayload); static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status); bool ble_app_init() { + SHCI_CmdStatus_t status; ble_app = furi_alloc(sizeof(BleApp)); - // Allocate semafore and mutex for ble command buffer access ble_app->hci_mtx = osMutexNew(NULL); ble_app->hci_sem = osSemaphoreNew(1, 0, NULL); // HCI transport layer thread to handle user asynch events - ble_app->hci_thread_attr.name = "ble hci"; + ble_app->hci_thread_attr.name = "BleHciWorker"; ble_app->hci_thread_attr.stack_size = 1024; ble_app->hci_thread_id = osThreadNew(ble_app_hci_thread, NULL, &ble_app->hci_thread_attr); @@ -43,6 +44,18 @@ bool ble_app_init() { }; hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config); + // Configure NVM store for pairing data + SHCI_C2_CONFIG_Cmd_Param_t config_param = { + .PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE, + .Config1 =SHCI_C2_CONFIG_CONFIG1_BIT0_BLE_NVM_DATA_TO_SRAM, + .BleNvmRamAddress = (uint32_t)ble_app_nvm, + .EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE, + }; + status = SHCI_C2_Config(&config_param); + if(status) { + FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); + } + // Start ble stack on 2nd core SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { .Header = {{0,0,0}}, // Header unused @@ -67,13 +80,18 @@ bool ble_app_init() { 0, } }; - SHCI_CmdStatus_t status = SHCI_C2_BLE_Init(&ble_init_cmd_packet); + status = SHCI_C2_BLE_Init(&ble_init_cmd_packet); if(status) { - FURI_LOG_E(BLE_APP_TAG, "Failed to start ble stack: %d", status); + FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); } return status == SHCI_Success; } +void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) { + *addr = (uint8_t*)ble_app_nvm; + *size = sizeof(ble_app_nvm); +} + static void ble_app_hci_thread(void *arg) { while(1) { osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); diff --git a/firmware/targets/f7/ble-glue/ble_app.h b/firmware/targets/f7/ble-glue/ble_app.h index 17d8dc5f..64000bde 100644 --- a/firmware/targets/f7/ble-glue/ble_app.h +++ b/firmware/targets/f7/ble-glue/ble_app.h @@ -5,8 +5,10 @@ extern "C" { #endif #include +#include bool ble_app_init(); +void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); #ifdef __cplusplus } diff --git a/firmware/targets/f7/ble-glue/ble_glue.c b/firmware/targets/f7/ble-glue/ble_glue.c new file mode 100644 index 00000000..45503683 --- /dev/null +++ b/firmware/targets/f7/ble-glue/ble_glue.c @@ -0,0 +1,230 @@ +#include "ble_glue.h" +#include "app_common.h" +#include "main.h" +#include "ble_app.h" +#include "ble.h" +#include "tl.h" +#include "shci.h" +#include "cmsis_os.h" +#include "shci_tl.h" +#include "app_debug.h" +#include + +#define TAG "Core2" + +#define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) + +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_system_cmd_buff; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_system_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; + +typedef enum { + // Stage 1: core2 startup and FUS + BleGlueStatusStartup, + BleGlueStatusBroken, + BleGlueStatusFusStarted, + // Stage 2: radio stack + BleGlueStatusRadioStackStarted, + BleGlueStatusRadioStackMissing +} BleGlueStatus; + +typedef struct { + osMutexId_t shci_mtx; + osSemaphoreId_t shci_sem; + osThreadId_t shci_user_event_thread_id; + osThreadAttr_t shci_user_event_thread_attr; + BleGlueStatus status; + BleGlueKeyStorageChangedCallback callback; + void* context; +} BleGlue; + +static BleGlue* ble_glue = NULL; + +static void ble_glue_user_event_thread(void *argument); +static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status); +static void ble_glue_sys_user_event_callback(void* pPayload); + +void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback callback, void* context) { + furi_assert(ble_glue); + furi_assert(callback); + ble_glue->callback = callback; + ble_glue->context = context; +} + +void ble_glue_init() { + ble_glue = furi_alloc(sizeof(BleGlue)); + ble_glue->status = BleGlueStatusStartup; + ble_glue->shci_user_event_thread_attr.name = "BleShciWorker"; + ble_glue->shci_user_event_thread_attr.stack_size = 1024; + + // Configure the system Power Mode + // Select HSI as system clock source after Wake Up from Stop mode + LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); + /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + furi_hal_power_insomnia_enter(); + + // APPD_Init(); + + // Initialize all transport layers + TL_MM_Config_t tl_mm_config; + SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf; + // Reference table initialization + TL_Init(); + + ble_glue->shci_mtx = osMutexNew(NULL); + ble_glue->shci_sem = osSemaphoreNew(1, 0, NULL); + + // FreeRTOS system task creation + ble_glue->shci_user_event_thread_id = osThreadNew(ble_glue_user_event_thread, NULL, &ble_glue->shci_user_event_thread_attr); + + // System channel initialization + SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff; + SHci_Tl_Init_Conf.StatusNotCallBack = ble_glue_sys_status_not_callback; + shci_init(ble_glue_sys_user_event_callback, (void*) &SHci_Tl_Init_Conf); + + /**< Memory Manager channel initialization */ + tl_mm_config.p_BleSpareEvtBuffer = ble_glue_ble_spare_event_buff; + tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_system_spare_event_buff; + tl_mm_config.p_AsynchEvtPool = ble_glue_event_pool; + tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; + TL_MM_Init( &tl_mm_config ); + TL_Enable(); + + /* + * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) + * received on the system channel before starting the Stack + * This system event is received with ble_glue_sys_user_event_callback() + */ +} + +static bool ble_glue_wait_status(BleGlueStatus status) { + bool ret = false; + size_t countdown = 1000; + while (countdown > 0) { + if (ble_glue->status == status) { + ret = true; + break; + } + countdown--; + osDelay(1); + } + return ret; +} + +bool ble_glue_start() { + furi_assert(ble_glue); + + if (!ble_glue_wait_status(BleGlueStatusFusStarted)) { + // shutdown core2 power + FURI_LOG_E(TAG, "Core2 catastrophic failure, cutting its power"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue->status = BleGlueStatusBroken; + furi_hal_power_insomnia_exit(); + return false; + } + + bool ret = false; + furi_hal_power_insomnia_enter(); + if(ble_app_init()) { + FURI_LOG_I(TAG, "Radio stack started"); + ble_glue->status = BleGlueStatusRadioStackStarted; + ret = true; + if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { + FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); + } else { + FURI_LOG_E(TAG, "Failed to switch flash activity control to SEM7"); + } + } else { + FURI_LOG_E(TAG, "Radio stack startup failed"); + ble_glue->status = BleGlueStatusRadioStackMissing; + } + furi_hal_power_insomnia_exit(); + + return ret; +} + +bool ble_glue_is_alive() { + if(!ble_glue) { + return false; + } + + return ble_glue->status >= BleGlueStatusFusStarted; +} + +bool ble_glue_is_radio_stack_ready() { + if(!ble_glue) { + return false; + } + + return ble_glue->status == BleGlueStatusRadioStackStarted; +} + +static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { + switch (status) { + case SHCI_TL_CmdBusy: + osMutexAcquire( ble_glue->shci_mtx, osWaitForever ); + break; + case SHCI_TL_CmdAvailable: + osMutexRelease( ble_glue->shci_mtx ); + break; + default: + break; + } +} + +/* + * The type of the payload for a system user event is tSHCI_UserEvtRxParam + * When the system event is both : + * - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY) + * - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING) + * The buffer shall not be released + * ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) + * When the status is not filled, the buffer is released by default + */ +static void ble_glue_sys_user_event_callback( void * pPayload ) { + UNUSED(pPayload); + /* Traces channel initialization */ + // APPD_EnableCPU2( ); + + TL_AsynchEvt_t *p_sys_event = (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); + + if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { + FURI_LOG_I(TAG, "Fus started"); + ble_glue->status = BleGlueStatusFusStarted; + furi_hal_power_insomnia_exit(); + } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { + FURI_LOG_E(TAG, "Error during initialization"); + furi_hal_power_insomnia_exit(); + } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE) { + SHCI_C2_BleNvmRamUpdate_Evt_t* p_sys_ble_nvm_ram_update_event = (SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload; + if(ble_glue->callback) { + ble_glue->callback((uint8_t*)p_sys_ble_nvm_ram_update_event->StartAddress, p_sys_ble_nvm_ram_update_event->Size, ble_glue->context); + } + } +} + +// Wrap functions +static void ble_glue_user_event_thread(void *argument) { + UNUSED(argument); + for(;;) { + osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); + shci_user_evt_proc(); + } +} + +void shci_notify_asynch_evt(void* pdata) { + UNUSED(pdata); + osThreadFlagsSet(ble_glue->shci_user_event_thread_id, 1); +} + +void shci_cmd_resp_release(uint32_t flag) { + UNUSED(flag); + osSemaphoreRelease(ble_glue->shci_sem); +} + +void shci_cmd_resp_wait(uint32_t timeout) { + UNUSED(timeout); + osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); +} diff --git a/firmware/targets/f7/ble-glue/ble_glue.h b/firmware/targets/f7/ble-glue/ble_glue.h new file mode 100644 index 00000000..ac668c42 --- /dev/null +++ b/firmware/targets/f7/ble-glue/ble_glue.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void(*BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); + + +/** Initialize start core2 and initialize transport */ +void ble_glue_init(); + +/** Start Core2 Radio stack + * + * @return true on success + */ +bool ble_glue_start(); + +/** Is core2 alive and at least FUS is running + * + * @return true if core2 is alive + */ +bool ble_glue_is_alive(); + +/** Is core2 radio stack present and ready + * + * @return true if present and ready + */ +bool ble_glue_is_radio_stack_ready(); + +/** Set callback for NVM in RAM changes + * + * @param[in] callback The callback to call on NVM change + * @param context The context for callback + */ +void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback callback, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/ble-glue/dev_info_service.c b/firmware/targets/f7/ble-glue/dev_info_service.c index 7ce2647c..64ff0509 100644 --- a/firmware/targets/f7/ble-glue/dev_info_service.c +++ b/firmware/targets/f7/ble-glue/dev_info_service.c @@ -4,7 +4,7 @@ #include -#define DEV_INFO_SVC_TAG "dev info service" +#define TAG "BtDevInfoSvc" typedef struct { uint16_t service_handle; @@ -29,7 +29,7 @@ void dev_info_svc_start() { uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; status = aci_gatt_add_service(UUID_TYPE_16, (Service_UUID_t*)&uuid, PRIMARY_SERVICE, 9, &dev_info_svc->service_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add Device Information Service: %d", status); + FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status); } // Add characteristics @@ -45,7 +45,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->man_name_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add manufacturer name char: %d", status); + FURI_LOG_E(TAG, "Failed to add manufacturer name char: %d", status); } uuid = SERIAL_NUMBER_UUID; status = aci_gatt_add_char(dev_info_svc->service_handle, @@ -59,7 +59,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->serial_num_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add serial number char: %d", status); + FURI_LOG_E(TAG, "Failed to add serial number char: %d", status); } uuid = FIRMWARE_REVISION_UUID; status = aci_gatt_add_char(dev_info_svc->service_handle, @@ -73,7 +73,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->firmware_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add firmware revision char: %d", status); + FURI_LOG_E(TAG, "Failed to add firmware revision char: %d", status); } uuid = SOFTWARE_REVISION_UUID; status = aci_gatt_add_char(dev_info_svc->service_handle, @@ -87,7 +87,7 @@ void dev_info_svc_start() { CHAR_VALUE_LEN_CONSTANT, &dev_info_svc->software_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add software revision char: %d", status); + FURI_LOG_E(TAG, "Failed to add software revision char: %d", status); } // Update characteristics @@ -97,7 +97,7 @@ void dev_info_svc_start() { strlen(dev_info_man_name), (uint8_t*)dev_info_man_name); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update manufacturer name char: %d", status); + FURI_LOG_E(TAG, "Failed to update manufacturer name char: %d", status); } status = aci_gatt_update_char_value(dev_info_svc->service_handle, dev_info_svc->serial_num_char_handle, @@ -105,7 +105,7 @@ void dev_info_svc_start() { strlen(dev_info_serial_num), (uint8_t*)dev_info_serial_num); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update serial number char: %d", status); + FURI_LOG_E(TAG, "Failed to update serial number char: %d", status); } status = aci_gatt_update_char_value(dev_info_svc->service_handle, dev_info_svc->firmware_rev_char_handle, @@ -113,7 +113,7 @@ void dev_info_svc_start() { strlen(dev_info_firmware_rev_num), (uint8_t*)dev_info_firmware_rev_num); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update firmware revision char: %d", status); + FURI_LOG_E(TAG, "Failed to update firmware revision char: %d", status); } status = aci_gatt_update_char_value(dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle, @@ -121,7 +121,7 @@ void dev_info_svc_start() { strlen(dev_info_software_rev_num), (uint8_t*)dev_info_software_rev_num); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update software revision char: %d", status); + FURI_LOG_E(TAG, "Failed to update software revision char: %d", status); } } @@ -131,24 +131,24 @@ void dev_info_svc_stop() { // Delete service characteristics status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->man_name_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete manufacturer name char: %d", status); + FURI_LOG_E(TAG, "Failed to delete manufacturer name char: %d", status); } status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->serial_num_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete serial number char: %d", status); + FURI_LOG_E(TAG, "Failed to delete serial number char: %d", status); } status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->firmware_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete firmware revision char: %d", status); + FURI_LOG_E(TAG, "Failed to delete firmware revision char: %d", status); } status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete software revision char: %d", status); + FURI_LOG_E(TAG, "Failed to delete software revision char: %d", status); } // Delete service status = aci_gatt_del_service(dev_info_svc->service_handle); if(status) { - FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete device info service: %d", status); + FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); } free(dev_info_svc); dev_info_svc = NULL; diff --git a/firmware/targets/f7/ble-glue/gap.c b/firmware/targets/f7/ble-glue/gap.c index eae5f87c..2fd29f10 100644 --- a/firmware/targets/f7/ble-glue/gap.c +++ b/firmware/targets/f7/ble-glue/gap.c @@ -1,6 +1,5 @@ #include "gap.h" -#include "app_entry.h" #include "ble.h" #include "cmsis_os.h" @@ -11,7 +10,7 @@ #include -#define GAP_TAG "BLE" +#define TAG "BtGap" #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 @@ -81,7 +80,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) if (disconnection_complete_event->Connection_Handle == gap->gap_svc.connection_handle) { gap->gap_svc.connection_handle = 0; gap->state = GapStateIdle; - FURI_LOG_I(GAP_TAG, "Disconnect from client. Reason: %d", disconnection_complete_event->Reason); + FURI_LOG_I(TAG, "Disconnect from client. Reason: %d", disconnection_complete_event->Reason); } if(gap->enable_adv) { // Restart advertising @@ -97,28 +96,28 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) meta_evt = (evt_le_meta_event*) event_pckt->data; switch (meta_evt->subevent) { case EVT_LE_CONN_UPDATE_COMPLETE: - FURI_LOG_D(GAP_TAG, "Connection update event"); + FURI_LOG_D(TAG, "Connection update event"); break; case EVT_LE_PHY_UPDATE_COMPLETE: evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data; if(evt_le_phy_update_complete->Status) { - FURI_LOG_E(GAP_TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); + FURI_LOG_E(TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); } else { - FURI_LOG_I(GAP_TAG, "Update PHY succeed"); + FURI_LOG_I(TAG, "Update PHY succeed"); } ret = hci_le_read_phy(gap->gap_svc.connection_handle,&tx_phy,&rx_phy); if(ret) { - FURI_LOG_E(GAP_TAG, "Read PHY failed, status: %d", ret); + FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); } else { - FURI_LOG_I(GAP_TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); + FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy); } break; case EVT_LE_CONN_COMPLETE: furi_hal_power_insomnia_enter(); hci_le_connection_complete_event_rp0* connection_complete_event = (hci_le_connection_complete_event_rp0 *) meta_evt->data; - FURI_LOG_I(GAP_TAG, "Connection complete for connection handle 0x%x", connection_complete_event->Connection_Handle); + FURI_LOG_I(TAG, "Connection complete for connection handle 0x%x", connection_complete_event->Connection_Handle); // Stop advertising as connection completed osTimerStop(gap->advertise_timer); @@ -142,7 +141,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_gap_pairing_complete_event_rp0 *pairing_complete; case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: - FURI_LOG_I(GAP_TAG, "Limited discoverable event"); + FURI_LOG_I(TAG, "Limited discoverable event"); break; case EVT_BLUE_GAP_PASS_KEY_REQUEST: @@ -150,39 +149,39 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) // Generate random PIN code uint32_t pin = rand() % 999999; aci_gap_pass_key_resp(gap->gap_svc.connection_handle, pin); - FURI_LOG_I(GAP_TAG, "Pass key request event. Pin: %d", pin); + FURI_LOG_I(TAG, "Pass key request event. Pin: %d", pin); BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); } break; case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: - FURI_LOG_I(GAP_TAG, "Authorization request event"); + FURI_LOG_I(TAG, "Authorization request event"); break; case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: - FURI_LOG_I(GAP_TAG, "Slave security initiated"); + FURI_LOG_I(TAG, "Slave security initiated"); break; case EVT_BLUE_GAP_BOND_LOST: - FURI_LOG_I(GAP_TAG, "Bond lost event. Start rebonding"); + FURI_LOG_I(TAG, "Bond lost event. Start rebonding"); aci_gap_allow_rebond(gap->gap_svc.connection_handle); break; case EVT_BLUE_GAP_DEVICE_FOUND: - FURI_LOG_I(GAP_TAG, "Device found event"); + FURI_LOG_I(TAG, "Device found event"); break; case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: - FURI_LOG_I(GAP_TAG, "Address not resolved event"); + FURI_LOG_I(TAG, "Address not resolved event"); break; case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: - FURI_LOG_I(GAP_TAG, "Key press notification event"); + FURI_LOG_I(TAG, "Key press notification event"); break; case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: - FURI_LOG_I(GAP_TAG, "Hex_value = %lx", + FURI_LOG_I(TAG, "Hex_value = %lx", ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); aci_gap_numeric_comparison_value_confirm_yesno(gap->gap_svc.connection_handle, 1); break; @@ -190,17 +189,17 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) case EVT_BLUE_GAP_PAIRING_CMPLT: pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; if (pairing_complete->Status) { - FURI_LOG_E(GAP_TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); + FURI_LOG_E(TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); aci_gap_terminate(gap->gap_svc.connection_handle, 5); } else { - FURI_LOG_I(GAP_TAG, "Pairing complete"); + FURI_LOG_I(TAG, "Pairing complete"); BleEvent event = {.type = BleEventTypeConnected}; gap->on_event_cb(event, gap->context); } break; case EVT_BLUE_GAP_PROCEDURE_COMPLETE: - FURI_LOG_I(GAP_TAG, "Procedure complete event"); + FURI_LOG_I(TAG, "Procedure complete event"); break; } default: @@ -287,11 +286,11 @@ static void gap_init_svc(Gap* gap) { // Set GAP characteristics status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); if (status) { - FURI_LOG_E(GAP_TAG, "Failed updating name characteristic: %d", status); + FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); } status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.appearance_char_handle, 0, 2, gap_appearence_char_uuid); if(status) { - FURI_LOG_E(GAP_TAG, "Failed updating appearence characteristic: %d", status); + FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); } // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); @@ -323,7 +322,7 @@ static void gap_advertise_start(GapState new_state) // Stop advertising status = aci_gap_set_non_discoverable(); if (status) { - FURI_LOG_E(GAP_TAG, "Stop Advertising Failed, result: %d", status); + FURI_LOG_E(TAG, "Stop Advertising Failed, result: %d", status); } } // Configure advertising @@ -332,7 +331,7 @@ static void gap_advertise_start(GapState new_state) strlen(name), (uint8_t*)name, gap->gap_svc.adv_svc_uuid_len, gap->gap_svc.adv_svc_uuid, 0, 0); if(status) { - FURI_LOG_E(GAP_TAG, "Set discoverable err: %d", status); + FURI_LOG_E(TAG, "Set discoverable err: %d", status); } gap->state = new_state; BleEvent event = {.type = BleEventTypeStartAdvertising}; @@ -356,14 +355,14 @@ static void gap_advertise_stop() { } void gap_start_advertising() { - FURI_LOG_I(GAP_TAG, "Start advertising"); + FURI_LOG_I(TAG, "Start advertising"); gap->enable_adv = true; GapCommand command = GapCommandAdvFast; furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); } void gap_stop_advertising() { - FURI_LOG_I(GAP_TAG, "Stop advertising"); + FURI_LOG_I(TAG, "Stop advertising"); gap->enable_adv = false; GapCommand command = GapCommandAdvStop; furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); @@ -375,7 +374,7 @@ static void gap_advetise_timer_callback(void* context) { } bool gap_init(BleEventCallback on_event_cb, void* context) { - if (APPE_Status() != BleGlueStatusStarted) { + if (!ble_glue_is_radio_stack_ready()) { return false; } @@ -394,7 +393,7 @@ bool gap_init(BleEventCallback on_event_cb, void* context) { gap->enable_adv = true; // Thread configuration - gap->thread_attr.name = "BLE advertising"; + gap->thread_attr.name = "BleGapWorker"; gap->thread_attr.stack_size = 1024; gap->thread_id = osThreadNew(gap_app, NULL, &gap->thread_attr); diff --git a/firmware/targets/f7/ble-glue/hw_conf.h b/firmware/targets/f7/ble-glue/hw_conf.h index dcda0176..9545238b 100644 --- a/firmware/targets/f7/ble-glue/hw_conf.h +++ b/firmware/targets/f7/ble-glue/hw_conf.h @@ -29,6 +29,37 @@ * Semaphores * THIS SHALL NO BE CHANGED AS THESE SEMAPHORES ARE USED AS WELL ON THE CM0+ *****************************************************************************/ +/** +* Index of the semaphore used the prevent conflicts after standby sleep. +* Each CPUs takes this semaphore at standby wakeup until conclicting elements are restored. +*/ +#define CFG_HW_PWR_STANDBY_SEMID 10 +/** +* The CPU2 may be configured to store the Thread persistent data either in internal NVM storage on CPU2 or in +* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() +* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. +* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: +* + CPU1 takes CFG_HW_THREAD_NVM_SRAM_SEMID semaphore +* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) +* + CPU1 releases CFG_HW_THREAD_NVM_SRAM_SEMID semaphore +* CFG_HW_THREAD_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. +* There is no timing constraint on how long this semaphore can be kept. +*/ +#define CFG_HW_THREAD_NVM_SRAM_SEMID 9 + +/** +* The CPU2 may be configured to store the BLE persistent data either in internal NVM storage on CPU2 or in +* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() +* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. +* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: +* + CPU1 takes CFG_HW_BLE_NVM_SRAM_SEMID semaphore +* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) +* + CPU1 releases CFG_HW_BLE_NVM_SRAM_SEMID semaphore +* CFG_HW_BLE_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. +* There is no timing constraint on how long this semaphore can be kept. +*/ +#define CFG_HW_BLE_NVM_SRAM_SEMID 8 + /** * Index of the semaphore used by CPU2 to prevent the CPU1 to either write or erase data in flash * The CPU1 shall not either write or erase in flash when this semaphore is taken by the CPU2 diff --git a/firmware/targets/f7/ble-glue/serial_service.c b/firmware/targets/f7/ble-glue/serial_service.c index 7de3529c..c7ea6db2 100644 --- a/firmware/targets/f7/ble-glue/serial_service.c +++ b/firmware/targets/f7/ble-glue/serial_service.c @@ -4,22 +4,27 @@ #include -#define SERIAL_SERVICE_TAG "serial service" +#define TAG "BtSerialSvc" typedef struct { uint16_t svc_handle; uint16_t rx_char_handle; uint16_t tx_char_handle; + uint16_t flow_ctrl_char_handle; + osMutexId_t buff_size_mtx; + uint32_t buff_size; + uint16_t bytes_ready_to_receive; SerialSvcDataReceivedCallback on_received_cb; SerialSvcDataSentCallback on_sent_cb; void* context; } SerialSvc; -static SerialSvc* serial_svc; +static SerialSvc* serial_svc = NULL; static const uint8_t service_uuid[] = {0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f}; -static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; static const uint8_t char_tx_uuid[] = {0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; +static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; +static const uint8_t flow_ctrl_uuid[] = {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; @@ -32,16 +37,26 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 2) { // Descriptor handle ret = SVCCTL_EvtAckFlowEnable; - FURI_LOG_D(SERIAL_SERVICE_TAG, "RX descriptor event"); + FURI_LOG_D(TAG, "RX descriptor event"); } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) { - FURI_LOG_D(SERIAL_SERVICE_TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); + FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); if(serial_svc->on_received_cb) { - serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context); + furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK); + if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) { + FURI_LOG_W( + TAG, "Received %d, while was ready to receive %d bytes. Can lead to buffer overflow!", + attribute_modified->Attr_Data_Length, serial_svc->bytes_ready_to_receive); + } + serial_svc->bytes_ready_to_receive -= MIN(serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length); + uint32_t buff_free_size = + serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context); + FURI_LOG_D(TAG, "Available buff size: %d", buff_free_size); + furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK); } ret = SVCCTL_EvtAckFlowEnable; } } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { - FURI_LOG_D(SERIAL_SERVICE_TAG, "Ack received", blecore_evt->ecode); + FURI_LOG_D(TAG, "Ack received", blecore_evt->ecode); if(serial_svc->on_sent_cb) { serial_svc->on_sent_cb(serial_svc->context); } @@ -58,9 +73,9 @@ void serial_svc_start() { SVCCTL_RegisterSvcHandler(serial_svc_event_handler); // Add service - status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 6, &serial_svc->svc_handle); + status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 10, &serial_svc->svc_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Serial service: %d", status); + FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); } // Add RX characteristics @@ -73,12 +88,12 @@ void serial_svc_start() { CHAR_VALUE_LEN_VARIABLE, &serial_svc->rx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add RX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to add RX characteristic: %d", status); } // Add TX characteristic status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)char_tx_uuid, - SERIAL_SVC_DATA_LEN_MAX, + SERIAL_SVC_DATA_LEN_MAX, CHAR_PROP_READ | CHAR_PROP_INDICATE, ATTR_PERMISSION_AUTHEN_READ, GATT_DONT_NOTIFY_EVENTS, @@ -86,14 +101,47 @@ void serial_svc_start() { CHAR_VALUE_LEN_VARIABLE, &serial_svc->tx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add TX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to add TX characteristic: %d", status); } + // Add Flow Control characteristic + status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)flow_ctrl_uuid, + sizeof(uint32_t), + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_AUTHEN_READ, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_CONSTANT, + &serial_svc->flow_ctrl_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add Flow Control characteristic: %d", status); + } + // Allocate buffer size mutex + serial_svc->buff_size_mtx = osMutexNew(NULL); } -void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { +void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { + furi_assert(serial_svc); serial_svc->on_received_cb = on_received_cb; serial_svc->on_sent_cb = on_sent_cb; serial_svc->context = context; + serial_svc->buff_size = buff_size; + serial_svc->bytes_ready_to_receive = buff_size; + uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); + aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed); +} + +void serial_svc_notify_buffer_is_empty() { + furi_assert(serial_svc); + furi_assert(serial_svc->buff_size_mtx); + + furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK); + if(serial_svc->bytes_ready_to_receive == 0) { + FURI_LOG_D(TAG, "Buffer is empty. Notifying client"); + serial_svc->bytes_ready_to_receive = serial_svc->buff_size; + uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); + aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed); + } + furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK); } void serial_svc_stop() { @@ -102,17 +150,23 @@ void serial_svc_stop() { // Delete characteristics status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->tx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete TX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to delete TX characteristic: %d", status); } status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rx_char_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete RX characteristic: %d", status); + FURI_LOG_E(TAG, "Failed to delete RX characteristic: %d", status); + } + status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Flow Control characteristic: %d", status); } // Delete service status = aci_gatt_del_service(serial_svc->svc_handle); if(status) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Serial service: %d", status); + FURI_LOG_E(TAG, "Failed to delete Serial service: %d", status); } + // Delete buffer size mutex + osMutexDelete(serial_svc->buff_size_mtx); free(serial_svc); serial_svc = NULL; } @@ -122,14 +176,13 @@ bool serial_svc_update_tx(uint8_t* data, uint8_t data_len) { if(data_len > SERIAL_SVC_DATA_LEN_MAX) { return false; } - FURI_LOG_D(SERIAL_SERVICE_TAG, "Updating char %d len", data_len); tBleStatus result = aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->tx_char_handle, 0, data_len, data); if(result) { - FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed updating TX characteristic: %d", result); + FURI_LOG_E(TAG, "Failed updating TX characteristic: %d", result); } return result != BLE_STATUS_SUCCESS; } diff --git a/firmware/targets/f7/ble-glue/serial_service.h b/firmware/targets/f7/ble-glue/serial_service.h index b5a1c078..0aa4c79f 100644 --- a/firmware/targets/f7/ble-glue/serial_service.h +++ b/firmware/targets/f7/ble-glue/serial_service.h @@ -9,12 +9,14 @@ extern "C" { #endif -typedef void(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context); +typedef uint16_t(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context); typedef void(*SerialSvcDataSentCallback)(void* context); void serial_svc_start(); -void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); +void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); + +void serial_svc_notify_buffer_is_empty(); void serial_svc_stop(); diff --git a/firmware/targets/f7/Src/fatfs/fatfs.c b/firmware/targets/f7/fatfs/fatfs.c similarity index 100% rename from firmware/targets/f7/Src/fatfs/fatfs.c rename to firmware/targets/f7/fatfs/fatfs.c diff --git a/firmware/targets/f7/Src/fatfs/fatfs.h b/firmware/targets/f7/fatfs/fatfs.h similarity index 100% rename from firmware/targets/f7/Src/fatfs/fatfs.h rename to firmware/targets/f7/fatfs/fatfs.h diff --git a/firmware/targets/f7/Src/fatfs/ffconf.h b/firmware/targets/f7/fatfs/ffconf.h similarity index 100% rename from firmware/targets/f7/Src/fatfs/ffconf.h rename to firmware/targets/f7/fatfs/ffconf.h diff --git a/firmware/targets/f7/Src/fatfs/spi_sd_hal.c b/firmware/targets/f7/fatfs/spi_sd_hal.c similarity index 100% rename from firmware/targets/f7/Src/fatfs/spi_sd_hal.c rename to firmware/targets/f7/fatfs/spi_sd_hal.c diff --git a/firmware/targets/f7/Src/fatfs/stm32_adafruit_sd.c b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c similarity index 100% rename from firmware/targets/f7/Src/fatfs/stm32_adafruit_sd.c rename to firmware/targets/f7/fatfs/stm32_adafruit_sd.c diff --git a/firmware/targets/f7/Src/fatfs/stm32_adafruit_sd.h b/firmware/targets/f7/fatfs/stm32_adafruit_sd.h similarity index 100% rename from firmware/targets/f7/Src/fatfs/stm32_adafruit_sd.h rename to firmware/targets/f7/fatfs/stm32_adafruit_sd.h diff --git a/firmware/targets/f7/Src/fatfs/syscall.c b/firmware/targets/f7/fatfs/syscall.c similarity index 100% rename from firmware/targets/f7/Src/fatfs/syscall.c rename to firmware/targets/f7/fatfs/syscall.c diff --git a/firmware/targets/f7/Src/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c similarity index 100% rename from firmware/targets/f7/Src/fatfs/user_diskio.c rename to firmware/targets/f7/fatfs/user_diskio.c diff --git a/firmware/targets/f7/Src/fatfs/user_diskio.h b/firmware/targets/f7/fatfs/user_diskio.h similarity index 100% rename from firmware/targets/f7/Src/fatfs/user_diskio.h rename to firmware/targets/f7/fatfs/user_diskio.h diff --git a/firmware/targets/f7/furi-hal/furi-hal-boot.c b/firmware/targets/f7/furi-hal/furi-hal-bootloader.c similarity index 57% rename from firmware/targets/f7/furi-hal/furi-hal-boot.c rename to firmware/targets/f7/furi-hal/furi-hal-bootloader.c index 978711c5..e8ea913e 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-boot.c +++ b/firmware/targets/f7/furi-hal/furi-hal-bootloader.c @@ -1,31 +1,33 @@ -#include +#include #include #include +#define TAG "FuriHalBoot" + // Boot request enum #define BOOT_REQUEST_TAINTED 0x00000000 #define BOOT_REQUEST_CLEAN 0xDADEDADE #define BOOT_REQUEST_DFU 0xDF00B000 -void furi_hal_boot_init() { +void furi_hal_bootloader_init() { #ifndef DEBUG LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_TAINTED); #endif - FURI_LOG_I("FuriHalBoot", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_boot_set_mode(FuriHalBootMode mode) { - if (mode == FuriHalBootModeNormal) { +void furi_hal_bootloader_set_mode(FuriHalBootloaderMode mode) { + if (mode == FuriHalBootloaderModeNormal) { LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_CLEAN); - } else if (mode == FuriHalBootModeDFU) { + } else if (mode == FuriHalBootloaderModeDFU) { LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_DFU); } } -void furi_hal_boot_set_flags(FuriHalBootFlag flags) { +void furi_hal_bootloader_set_flags(FuriHalBootloaderFlag flags) { LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR2, flags); } -FuriHalBootFlag furi_hal_boot_get_flags() { +FuriHalBootloaderFlag furi_hal_bootloader_get_flags() { return LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR2); } \ No newline at end of file diff --git a/firmware/targets/f7/furi-hal/furi-hal-bt.c b/firmware/targets/f7/furi-hal/furi-hal-bt.c index 97db87c7..b74a9e29 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-bt.c +++ b/firmware/targets/f7/furi-hal/furi-hal-bt.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -7,11 +6,42 @@ #include +#define TAG "FuriHalBt" + +osMutexId_t furi_hal_bt_core2_mtx = NULL; + void furi_hal_bt_init() { + furi_hal_bt_core2_mtx = osMutexNew(NULL); + furi_assert(furi_hal_bt_core2_mtx); + // Explicitly tell that we are in charge of CLK48 domain HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); - // Start Core2, init HCI and start GAP/GATT - APPE_Init(); + + // Start Core2 + ble_glue_init(); +} + +void furi_hal_bt_lock_core2() { + furi_assert(furi_hal_bt_core2_mtx); + furi_check(osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever) == osOK); +} + +void furi_hal_bt_unlock_core2() { + furi_assert(furi_hal_bt_core2_mtx); + furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); +} + +bool furi_hal_bt_start_core2() { + furi_assert(furi_hal_bt_core2_mtx); + + osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); + // Explicitly tell that we are in charge of CLK48 domain + HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + // Start Core2 + bool ret = ble_glue_start(); + osMutexRelease(furi_hal_bt_core2_mtx); + + return ret; } bool furi_hal_bt_init_app(BleEventCallback event_cb, void* context) { @@ -34,8 +64,12 @@ void furi_hal_bt_stop_advertising() { } } -void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { - serial_svc_set_callbacks(on_received_cb, on_sent_cb, context); +void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { + serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context); +} + +void furi_hal_bt_notify_buffer_is_empty() { + serial_svc_notify_buffer_is_empty(); } bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { @@ -45,9 +79,27 @@ bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { return serial_svc_update_tx(data, size); } +void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { + ble_app_get_key_storage_buff(key_buff_addr, key_buff_size); +} + +void furi_hal_bt_set_key_storage_change_callback(BleGlueKeyStorageChangedCallback callback, void* context) { + furi_assert(callback); + ble_glue_set_key_storage_changed_callback(callback, context); +} + +void furi_hal_bt_nvm_sram_sem_acquire() { + while(HAL_HSEM_FastTake(CFG_HW_BLE_NVM_SRAM_SEMID) != HAL_OK) { + osDelay(1); + } +} + +void furi_hal_bt_nvm_sram_sem_release() { + HAL_HSEM_Release(CFG_HW_BLE_NVM_SRAM_SEMID, 0); +} + void furi_hal_bt_dump_state(string_t buffer) { - BleGlueStatus status = APPE_Status(); - if (status == BleGlueStatusStarted) { + if (furi_hal_bt_is_alive()) { uint8_t HCI_Version; uint16_t HCI_Revision; uint8_t LMP_PAL_Version; @@ -68,58 +120,13 @@ void furi_hal_bt_dump_state(string_t buffer) { } bool furi_hal_bt_is_alive() { - BleGlueStatus status = APPE_Status(); - return (status == BleGlueStatusBroken) || (status == BleGlueStatusStarted); + return ble_glue_is_alive(); } bool furi_hal_bt_is_active() { return gap_get_state() > GapStateIdle; } -bool furi_hal_bt_wait_startup() { - uint16_t counter = 0; - while (!(APPE_Status() == BleGlueStatusStarted || APPE_Status() == BleGlueStatusBroken)) { - osDelay(10); - counter++; - if (counter > 1000) { - return false; - } - } - return true; -} - -bool furi_hal_bt_lock_flash(bool erase_flag) { - if (!furi_hal_bt_wait_startup()) { - return false; - } - - while (HAL_HSEM_FastTake(CFG_HW_FLASH_SEMID) != HAL_OK) { - osDelay(1); - } - - HAL_FLASH_Unlock(); - - if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); - - while(LL_FLASH_IsActiveFlag_OperationSuspended()) { - osDelay(1); - }; - - __disable_irq(); - - return true; -} - -void furi_hal_bt_unlock_flash(bool erase_flag) { - __enable_irq(); - - if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); - - HAL_FLASH_Lock(); - - HAL_HSEM_Release(CFG_HW_FLASH_SEMID, HSEM_CPU1_COREID); -} - void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) { aci_hal_set_tx_power_level(0, power); aci_hal_tone_start(channel, 0); diff --git a/firmware/targets/f7/furi-hal/furi-hal-clock.c b/firmware/targets/f7/furi-hal/furi-hal-clock.c index 2544c769..7a124049 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-clock.c +++ b/firmware/targets/f7/furi-hal/furi-hal-clock.c @@ -5,6 +5,8 @@ #include #include +#define TAG "FuriHalClock" + #define HS_CLOCK_IS_READY() (LL_RCC_HSE_IsReady() && LL_RCC_HSI_IsReady()) #define LS_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) @@ -84,6 +86,7 @@ void furi_hal_clock_init() { LL_RCC_EnableRTC(); LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK2); + LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_PLLSAI1); LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_CLK48); @@ -117,11 +120,12 @@ void furi_hal_clock_init() { // APB1 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); + LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPUART1); // APB2 LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1); - FURI_LOG_I("FuriHalClock", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_clock_switch_to_hsi() { diff --git a/firmware/targets/f7/furi-hal/furi-hal-compress.c b/firmware/targets/f7/furi-hal/furi-hal-compress.c index 9b7678f5..eb6e9d51 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-compress.c +++ b/firmware/targets/f7/furi-hal/furi-hal-compress.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalCompress" + #define FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE (512) #define FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE (1024) @@ -46,7 +48,7 @@ void furi_hal_compress_icon_init() { FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); heatshrink_decoder_reset(icon_decoder->decoder); memset(icon_decoder->decoded_buff, 0, sizeof(icon_decoder->decoded_buff)); - FURI_LOG_I("FuriHalCompress", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff) { diff --git a/firmware/targets/f7/furi-hal/furi-hal-console.c b/firmware/targets/f7/furi-hal/furi-hal-console.c index 552f9e77..993b498e 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-console.c +++ b/firmware/targets/f7/furi-hal/furi-hal-console.c @@ -1,131 +1,64 @@ #include -#include +#include #include #include #include #include +#include + #include +#define TAG "FuriHalConsole" + #define CONSOLE_BAUDRATE 230400 volatile bool furi_hal_console_alive = false; -static void (*irq_cb)(uint8_t ev, uint8_t data); - void furi_hal_console_init() { - LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = LL_GPIO_PIN_6|LL_GPIO_PIN_7; - GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; - GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - GPIO_InitStruct.Alternate = LL_GPIO_AF_7; - LL_GPIO_Init(GPIOB, &GPIO_InitStruct); - - LL_USART_InitTypeDef USART_InitStruct = {0}; - USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; - USART_InitStruct.BaudRate = CONSOLE_BAUDRATE; - USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; - USART_InitStruct.StopBits = LL_USART_STOPBITS_1; - USART_InitStruct.Parity = LL_USART_PARITY_NONE; - USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; - USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; - USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; - LL_USART_Init(USART1, &USART_InitStruct); - LL_USART_SetTXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_2); - LL_USART_EnableFIFO(USART1); - LL_USART_ConfigAsyncMode(USART1); - - LL_USART_Enable(USART1); - - while(!LL_USART_IsActiveFlag_TEACK(USART1)) ; - - LL_USART_EnableIT_RXNE_RXFNE(USART1); - LL_USART_EnableIT_IDLE(USART1); - HAL_NVIC_SetPriority(USART1_IRQn, 5, 0); - + furi_hal_uart_init(FuriHalUartIdUSART1, CONSOLE_BAUDRATE); furi_hal_console_alive = true; - FURI_LOG_I("FuriHalConsole", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_usart_init() { - furi_hal_console_alive = false; -} - -void furi_hal_usart_set_br(uint32_t baud) { - if (LL_USART_IsEnabled(USART1)) { - // Wait for transfer complete flag - while (!LL_USART_IsActiveFlag_TC(USART1)); - LL_USART_Disable(USART1); - uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); - LL_USART_SetBaudRate(USART1, uartclk, LL_USART_PRESCALER_DIV1, LL_USART_OVERSAMPLING_16, baud); - LL_USART_Enable(USART1); - } -} - -void furi_hal_usart_deinit() { +void furi_hal_console_enable() { + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL); while (!LL_USART_IsActiveFlag_TC(USART1)); - furi_hal_usart_set_br(CONSOLE_BAUDRATE); + furi_hal_uart_set_br(FuriHalUartIdUSART1, CONSOLE_BAUDRATE); furi_hal_console_alive = true; } -void furi_hal_usart_tx(const uint8_t* buffer, size_t buffer_size) { - if (LL_USART_IsEnabled(USART1) == 0) - return; - - while(buffer_size > 0) { - while (!LL_USART_IsActiveFlag_TXE(USART1)); - - LL_USART_TransmitData8(USART1, *buffer); - - buffer++; - buffer_size--; - } -} - -void furi_hal_usart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)) { - irq_cb = cb; - if (irq_cb == NULL) - NVIC_DisableIRQ(USART1_IRQn); - else - NVIC_EnableIRQ(USART1_IRQn); -} - -void USART1_IRQHandler(void) { - if (LL_USART_IsActiveFlag_RXNE_RXFNE(USART1)) { - uint8_t data = LL_USART_ReceiveData8(USART1); - irq_cb(UartIrqEventRXNE, data); - } else if (LL_USART_IsActiveFlag_IDLE(USART1)) { - irq_cb(UartIrqEventIDLE, 0); - LL_USART_ClearFlag_IDLE(USART1); - } - - //TODO: more events +void furi_hal_console_disable() { + while (!LL_USART_IsActiveFlag_TC(USART1)); + furi_hal_console_alive = false; } void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size) { if (!furi_hal_console_alive) return; + UTILS_ENTER_CRITICAL_SECTION(); // Transmit data - furi_hal_usart_tx(buffer, buffer_size); + furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size); // Wait for TC flag to be raised for last char while (!LL_USART_IsActiveFlag_TC(USART1)); + UTILS_EXIT_CRITICAL_SECTION(); } void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size) { if (!furi_hal_console_alive) return; + UTILS_ENTER_CRITICAL_SECTION(); // Transmit data - furi_hal_usart_tx(buffer, buffer_size); + furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size); // Transmit new line symbols - furi_hal_usart_tx((const uint8_t*)"\r\n", 2); + furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)"\r\n", 2); // Wait for TC flag to be raised for last char while (!LL_USART_IsActiveFlag_TC(USART1)); + UTILS_EXIT_CRITICAL_SECTION(); } void furi_hal_console_printf(const char format[], ...) { @@ -140,4 +73,4 @@ void furi_hal_console_printf(const char format[], ...) { void furi_hal_console_puts(const char *data) { furi_hal_console_tx((const uint8_t*)data, strlen(data)); -} \ No newline at end of file +} diff --git a/firmware/targets/f7/furi-hal/furi-hal-console.h b/firmware/targets/f7/furi-hal/furi-hal-console.h index 013653ba..637c17f6 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-console.h +++ b/firmware/targets/f7/furi-hal/furi-hal-console.h @@ -7,14 +7,12 @@ extern "C" { #endif -typedef enum { - UartIrqEventRXNE, - UartIrqEventIDLE, - //TODO: more events -} UartIrqEvent; - void furi_hal_console_init(); +void furi_hal_console_enable(); + +void furi_hal_console_disable(); + void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size); void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size); @@ -29,18 +27,6 @@ void furi_hal_console_printf(const char format[], ...); void furi_hal_console_puts(const char* data); - -void furi_hal_usart_init(); - -void furi_hal_usart_deinit(); - -void furi_hal_usart_set_br(uint32_t baud); - -void furi_hal_usart_tx(const uint8_t* buffer, size_t buffer_size); - -void furi_hal_usart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)); - - #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi-hal/furi-hal-crypto.c b/firmware/targets/f7/furi-hal/furi-hal-crypto.c index 3e4ec98f..91875d23 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-crypto.c +++ b/firmware/targets/f7/furi-hal/furi-hal-crypto.c @@ -1,35 +1,42 @@ #include +#include #include #include +#define TAG "FuriHalCrypto" + CRYP_HandleTypeDef crypt; void furi_hal_crypto_init() { - FURI_LOG_I("FuriHalCrypto", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot) { furi_assert(key); furi_assert(slot); + if(!furi_hal_bt_is_alive()) { + return false; + } + SHCI_C2_FUS_StoreUsrKey_Cmd_Param_t pParam; size_t key_data_size = 0; - if (key->type == FuriHalCryptoKeyTypeMaster) { + if(key->type == FuriHalCryptoKeyTypeMaster) { pParam.KeyType = KEYTYPE_MASTER; - } else if (key->type == FuriHalCryptoKeyTypeSimple) { + } else if(key->type == FuriHalCryptoKeyTypeSimple) { pParam.KeyType = KEYTYPE_SIMPLE; - } else if (key->type == FuriHalCryptoKeyTypeEncrypted) { + } else if(key->type == FuriHalCryptoKeyTypeEncrypted) { pParam.KeyType = KEYTYPE_ENCRYPTED; key_data_size += 12; } else { furi_crash("Incorrect key type"); } - if (key->size == FuriHalCryptoKeySize128) { + if(key->size == FuriHalCryptoKeySize128) { pParam.KeySize = KEYSIZE_16; key_data_size += 16; - } else if (key->size == FuriHalCryptoKeySize256) { + } else if(key->size == FuriHalCryptoKeySize256) { pParam.KeySize = KEYSIZE_32; key_data_size += 32; } else { @@ -44,16 +51,21 @@ bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot) { bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { furi_assert(slot > 0 && slot <= 100); + if(!furi_hal_bt_is_alive()) { + return false; + } + crypt.Instance = AES1; crypt.Init.DataType = CRYP_DATATYPE_32B; crypt.Init.KeySize = CRYP_KEYSIZE_256B; crypt.Init.Algorithm = CRYP_AES_CBC; crypt.Init.pInitVect = (uint32_t*)iv; + crypt.Init.KeyIVConfigSkip = CRYP_KEYIVCONFIG_ONCE; crypt.Init.pKey = NULL; furi_check(HAL_CRYP_Init(&crypt) == HAL_OK); - if (SHCI_C2_FUS_LoadUsrKey(slot) == SHCI_Success) { + if(SHCI_C2_FUS_LoadUsrKey(slot) == SHCI_Success) { return true; } else { furi_check(HAL_CRYP_DeInit(&crypt) == HAL_OK); @@ -62,14 +74,18 @@ bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { } bool furi_hal_crypto_store_unload_key(uint8_t slot) { + if(!furi_hal_bt_is_alive()) { + return false; + } + furi_check(HAL_CRYP_DeInit(&crypt) == HAL_OK); return SHCI_C2_FUS_UnloadUsrKey(slot) == SHCI_Success; } -bool furi_hal_crypto_encrypt(const uint8_t *input, uint8_t *output, size_t size) { - return HAL_CRYP_Encrypt(&crypt, (uint32_t*)input, size/4, (uint32_t*)output, 1000) == HAL_OK; +bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) { + return HAL_CRYP_Encrypt(&crypt, (uint32_t*)input, size / 4, (uint32_t*)output, 1000) == HAL_OK; } -bool furi_hal_crypto_decrypt(const uint8_t *input, uint8_t *output, size_t size) { - return HAL_CRYP_Decrypt(&crypt, (uint32_t*)input, size/4, (uint32_t*)output, 1000) == HAL_OK; +bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) { + return HAL_CRYP_Decrypt(&crypt, (uint32_t*)input, size / 4, (uint32_t*)output, 1000) == HAL_OK; } diff --git a/firmware/targets/f7/furi-hal/furi-hal-delay.c b/firmware/targets/f7/furi-hal/furi-hal-delay.c index 52de8715..b5f3c334 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-delay.c +++ b/firmware/targets/f7/furi-hal/furi-hal-delay.c @@ -3,6 +3,8 @@ #include #include +#define TAG "FuriHalDelay" + static uint32_t clk_per_microsecond; void furi_hal_delay_init(void) { @@ -10,7 +12,7 @@ void furi_hal_delay_init(void) { DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0U; clk_per_microsecond = SystemCoreClock / 1000000.0f; - FURI_LOG_I("FuriHalDelay", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void delay_us(float microseconds) { diff --git a/firmware/targets/f7/furi-hal/furi-hal-flash.c b/firmware/targets/f7/furi-hal/furi-hal-flash.c index c9922122..156a26a9 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-flash.c +++ b/firmware/targets/f7/furi-hal/furi-hal-flash.c @@ -1,16 +1,21 @@ #include #include -#include #include +#include +#include -/* Free flash space borders, exported by linker */ -extern const void __free_flash_start__; +#include +#define FURI_HAL_TAG "FuriHalFlash" +#define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" #define FURI_HAL_FLASH_READ_BLOCK 8 #define FURI_HAL_FLASH_WRITE_BLOCK 8 #define FURI_HAL_FLASH_PAGE_SIZE 4096 #define FURI_HAL_FLASH_CYCLES_COUNT 10000 +/* Free flash space borders, exported by linker */ +extern const void __free_flash_start__; + size_t furi_hal_flash_get_base() { return FLASH_BASE; } @@ -36,9 +41,9 @@ const void* furi_hal_flash_get_free_start_address() { } const void* furi_hal_flash_get_free_end_address() { - FLASH_OBProgramInitTypeDef pOBInit; - HAL_FLASHEx_OBGetConfig(&pOBInit); - return (const void *)pOBInit.SecureFlashStartAddr; + uint32_t sfr_reg_val = READ_REG(FLASH->SFR); + uint32_t sfsa = (READ_BIT(sfr_reg_val, FLASH_SFR_SFSA) >> FLASH_SFR_SFSA_Pos); + return (const void *)((sfsa * FLASH_PAGE_SIZE) + FLASH_BASE); } size_t furi_hal_flash_get_free_page_start_address() { @@ -56,34 +61,239 @@ size_t furi_hal_flash_get_free_page_count() { return (end-page_start) / FURI_HAL_FLASH_PAGE_SIZE; } -bool furi_hal_flash_erase(uint8_t page, uint8_t count) { - if (!furi_hal_bt_lock_flash(true)) { - return false; +static void furi_hal_flash_unlock() { + /* verify Flash is locked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U); + + /* Authorize the FLASH Registers access */ + WRITE_REG(FLASH->KEYR, FLASH_KEY1); + WRITE_REG(FLASH->KEYR, FLASH_KEY2); + + /* verify Flash is unlock */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); +} + +static void furi_hal_flash_lock(void) { + /* verify Flash is unlocked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); + + /* Set the LOCK Bit to lock the FLASH Registers access */ + /* @Note The lock and unlock procedure is done only using CR registers even from CPU2 */ + SET_BIT(FLASH->CR, FLASH_CR_LOCK); + + /* verify Flash is locked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U); +} + +static void furi_hal_flash_begin_with_core2(bool erase_flag) { + // Take flash controller ownership + while (HAL_HSEM_FastTake(CFG_HW_FLASH_SEMID) != HAL_OK) { + taskYIELD(); } - FLASH_EraseInitTypeDef erase; - erase.TypeErase = FLASH_TYPEERASE_PAGES; - erase.Page = page; - erase.NbPages = count; - uint32_t error; - HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase, &error); - furi_hal_bt_unlock_flash(true); - return status == HAL_OK; + + // Unlock flash operation + furi_hal_flash_unlock(); + + // Erase activity notification + if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); + + while(true) { + // Wait till flash controller become usable + while(LL_FLASH_IsActiveFlag_OperationSuspended()) { + taskYIELD(); + }; + + // Just a little more love + taskENTER_CRITICAL(); + + // Actually we already have mutex for it, but specification is specification + if (HAL_HSEM_IsSemTaken(CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) { + taskEXIT_CRITICAL(); + continue; + } + + // Take sempahopre and prevent core2 from anyting funky + if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { + taskEXIT_CRITICAL(); + continue; + } + + break; + } +} + +static void furi_hal_flash_begin(bool erase_flag) { + // Acquire dangerous ops mutex + furi_hal_bt_lock_core2(); + + // If Core2 is running use IPC locking + if(furi_hal_bt_is_alive()) { + furi_hal_flash_begin_with_core2(erase_flag); + } else { + furi_hal_flash_unlock(); + } +} + +static void furi_hal_flash_end_with_core2(bool erase_flag) { + // Funky ops are ok at this point + HAL_HSEM_Release(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0); + + // Task switching is ok + taskEXIT_CRITICAL(); + + // Doesn't make much sense, does it? + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) { + taskYIELD(); + } + + // Erase activity over, core2 can continue + if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); + + // Lock flash controller + furi_hal_flash_lock(); + + // Release flash controller ownership + HAL_HSEM_Release(CFG_HW_FLASH_SEMID, 0); +} + +static void furi_hal_flash_end(bool erase_flag) { + // If Core2 is running use IPC locking + if(furi_hal_bt_is_alive()) { + furi_hal_flash_end_with_core2(erase_flag); + } else { + furi_hal_flash_lock(); + } + + // Release dangerous ops mutex + furi_hal_bt_unlock_core2(); +} + +static void furi_hal_flush_cache(void) { + /* Flush instruction cache */ + if (READ_BIT(FLASH->ACR, FLASH_ACR_ICEN) == FLASH_ACR_ICEN) { + /* Disable instruction cache */ + __HAL_FLASH_INSTRUCTION_CACHE_DISABLE(); + /* Reset instruction cache */ + __HAL_FLASH_INSTRUCTION_CACHE_RESET(); + /* Enable instruction cache */ + __HAL_FLASH_INSTRUCTION_CACHE_ENABLE(); + } + + /* Flush data cache */ + if (READ_BIT(FLASH->ACR, FLASH_ACR_DCEN) == FLASH_ACR_DCEN) { + /* Disable data cache */ + __HAL_FLASH_DATA_CACHE_DISABLE(); + /* Reset data cache */ + __HAL_FLASH_DATA_CACHE_RESET(); + /* Enable data cache */ + __HAL_FLASH_DATA_CACHE_ENABLE(); + } +} + +HAL_StatusTypeDef furi_hal_flash_wait_last_operation(uint32_t timeout) { + uint32_t error = 0; + uint32_t countdown = 0; + + // Wait for the FLASH operation to complete by polling on BUSY flag to be reset. + // Even if the FLASH operation fails, the BUSY flag will be reset and an error + // flag will be set + countdown = timeout; + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) { + if(LL_SYSTICK_IsActiveCounterFlag()) { + countdown--; + } + if (countdown == 0) { + return HAL_TIMEOUT; + } + } + + /* Check FLASH operation error flags */ + error = FLASH->SR; + + /* Check FLASH End of Operation flag */ + if ((error & FLASH_FLAG_EOP) != 0U) { + /* Clear FLASH End of Operation pending bit */ + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP); + } + + /* Now update error variable to only error value */ + error &= FLASH_FLAG_SR_ERRORS; + + furi_check(error == 0); + + /* clear error flags */ + __HAL_FLASH_CLEAR_FLAG(error); + + /* Wait for control register to be written */ + countdown = timeout; + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_CFGBSY)) { + if(LL_SYSTICK_IsActiveCounterFlag()) { + countdown--; + } + if (countdown == 0) { + return HAL_TIMEOUT; + } + } + + return HAL_OK; +} + +bool furi_hal_flash_erase(uint8_t page) { + furi_hal_flash_begin(true); + + // Ensure that controller state is valid + furi_check(FLASH->SR == 0); + + /* Verify that next operation can be proceed */ + furi_check(furi_hal_flash_wait_last_operation(FLASH_TIMEOUT_VALUE) == HAL_OK); + + /* Select page and start operation */ + MODIFY_REG(FLASH->CR, FLASH_CR_PNB, ((page << FLASH_CR_PNB_Pos) | FLASH_CR_PER | FLASH_CR_STRT)); + + /* Wait for last operation to be completed */ + furi_check(furi_hal_flash_wait_last_operation(FLASH_TIMEOUT_VALUE) == HAL_OK); + + /* If operation is completed or interrupted, disable the Page Erase Bit */ + CLEAR_BIT(FLASH->CR, (FLASH_CR_PER | FLASH_CR_PNB)); + + /* Flush the caches to be sure of the data consistency */ + furi_hal_flush_cache(); + + furi_hal_flash_end(true); + + return true; } bool furi_hal_flash_write_dword(size_t address, uint64_t data) { - if (!furi_hal_bt_lock_flash(false)) { - return false; - } - HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data); - furi_hal_bt_unlock_flash(false); - return status == HAL_OK; -} + furi_hal_flash_begin(false); -bool furi_hal_flash_write_dword_from(size_t address, size_t source_address) { - if (!furi_hal_bt_lock_flash(false)) { - return false; - } - HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, address, source_address); - furi_hal_bt_unlock_flash(false); - return status == HAL_OK; + // Ensure that controller state is valid + furi_check(FLASH->SR == 0); + + /* Check the parameters */ + furi_check(IS_ADDR_ALIGNED_64BITS(address)); + furi_check(IS_FLASH_PROGRAM_ADDRESS(address)); + + /* Set PG bit */ + SET_BIT(FLASH->CR, FLASH_CR_PG); + + /* Program first word */ + *(uint32_t *)address = (uint32_t)data; + + // Barrier to ensure programming is performed in 2 steps, in right order + // (independently of compiler optimization behavior) + __ISB(); + + /* Program second word */ + *(uint32_t *)(address + 4U) = (uint32_t)(data >> 32U); + + /* Wait for last operation to be completed */ + furi_check(furi_hal_flash_wait_last_operation(FLASH_TIMEOUT_VALUE) == HAL_OK); + + /* If the program operation is completed, disable the PG or FSTPG Bit */ + CLEAR_BIT(FLASH->CR, FLASH_CR_PG); + + furi_hal_flash_end(false); + + return true; } diff --git a/firmware/targets/f7/furi-hal/furi-hal-flash.h b/firmware/targets/f7/furi-hal/furi-hal-flash.h index 583d53eb..3d8031e6 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-flash.h +++ b/firmware/targets/f7/furi-hal/furi-hal-flash.h @@ -60,18 +60,17 @@ size_t furi_hal_flash_get_free_page_count(); /** Erase Flash * - * Locking operation, uses HSEM to manage shared access. + * @warning locking operation with critical section, stales execution * - * @param page page number - * @param count page count to erase + * @param page The page to erase * * @return true on success */ -bool furi_hal_flash_erase(uint8_t page, uint8_t count); +bool furi_hal_flash_erase(uint8_t page); /** Write double word (64 bits) * - * Locking operation, uses HSEM to manage shared access. + * @warning locking operation with critical section, stales execution * * @param address destination address, must be double word aligned. * @param data data to write @@ -80,13 +79,3 @@ bool furi_hal_flash_erase(uint8_t page, uint8_t count); */ bool furi_hal_flash_write_dword(size_t address, uint64_t data); -/** Write double word (64 bits) from address - * - * Locking operation, uses HSEM to manage shared access. - * - * @param address destination address, must be block aligned - * @param source_address source address - * - * @return true on success - */ -bool furi_hal_flash_write_dword_from(size_t address, size_t source_address); diff --git a/firmware/targets/f7/furi-hal/furi-hal-i2c.c b/firmware/targets/f7/furi-hal/furi-hal-i2c.c index 196a2a7b..b1ec4711 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-i2c.c +++ b/firmware/targets/f7/furi-hal/furi-hal-i2c.c @@ -6,6 +6,8 @@ #include #include +#define TAG "FuriHalI2C" + osMutexId_t furi_hal_i2c_mutex = NULL; void furi_hal_i2c_init() { @@ -42,7 +44,7 @@ void furi_hal_i2c_init() { LL_I2C_DisableOwnAddress2(POWER_I2C); LL_I2C_DisableGeneralCall(POWER_I2C); LL_I2C_EnableClockStretching(POWER_I2C); - FURI_LOG_I("FuriHalI2C", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } bool furi_hal_i2c_tx( diff --git a/firmware/targets/f7/furi-hal/furi-hal-interrupt.c b/firmware/targets/f7/furi-hal/furi-hal-interrupt.c index 47e99c9f..2685edab 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-interrupt.c +++ b/firmware/targets/f7/furi-hal/furi-hal-interrupt.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalInterrupt" + volatile FuriHalInterruptISR furi_hal_tim_tim2_isr = NULL; volatile FuriHalInterruptISR furi_hal_tim_tim1_isr = NULL; @@ -22,7 +24,7 @@ void furi_hal_interrupt_init() { NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(DMA1_Channel1_IRQn); - FURI_LOG_I("FuriHalInterrupt", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_interrupt_set_timer_isr(TIM_TypeDef* timer, FuriHalInterruptISR isr) { @@ -161,10 +163,10 @@ void TAMP_STAMP_LSECSS_IRQHandler(void) { if (LL_RCC_IsActiveFlag_LSECSS()) { LL_RCC_ClearFlag_LSECSS(); if (!LL_RCC_LSE_IsReady()) { - FURI_LOG_E("FuriHalInterrupt", "LSE CSS fired: resetting system"); + FURI_LOG_E(TAG, "LSE CSS fired: resetting system"); NVIC_SystemReset(); } else { - FURI_LOG_E("FuriHalInterrupt", "LSE CSS fired: but LSE is alive"); + FURI_LOG_E(TAG, "LSE CSS fired: but LSE is alive"); } } } @@ -176,7 +178,7 @@ void RCC_IRQHandler(void) { void NMI_Handler(void) { if (LL_RCC_IsActiveFlag_HSECSS()) { LL_RCC_ClearFlag_HSECSS(); - FURI_LOG_E("FuriHalInterrupt", "HSE CSS fired: resetting system"); + FURI_LOG_E(TAG, "HSE CSS fired: resetting system"); NVIC_SystemReset(); } } diff --git a/firmware/targets/f7/furi-hal/furi-hal-light.c b/firmware/targets/f7/furi-hal/furi-hal-light.c index fba1bec4..ecf0d4f2 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-light.c +++ b/firmware/targets/f7/furi-hal/furi-hal-light.c @@ -1,6 +1,8 @@ #include #include +#define TAG "FuriHalLight" + #define LED_CURRENT_RED 50 #define LED_CURRENT_GREEN 50 #define LED_CURRENT_BLUE 50 @@ -21,7 +23,7 @@ void furi_hal_light_init() { lp5562_enable(); lp5562_configure(); - FURI_LOG_I("FuriHalLight", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_light_set(Light light, uint8_t value) { diff --git a/firmware/targets/f7/furi-hal/furi-hal-lock.c b/firmware/targets/f7/furi-hal/furi-hal-lock.c new file mode 100644 index 00000000..0f519380 --- /dev/null +++ b/firmware/targets/f7/furi-hal/furi-hal-lock.c @@ -0,0 +1,17 @@ +#include "furi-hal-lock.h" +#include + +#define FLIPPER_LOCKED_VALUE 0x5432FAFA + +bool furi_hal_lock_get() { + return FLIPPER_LOCKED_VALUE == LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR3); +} + +void furi_hal_lock_set(bool locked) { + if (locked) { + LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR3, FLIPPER_LOCKED_VALUE); + } else { + LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR3, 0); + } +} + diff --git a/firmware/targets/f7/furi-hal/furi-hal-lpuart.c b/firmware/targets/f7/furi-hal/furi-hal-lpuart.c deleted file mode 100644 index 31aa8b86..00000000 --- a/firmware/targets/f7/furi-hal/furi-hal-lpuart.c +++ /dev/null @@ -1,105 +0,0 @@ -#include -#include -#include -#include - -#include - -static void (*irq_cb)(uint8_t ev, uint8_t data); - -void furi_hal_lpuart_init() { - LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = PC0_Pin|PC1_Pin; - GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; - GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - GPIO_InitStruct.Alternate = LL_GPIO_AF_8; - LL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); - LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPUART1); - - LL_LPUART_InitTypeDef LPUART_InitStruct = {0}; - LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; - LPUART_InitStruct.BaudRate = 115200; - LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; - LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1; - LPUART_InitStruct.Parity = LL_LPUART_PARITY_NONE; - LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX; - LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE; - LL_LPUART_Init(LPUART1, &LPUART_InitStruct); - LL_LPUART_SetTXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); - LL_LPUART_SetRXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); - LL_LPUART_EnableFIFO(LPUART1); - - LL_LPUART_Enable(LPUART1); - - while((!(LL_LPUART_IsActiveFlag_TEACK(LPUART1))) || (!(LL_LPUART_IsActiveFlag_REACK(LPUART1)))); - - LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - LL_LPUART_EnableIT_IDLE(LPUART1); - HAL_NVIC_SetPriority(LPUART1_IRQn, 5, 0); - - FURI_LOG_I("FuriHalLpUart", "Init OK"); -} - -void furi_hal_lpuart_set_br(uint32_t baud) { - if (LL_LPUART_IsEnabled(LPUART1)) { - // Wait for transfer complete flag - while (!LL_LPUART_IsActiveFlag_TC(LPUART1)); - LL_LPUART_Disable(LPUART1); - uint32_t uartclk = LL_RCC_GetLPUARTClockFreq(LL_RCC_GetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1)); - if (uartclk/baud > 4095) { - LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV32); - LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV32, baud); - } else { - LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV1); - LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV1, baud); - } - - LL_LPUART_Enable(LPUART1); - } -} - -void furi_hal_lpuart_deinit() { - furi_hal_lpuart_set_irq_cb(NULL); - LL_GPIO_SetPinMode(GPIOC, PC0_Pin, LL_GPIO_MODE_ANALOG); - LL_GPIO_SetPinMode(GPIOC, PC1_Pin, LL_GPIO_MODE_ANALOG); - LL_LPUART_Disable(LPUART1); - LL_APB1_GRP2_DisableClock(LL_APB1_GRP2_PERIPH_LPUART1); -} - -void furi_hal_lpuart_tx(const uint8_t* buffer, size_t buffer_size) { - if (LL_LPUART_IsEnabled(LPUART1) == 0) - return; - - while(buffer_size > 0) { - while (!LL_LPUART_IsActiveFlag_TXE(LPUART1)); - - LL_LPUART_TransmitData8(LPUART1, *buffer); - - buffer++; - buffer_size--; - } -} - -void furi_hal_lpuart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)) { - irq_cb = cb; - if (irq_cb == NULL) - NVIC_DisableIRQ(LPUART1_IRQn); - else - NVIC_EnableIRQ(LPUART1_IRQn); -} - -void LPUART1_IRQHandler(void) { - if (LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1)) { - uint8_t data = LL_LPUART_ReceiveData8(LPUART1); - irq_cb(UartIrqEventRXNE, data); - } else if (LL_LPUART_IsActiveFlag_IDLE(LPUART1)) { - irq_cb(UartIrqEventIDLE, 0); - LL_LPUART_ClearFlag_IDLE(LPUART1); - } - - //TODO: more events -} diff --git a/firmware/targets/f7/furi-hal/furi-hal-lpuart.h b/firmware/targets/f7/furi-hal/furi-hal-lpuart.h deleted file mode 100644 index 118a9a9c..00000000 --- a/firmware/targets/f7/furi-hal/furi-hal-lpuart.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include "furi-hal-console.h" - -#ifdef __cplusplus -extern "C" { -#endif - - -void furi_hal_lpuart_init(); - -void furi_hal_lpuart_deinit(); - -void furi_hal_lpuart_set_br(uint32_t baud); - -void furi_hal_lpuart_tx(const uint8_t* buffer, size_t buffer_size); - -void furi_hal_lpuart_set_irq_cb(void (*cb)(UartIrqEvent ev, uint8_t data)); - -#ifdef __cplusplus -} -#endif diff --git a/firmware/targets/f7/furi-hal/furi-hal-nfc.c b/firmware/targets/f7/furi-hal/furi-hal-nfc.c index d92e2cad..4bca12a9 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-nfc.c +++ b/firmware/targets/f7/furi-hal/furi-hal-nfc.c @@ -1,15 +1,17 @@ #include "furi-hal-nfc.h" #include +#define TAG "FuriHalNfc" + static const uint32_t clocks_in_ms = 64 * 1000; void furi_hal_nfc_init() { ReturnCode ret = rfalNfcInitialize(); if(ret == ERR_NONE) { furi_hal_nfc_start_sleep(); - FURI_LOG_I("FuriHalNfc", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } else { - FURI_LOG_W("FuriHalNfc", "Initialization failed, RFAL returned: %d", ret); + FURI_LOG_W(TAG, "Initialization failed, RFAL returned: %d", ret); } } @@ -63,7 +65,7 @@ bool furi_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t ti while(state != RFAL_NFC_STATE_ACTIVATED) { rfalNfcWorker(); state = rfalNfcGetState(); - FURI_LOG_D("HAL NFC", "Current state %d", state); + FURI_LOG_D(TAG, "Current state %d", state); if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { start = DWT->CYCCNT; continue; @@ -73,7 +75,7 @@ bool furi_hal_nfc_detect(rfalNfcDevice **dev_list, uint8_t* dev_cnt, uint32_t ti } if(DWT->CYCCNT - start > timeout * clocks_in_ms) { rfalNfcDeactivate(true); - FURI_LOG_D("HAL NFC", "Timeout"); + FURI_LOG_D(TAG, "Timeout"); return false; } osThreadYield(); diff --git a/firmware/targets/f7/furi-hal/furi-hal-os.c b/firmware/targets/f7/furi-hal/furi-hal-os.c index eb1811b6..aac1b10e 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-os.c +++ b/firmware/targets/f7/furi-hal/furi-hal-os.c @@ -5,6 +5,8 @@ #include +#define TAG "FuriHalOs" + #define FURI_HAL_OS_CLK_FREQUENCY 32768 #define FURI_HAL_OS_TICK_PER_SECOND 1024 #define FURI_HAL_OS_CLK_PER_TICK (FURI_HAL_OS_CLK_FREQUENCY / FURI_HAL_OS_TICK_PER_SECOND) @@ -44,7 +46,7 @@ void furi_hal_os_init() { osTimerStart(second_timer, 1024); #endif - FURI_LOG_I("FuriHalOs", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void LPTIM2_IRQHandler(void) { diff --git a/firmware/targets/f7/furi-hal/furi-hal-power.c b/firmware/targets/f7/furi-hal/furi-hal-power.c index 47ce5f4d..870cbda6 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-power.c +++ b/firmware/targets/f7/furi-hal/furi-hal-power.c @@ -15,6 +15,8 @@ #include +#define TAG "FuriHalPower" + typedef struct { volatile uint8_t insomnia; volatile uint8_t deep_insomnia; @@ -74,7 +76,7 @@ void furi_hal_power_init() { LL_PWR_SMPS_SetMode(LL_PWR_SMPS_STEP_DOWN); bq27220_init(&cedv); bq25896_init(); - FURI_LOG_I("FuriHalPower", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } uint16_t furi_hal_power_insomnia_level() { diff --git a/firmware/targets/f7/furi-hal/furi-hal-rfid.c b/firmware/targets/f7/furi-hal/furi-hal-rfid.c index 59d24333..02a82bd1 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-rfid.c +++ b/firmware/targets/f7/furi-hal/furi-hal-rfid.c @@ -109,7 +109,7 @@ void furi_hal_rfid_tim_read(float freq, float duty_cycle) { sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; - if(HAL_TIM_OC_ConfigChannel(&LFRFID_READ_TIM, &sConfigOC, LFRFID_READ_CHANNEL) != HAL_OK) { + if(HAL_TIM_PWM_ConfigChannel(&LFRFID_READ_TIM, &sConfigOC, LFRFID_READ_CHANNEL) != HAL_OK) { Error_Handler(); } @@ -147,7 +147,6 @@ void furi_hal_rfid_tim_emulate(float freq) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; - TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; // basic PWM setup with needed freq and internal clock LFRFID_EMULATE_TIM.Init.Prescaler = 0; @@ -191,24 +190,6 @@ void furi_hal_rfid_tim_emulate(float freq) { HAL_OK) { Error_Handler(); } - - // no deadtime - sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; - sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; - sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; - sBreakDeadTimeConfig.DeadTime = 0; - sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; - sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; - sBreakDeadTimeConfig.BreakFilter = 0; - sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT; - sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE; - sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH; - sBreakDeadTimeConfig.Break2Filter = 0; - sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT; - sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; - if(HAL_TIMEx_ConfigBreakDeadTime(&LFRFID_EMULATE_TIM, &sBreakDeadTimeConfig) != HAL_OK) { - Error_Handler(); - } } void furi_hal_rfid_tim_emulate_start() { diff --git a/firmware/targets/f7/furi-hal/furi-hal-spi.c b/firmware/targets/f7/furi-hal/furi-hal-spi.c index c925abee..da7c63df 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-spi.c +++ b/firmware/targets/f7/furi-hal/furi-hal-spi.c @@ -9,6 +9,8 @@ #include #include +#define TAG "FuriHalSpi" + void furi_hal_spi_init() { // Spi structure is const, but mutex is not // Need some hell-ish casting to make it work @@ -33,7 +35,7 @@ void furi_hal_spi_init() { hal_gpio_init_ex(&gpio_spi_d_mosi, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn5SPI2); hal_gpio_init_ex(&gpio_spi_d_sck, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn5SPI2); - FURI_LOG_I("FuriHalSpi", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_spi_bus_lock(const FuriHalSpiBus* bus) { @@ -133,7 +135,7 @@ const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id) { furi_assert(device_id < FuriHalSpiDeviceIdMax); const FuriHalSpiDevice* device = &furi_hal_spi_devices[device_id]; - assert(device); + furi_assert(device); furi_hal_spi_bus_lock(device->bus); furi_hal_spi_device_configure(device); diff --git a/firmware/targets/f7/furi-hal/furi-hal-subghz.c b/firmware/targets/f7/furi-hal/furi-hal-subghz.c index 02b91277..276482ee 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-subghz.c +++ b/firmware/targets/f7/furi-hal/furi-hal-subghz.c @@ -10,6 +10,8 @@ #include #include +#define TAG "FuriHalSubGhz" + static volatile SubGhzState furi_hal_subghz_state = SubGhzStateInit; static volatile SubGhzRegulation furi_hal_subghz_regulation = SubGhzRegulationTxRx; @@ -303,7 +305,7 @@ void furi_hal_subghz_init() { cc1101_shutdown(device); furi_hal_spi_device_return(device); - FURI_LOG_I("FuriHalSubGhz", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_subghz_sleep() { @@ -658,6 +660,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { bool is_odd = samples % 2; LevelDuration ld = furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context); + if(level_duration_is_wait(ld)) return; if(level_duration_is_reset(ld)) { // One more even sample required to end at low level if(is_odd) { @@ -675,7 +678,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { } uint32_t duration = level_duration_get_duration(ld); - assert(duration > 0); + furi_assert(duration > 0); *buffer = duration; buffer++; samples--; diff --git a/firmware/targets/f7/furi-hal/furi-hal-uart.c b/firmware/targets/f7/furi-hal/furi-hal-uart.c new file mode 100644 index 00000000..ff5a056b --- /dev/null +++ b/firmware/targets/f7/furi-hal/furi-hal-uart.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include + +#include + +static void (*irq_cb[2])(uint8_t ev, uint8_t data); + +static void furi_hal_usart_init(uint32_t baud) { + hal_gpio_init_ex( + &gpio_usart_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + hal_gpio_init_ex( + &gpio_usart_rx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + + LL_USART_InitTypeDef USART_InitStruct = {0}; + USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; + USART_InitStruct.BaudRate = baud; + USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; + USART_InitStruct.StopBits = LL_USART_STOPBITS_1; + USART_InitStruct.Parity = LL_USART_PARITY_NONE; + USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; + USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; + USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; + LL_USART_Init(USART1, &USART_InitStruct); + LL_USART_SetTXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_2); + LL_USART_EnableFIFO(USART1); + LL_USART_ConfigAsyncMode(USART1); + + LL_USART_Enable(USART1); + + while(!LL_USART_IsActiveFlag_TEACK(USART1)); + + LL_USART_EnableIT_RXNE_RXFNE(USART1); + LL_USART_EnableIT_IDLE(USART1); + HAL_NVIC_SetPriority(USART1_IRQn, 5, 0); +} + +static void furi_hal_lpuart_init(uint32_t baud) { + hal_gpio_init_ex( + &gpio_ext_pc0, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn8LPUART1); + hal_gpio_init_ex( + &gpio_ext_pc1, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn8LPUART1); + + LL_LPUART_InitTypeDef LPUART_InitStruct = {0}; + LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; + LPUART_InitStruct.BaudRate = 115200; + LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; + LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1; + LPUART_InitStruct.Parity = LL_LPUART_PARITY_NONE; + LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX; + LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE; + LL_LPUART_Init(LPUART1, &LPUART_InitStruct); + LL_LPUART_SetTXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); + LL_LPUART_SetRXFIFOThreshold(LPUART1, LL_LPUART_FIFOTHRESHOLD_1_8); + LL_LPUART_EnableFIFO(LPUART1); + + LL_LPUART_Enable(LPUART1); + + while((!(LL_LPUART_IsActiveFlag_TEACK(LPUART1))) || (!(LL_LPUART_IsActiveFlag_REACK(LPUART1)))); + + furi_hal_uart_set_br(FuriHalUartIdLPUART1, baud); + + LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); + LL_LPUART_EnableIT_IDLE(LPUART1); + HAL_NVIC_SetPriority(LPUART1_IRQn, 5, 0); +} + +void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud) { + if (ch == FuriHalUartIdLPUART1) + furi_hal_lpuart_init(baud); + else if (ch == FuriHalUartIdUSART1) + furi_hal_usart_init(baud); +} + +void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud) { + if (ch == FuriHalUartIdUSART1) { + if (LL_USART_IsEnabled(USART1)) { + // Wait for transfer complete flag + while (!LL_USART_IsActiveFlag_TC(USART1)); + LL_USART_Disable(USART1); + uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); + LL_USART_SetBaudRate(USART1, uartclk, LL_USART_PRESCALER_DIV1, LL_USART_OVERSAMPLING_16, baud); + LL_USART_Enable(USART1); + } + } else if (ch == FuriHalUartIdLPUART1) { + if (LL_LPUART_IsEnabled(LPUART1)) { + // Wait for transfer complete flag + while (!LL_LPUART_IsActiveFlag_TC(LPUART1)); + LL_LPUART_Disable(LPUART1); + uint32_t uartclk = LL_RCC_GetLPUARTClockFreq(LL_RCC_LPUART1_CLKSOURCE); + if (uartclk/baud > 4095) { + LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV32); + LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV32, baud); + } else { + LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV1); + LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV1, baud); + } + LL_LPUART_Enable(LPUART1); + } + } +} + +void furi_hal_uart_deinit(FuriHalUartId ch) { + furi_hal_uart_set_irq_cb(ch, NULL); + if (ch == FuriHalUartIdUSART1) { + LL_USART_Disable(USART1); + hal_gpio_init(&gpio_usart_tx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + hal_gpio_init(&gpio_usart_rx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if (ch == FuriHalUartIdLPUART1) { + LL_LPUART_Disable(LPUART1); + hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } +} + +void furi_hal_uart_tx(FuriHalUartId ch, uint8_t* buffer, size_t buffer_size) { + if (ch == FuriHalUartIdUSART1) { + if (LL_USART_IsEnabled(USART1) == 0) + return; + + while(buffer_size > 0) { + while (!LL_USART_IsActiveFlag_TXE(USART1)); + + LL_USART_TransmitData8(USART1, *buffer); + buffer++; + buffer_size--; + } + + } else if (ch == FuriHalUartIdLPUART1) { + if (LL_LPUART_IsEnabled(LPUART1) == 0) + return; + + while(buffer_size > 0) { + while (!LL_LPUART_IsActiveFlag_TXE(LPUART1)); + + LL_LPUART_TransmitData8(LPUART1, *buffer); + + buffer++; + buffer_size--; + } + } +} + +void furi_hal_uart_set_irq_cb(FuriHalUartId ch, void (*cb)(UartIrqEvent ev, uint8_t data)) { + if (cb == NULL) { + if (ch == FuriHalUartIdUSART1) + NVIC_DisableIRQ(USART1_IRQn); + else if (ch == FuriHalUartIdLPUART1) + NVIC_DisableIRQ(LPUART1_IRQn); + irq_cb[ch] = cb; + } else { + irq_cb[ch] = cb; + if (ch == FuriHalUartIdUSART1) + NVIC_EnableIRQ(USART1_IRQn); + else if (ch == FuriHalUartIdLPUART1) + NVIC_EnableIRQ(LPUART1_IRQn); + } +} + +void LPUART1_IRQHandler(void) { + if (LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1)) { + uint8_t data = LL_LPUART_ReceiveData8(LPUART1); + irq_cb[FuriHalUartIdLPUART1](UartIrqEventRXNE, data); + } else if (LL_LPUART_IsActiveFlag_IDLE(LPUART1)) { + irq_cb[FuriHalUartIdLPUART1](UartIrqEventIDLE, 0); + LL_LPUART_ClearFlag_IDLE(LPUART1); + } else if (LL_LPUART_IsActiveFlag_ORE(LPUART1)) { + LL_LPUART_ClearFlag_ORE(LPUART1); + } + //TODO: more events +} + +void USART1_IRQHandler(void) { + if (LL_USART_IsActiveFlag_RXNE_RXFNE(USART1)) { + uint8_t data = LL_USART_ReceiveData8(USART1); + irq_cb[FuriHalUartIdUSART1](UartIrqEventRXNE, data); + } else if (LL_USART_IsActiveFlag_IDLE(USART1)) { + irq_cb[FuriHalUartIdUSART1](UartIrqEventIDLE, 0); + LL_USART_ClearFlag_IDLE(USART1); + } else if (LL_USART_IsActiveFlag_ORE(USART1)) { + LL_USART_ClearFlag_ORE(USART1); + } +} diff --git a/firmware/targets/f7/furi-hal/furi-hal-uart.h b/firmware/targets/f7/furi-hal/furi-hal-uart.h new file mode 100644 index 00000000..731a12a2 --- /dev/null +++ b/firmware/targets/f7/furi-hal/furi-hal-uart.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FuriHalUartIdUSART1, + FuriHalUartIdLPUART1, +} FuriHalUartId; + +typedef enum { + UartIrqEventRXNE, + UartIrqEventIDLE, + //TODO: more events +} UartIrqEvent; + +void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud); + +void furi_hal_uart_deinit(FuriHalUartId ch); + +void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud); + +void furi_hal_uart_tx(FuriHalUartId ch, uint8_t* buffer, size_t buffer_size); + +void furi_hal_uart_set_irq_cb(FuriHalUartId ch, void (*cb)(UartIrqEvent ev, uint8_t data)); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi-hal/furi-hal-usb-cdc.c b/firmware/targets/f7/furi-hal/furi-hal-usb-cdc.c index e643fe57..e1f5de7d 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-usb-cdc.c +++ b/firmware/targets/f7/furi-hal/furi-hal-usb-cdc.c @@ -345,6 +345,7 @@ static const struct CdcConfigDescriptorDual cdc_cfg_desc_dual = { }; static struct usb_cdc_line_coding cdc_config[IF_NUM_MAX] = {}; +static uint8_t cdc_ctrl_line_state[IF_NUM_MAX]; static void cdc_init(usbd_device* dev, struct UsbInterface* intf); static void cdc_deinit(usbd_device *dev); @@ -438,6 +439,12 @@ struct usb_cdc_line_coding* furi_hal_cdc_get_port_settings(uint8_t if_num) { return NULL; } +uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { + if (if_num < 2) + return cdc_ctrl_line_state[if_num]; + return 0; +} + void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { if (if_num == 0) usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); @@ -446,10 +453,12 @@ void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { } int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { + int32_t len = 0; if (if_num == 0) - return usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); + len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); else - return usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + return ((len < 0) ? 0 : len); } static void cdc_on_wakeup(usbd_device *dev) { @@ -463,6 +472,7 @@ static void cdc_on_wakeup(usbd_device *dev) { static void cdc_on_suspend(usbd_device *dev) { for (uint8_t i = 0; i < IF_NUM_MAX; i++) { + cdc_ctrl_line_state[i] = 0; if (callbacks[i] != NULL) { if (callbacks[i]->state_callback != NULL) callbacks[i]->state_callback(0); @@ -578,8 +588,9 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal switch(req->bRequest) { case USB_CDC_SET_CONTROL_LINE_STATE: if (callbacks[if_num] != NULL) { + cdc_ctrl_line_state[if_num] = req->wValue; if (callbacks[if_num]->ctrl_line_callback != NULL) - callbacks[if_num]->ctrl_line_callback(req->wValue); + callbacks[if_num]->ctrl_line_callback(cdc_ctrl_line_state[if_num]); } return usbd_ack; case USB_CDC_SET_LINE_CODING: diff --git a/firmware/targets/f7/furi-hal/furi-hal-usb-cdc_i.h b/firmware/targets/f7/furi-hal/furi-hal-usb-cdc_i.h index 3b181582..c4859e69 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-usb-cdc_i.h +++ b/firmware/targets/f7/furi-hal/furi-hal-usb-cdc_i.h @@ -17,6 +17,8 @@ void furi_hal_cdc_set_callbacks(uint8_t if_num, CdcCallbacks* cb); struct usb_cdc_line_coding* furi_hal_cdc_get_port_settings(uint8_t if_num); +uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num); + void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len); int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len); diff --git a/firmware/targets/f7/furi-hal/furi-hal-usb.c b/firmware/targets/f7/furi-hal/furi-hal-usb.c index 8c78eb8b..45a6177e 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-usb.c +++ b/firmware/targets/f7/furi-hal/furi-hal-usb.c @@ -5,6 +5,8 @@ #include "usb.h" +#define TAG "FuriHalUsb" + #define USB_RECONNECT_DELAY 500 extern struct UsbInterface usb_cdc_single; @@ -64,7 +66,7 @@ void furi_hal_usb_init(void) { HAL_NVIC_SetPriority(USB_LP_IRQn, 5, 0); NVIC_EnableIRQ(USB_LP_IRQn); - FURI_LOG_I("FuriHalUsb", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_usb_set_config(UsbMode new_mode) { @@ -81,7 +83,7 @@ void furi_hal_usb_set_config(UsbMode new_mode) { usb_if_modes[usb_config.mode_cur]->deinit(&udev); if (usb_if_modes[new_mode] != NULL) { usb_if_modes[new_mode]->init(&udev, usb_if_modes[new_mode]); - FURI_LOG_I("FuriHalUsb", "USB mode change %u -> %u", usb_config.mode_cur, new_mode); + FURI_LOG_I(TAG, "USB mode change %u -> %u", usb_config.mode_cur, new_mode); usb_config.enabled = true; usb_config.mode_cur = new_mode; } @@ -98,7 +100,7 @@ void furi_hal_usb_disable() { susp_evt(&udev, 0, 0); usbd_connect(&udev, false); usb_config.enabled = false; - FURI_LOG_I("FuriHalUsb", "USB Disable"); + FURI_LOG_I(TAG, "USB Disable"); } } @@ -106,7 +108,7 @@ void furi_hal_usb_enable() { if ((!usb_config.enabled) && (usb_if_modes[usb_config.mode_cur] != NULL)) { usbd_connect(&udev, true); usb_config.enabled = true; - FURI_LOG_I("FuriHalUsb", "USB Enable"); + FURI_LOG_I(TAG, "USB Enable"); } } diff --git a/firmware/targets/f7/furi-hal/furi-hal-vcp.c b/firmware/targets/f7/furi-hal/furi-hal-vcp.c index b975495d..039481f1 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-vcp.c +++ b/firmware/targets/f7/furi-hal/furi-hal-vcp.c @@ -1,22 +1,42 @@ #include - +#include #include #include -#define APP_RX_DATA_SIZE CDC_DATA_SZ -#define APP_TX_DATA_SIZE CDC_DATA_SZ -#define FURI_HAL_VCP_RX_BUFFER_SIZE (APP_RX_DATA_SIZE * 16) +#define TAG "FuriHalVcp" + +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3) + #define VCP_IF_NUM 0 +typedef enum { + VcpEvtReserved = (1 << 0), // Reserved for StreamBuffer internal event + VcpEvtConnect = (1 << 1), + VcpEvtDisconnect = (1 << 2), + VcpEvtEnable = (1 << 3), + VcpEvtDisable = (1 << 4), + VcpEvtRx = (1 << 5), + VcpEvtTx = (1 << 6), + VcpEvtStreamRx = (1 << 7), + VcpEvtStreamTx = (1 << 8), +} WorkerEvtFlags; + +#define VCP_THREAD_FLAG_ALL (VcpEvtConnect | VcpEvtDisconnect | VcpEvtEnable | VcpEvtDisable | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | VcpEvtStreamTx) + typedef struct { + FuriThread* thread; + + StreamBufferHandle_t tx_stream; + StreamBufferHandle_t rx_stream; + volatile bool connected; - StreamBufferHandle_t rx_stream; - volatile bool rx_stream_full; - - osSemaphoreId_t tx_semaphore; + uint8_t data_buffer[USB_CDC_PKT_LEN]; } FuriHalVcp; +static int32_t vcp_worker(void* context); static void vcp_on_cdc_tx_complete(); static void vcp_on_cdc_rx(); static void vcp_state_callback(uint8_t state); @@ -30,130 +50,225 @@ static CdcCallbacks cdc_cb = { NULL, }; -static FuriHalVcp* furi_hal_vcp = NULL; +static FuriHalVcp* vcp = NULL; static const uint8_t ascii_soh = 0x01; static const uint8_t ascii_eot = 0x04; -static uint8_t* vcp_rx_buf; - void furi_hal_vcp_init() { - furi_hal_vcp = furi_alloc(sizeof(FuriHalVcp)); - vcp_rx_buf = furi_alloc(APP_RX_DATA_SIZE); - furi_hal_vcp->connected = false; - - furi_hal_vcp->rx_stream = xStreamBufferCreate(FURI_HAL_VCP_RX_BUFFER_SIZE, 1); - furi_hal_vcp->rx_stream_full = false; + vcp = furi_alloc(sizeof(FuriHalVcp)); + vcp->connected = false; - furi_hal_vcp->tx_semaphore = osSemaphoreNew(1, 1, NULL); + vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1); + vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); + + vcp->thread = furi_thread_alloc(); + furi_thread_set_name(vcp->thread, "VcpWorker"); + furi_thread_set_stack_size(vcp->thread, 1024); + furi_thread_set_callback(vcp->thread, vcp_worker); + furi_thread_start(vcp->thread); + + FURI_LOG_I(TAG, "Init OK"); +} + +static int32_t vcp_worker(void* context) { + bool enabled = true; + bool tx_idle = false; + size_t missed_rx = 0; furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); - FURI_LOG_I("FuriHalVcp", "Init OK"); + while (1) { + uint32_t flags = osThreadFlagsWait(VCP_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); + furi_assert((flags & osFlagsError) == 0); + + // New data received + if((flags & VcpEvtStreamRx) && enabled && missed_rx > 0) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP StreamRx\r\n"); +#endif + if (xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { + flags |= VcpEvtRx; + missed_rx--; + } + } + + // Rx buffer was read, maybe there is enough space for new data? + if((flags & VcpEvtRx)) { + if (xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { + int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_printf("VCP Rx %d\r\n", len); +#endif + if (len > 0) { + furi_check(xStreamBufferSend(vcp->rx_stream, vcp->data_buffer, len, osWaitForever) == len); + } + } else { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Rx missed\r\n"); +#endif + missed_rx++; + } + } + + // New data in Tx buffer + if((flags & VcpEvtStreamTx) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP StreamTx\r\n"); +#endif + if (tx_idle) { + flags |= VcpEvtTx; + } + } + + // CDC write transfer done + if((flags & VcpEvtTx) && enabled) { + size_t len = xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_printf("VCP Tx %d\r\n", len); +#endif + if (len > 0) { // Some data left in Tx buffer. Sending it now + tx_idle = false; + furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); + } else { // There is nothing to send. Set flag to start next transfer instantly + tx_idle = true; + } + } + + // VCP session opened + if((flags & VcpEvtConnect) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Connect\r\n"); +#endif + if (vcp->connected == false) { + vcp->connected = true; + xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever); + } + } + + // VCP session closed + if((flags & VcpEvtDisconnect) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Disconnect\r\n"); +#endif + if (vcp->connected == true) { + vcp->connected = false; + xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever); + } + } + + // VCP enabled + if((flags & VcpEvtEnable) && !enabled){ +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Enable\r\n"); +#endif + furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); + enabled = true; + furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); // flush Rx buffer + if (furi_hal_cdc_get_ctrl_line_state(VCP_IF_NUM) & (1 << 0)) { + vcp->connected = true; + xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever); + } + } + + // VCP disabled + if((flags & VcpEvtDisable) && enabled) { +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP Disable\r\n"); +#endif + enabled = false; + vcp->connected = false; + xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever); + } + } + return 0; } void furi_hal_vcp_enable() { - furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); - furi_hal_vcp->connected = true; + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtEnable); } void furi_hal_vcp_disable() { - furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL); - furi_hal_vcp->connected = false; - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); -} - -size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { - furi_assert(furi_hal_vcp); - - size_t received = xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, portMAX_DELAY); - - if(furi_hal_vcp->rx_stream_full - && xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream) >= APP_RX_DATA_SIZE) { - furi_hal_vcp->rx_stream_full = false; - } - - return received; + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisable); } size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { - furi_assert(furi_hal_vcp); - return xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, timeout); + furi_assert(vcp); + furi_assert(buffer); + + size_t rx_cnt = 0; + + while (size > 0) { + size_t batch_size = size; + if (batch_size > VCP_RX_BUF_SIZE) + batch_size = VCP_RX_BUF_SIZE; + + size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout); + if (len == 0) + break; + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamRx); + size -= len; + buffer += len; + rx_cnt += len; + } + return rx_cnt; +} + +size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { + furi_assert(vcp); + return furi_hal_vcp_rx_with_timeout(buffer, size, osWaitForever); } void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { - furi_assert(furi_hal_vcp); - - while (size > 0 && furi_hal_vcp->connected) { - furi_check(osSemaphoreAcquire(furi_hal_vcp->tx_semaphore, osWaitForever) == osOK); - if (!furi_hal_vcp->connected) - break; + furi_assert(vcp); + furi_assert(buffer); + while (size > 0) { size_t batch_size = size; - if (batch_size > APP_TX_DATA_SIZE) { - batch_size = APP_TX_DATA_SIZE; - } + if (batch_size > VCP_TX_BUF_SIZE) + batch_size = VCP_TX_BUF_SIZE; + + xStreamBufferSend(vcp->tx_stream, buffer, batch_size, osWaitForever); + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamTx); - furi_hal_cdc_send(VCP_IF_NUM, (uint8_t*)buffer, batch_size); size -= batch_size; buffer += batch_size; } } static void vcp_state_callback(uint8_t state) { - if (state == 1) - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); - else if (furi_hal_vcp->connected) { - furi_hal_vcp->connected = false; - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP State\r\n"); +#endif + if (state == 0) { + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); } } static void vcp_on_cdc_control_line(uint8_t state) { - - BaseType_t xHigherPriorityTaskWoken = pdFALSE; // bit 0: DTR state, bit 1: RTS state - // bool dtr = state & 0b01; - bool dtr = state & 0b1; - - if (dtr) { - if (!furi_hal_vcp->connected) { - furi_hal_vcp->connected = true; - xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_soh, 1, &xHigherPriorityTaskWoken); // SOH - - } + bool dtr = state & (1 << 0); +#ifdef FURI_HAL_USB_VCP_DEBUG + furi_hal_console_puts("VCP CtrlLine\r\n"); +#endif + if (dtr == true) { + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtConnect); } else { - if (furi_hal_vcp->connected) { - xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_eot, 1, &xHigherPriorityTaskWoken); // EOT - furi_hal_vcp->connected = false; - } + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); } - - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); - - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } -static void vcp_on_cdc_rx() { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - - uint16_t max_len = xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream); - if (max_len > 0) { - if (max_len > APP_RX_DATA_SIZE) - max_len = APP_RX_DATA_SIZE; - int32_t size = furi_hal_cdc_receive(VCP_IF_NUM, vcp_rx_buf, max_len); - - if (size > 0) { - size_t ret = xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, vcp_rx_buf, size, &xHigherPriorityTaskWoken); - furi_check(ret == size); - } - } else { - furi_hal_vcp->rx_stream_full = true; - }; - - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +static void vcp_on_cdc_rx() { + uint32_t ret = osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtRx); + furi_assert((ret & osFlagsError) == 0); } static void vcp_on_cdc_tx_complete() { - osSemaphoreRelease(furi_hal_vcp->tx_semaphore); + osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtTx); } + +bool furi_hal_vcp_is_connected(void) { + furi_assert(vcp); + return vcp->connected; +} + diff --git a/firmware/targets/f7/furi-hal/furi-hal-version.c b/firmware/targets/f7/furi-hal/furi-hal-version.c index 64641fc6..f38e6cdc 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-version.c +++ b/firmware/targets/f7/furi-hal/furi-hal-version.c @@ -7,6 +7,8 @@ #include #include "ble.h" +#define TAG "FuriHalVersion" + #define FURI_HAL_VERSION_OTP_HEADER_MAGIC 0xBABE #define FURI_HAL_VERSION_OTP_ADDRESS OTP_AREA_BASE @@ -191,7 +193,7 @@ void furi_hal_version_init() { break; default: furi_crash(NULL); } - FURI_LOG_I("FuriHalVersion", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } bool furi_hal_version_do_i_belong_here() { @@ -276,7 +278,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) { return version_get(); } -const struct Version* furi_hal_version_get_boot_version(void) { +const struct Version* furi_hal_version_get_bootloader_version(void) { #ifdef NO_BOOTLOADER return 0; #else diff --git a/firmware/targets/f7/furi-hal/furi-hal-vibro.c b/firmware/targets/f7/furi-hal/furi-hal-vibro.c index 7dfddd42..7de8ad84 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-vibro.c +++ b/firmware/targets/f7/furi-hal/furi-hal-vibro.c @@ -1,10 +1,12 @@ #include #include +#define TAG "FuriHalVibro" + void furi_hal_vibro_init() { hal_gpio_init(&vibro_gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); hal_gpio_write(&vibro_gpio, false); - FURI_LOG_I("FuriHalVibro", "Init OK"); + FURI_LOG_I(TAG, "Init OK"); } diff --git a/firmware/targets/f7/furi-hal/furi-hal.c b/firmware/targets/f7/furi-hal/furi-hal.c index bcddc6e0..d435c5f2 100644 --- a/firmware/targets/f7/furi-hal/furi-hal.c +++ b/firmware/targets/f7/furi-hal/furi-hal.c @@ -5,6 +5,12 @@ #include #include +#include + +#include + +#define TAG "FuriHal" + void furi_hal_init() { furi_hal_clock_init(); furi_hal_console_init(); @@ -12,23 +18,23 @@ void furi_hal_init() { furi_hal_delay_init(); MX_GPIO_Init(); - FURI_LOG_I("HAL", "GPIO OK"); + FURI_LOG_I(TAG, "GPIO OK"); MX_RTC_Init(); - FURI_LOG_I("HAL", "RTC OK"); - furi_hal_boot_init(); + FURI_LOG_I(TAG, "RTC OK"); + furi_hal_bootloader_init(); furi_hal_version_init(); furi_hal_spi_init(); MX_TIM1_Init(); - FURI_LOG_I("HAL", "TIM1 OK"); + FURI_LOG_I(TAG, "TIM1 OK"); MX_TIM2_Init(); - FURI_LOG_I("HAL", "TIM2 OK"); + FURI_LOG_I(TAG, "TIM2 OK"); MX_TIM16_Init(); - FURI_LOG_I("HAL", "TIM16 OK"); + FURI_LOG_I(TAG, "TIM16 OK"); MX_COMP1_Init(); - FURI_LOG_I("HAL", "COMP1 OK"); + FURI_LOG_I(TAG, "COMP1 OK"); furi_hal_crypto_init(); @@ -36,7 +42,7 @@ void furi_hal_init() { furi_hal_usb_init(); furi_hal_usb_set_config(UsbModeVcpSingle); furi_hal_vcp_init(); - FURI_LOG_I("HAL", "USB OK"); + FURI_LOG_I(TAG, "USB OK"); furi_hal_i2c_init(); @@ -52,4 +58,22 @@ void furi_hal_init() { // FreeRTOS glue furi_hal_os_init(); + + // FatFS driver initialization + MX_FATFS_Init(); + FURI_LOG_I(TAG, "FATFS OK"); + + // Partial null pointer dereference protection + LL_MPU_Disable(); + LL_MPU_ConfigRegion( + LL_MPU_REGION_NUMBER0, 0x00, 0x0, + LL_MPU_REGION_SIZE_1MB + | LL_MPU_REGION_PRIV_RO_URO + | LL_MPU_ACCESS_BUFFERABLE + | LL_MPU_ACCESS_CACHEABLE + | LL_MPU_ACCESS_SHAREABLE + | LL_MPU_TEX_LEVEL1 + | LL_MPU_INSTRUCTION_ACCESS_ENABLE + ); + LL_MPU_Enable(LL_MPU_CTRL_PRIVILEGED_DEFAULT); } diff --git a/firmware/targets/f7/stm32wb55xx_flash_cm4_no_boot.ld b/firmware/targets/f7/stm32wb55xx_flash_cm4_no_bootloader.ld similarity index 100% rename from firmware/targets/f7/stm32wb55xx_flash_cm4_no_boot.ld rename to firmware/targets/f7/stm32wb55xx_flash_cm4_no_bootloader.ld diff --git a/firmware/targets/f7/stm32wb55xx_flash_cm4_boot.ld b/firmware/targets/f7/stm32wb55xx_flash_cm4_with_bootloader.ld similarity index 100% rename from firmware/targets/f7/stm32wb55xx_flash_cm4_boot.ld rename to firmware/targets/f7/stm32wb55xx_flash_cm4_with_bootloader.ld diff --git a/firmware/targets/f7/target.mk b/firmware/targets/f7/target.mk index be91cee9..d6b8cbfa 100644 --- a/firmware/targets/f7/target.mk +++ b/firmware/targets/f7/target.mk @@ -49,8 +49,6 @@ C_SOURCES += \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_cortex.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_cryp.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_exti.c \ - $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_flash.c \ - $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_flash_ex.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_gpio.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_hsem.c \ $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_hal_ipcc.c \ @@ -130,6 +128,11 @@ ifeq ($(FURI_HAL_OS_DEBUG), 1) CFLAGS += -DFURI_HAL_OS_DEBUG endif +FURI_HAL_USB_VCP_DEBUG ?= 0 +ifeq ($(FURI_HAL_USB_VCP_DEBUG), 1) +CFLAGS += -DFURI_HAL_USB_VCP_DEBUG +endif + FURI_HAL_SUBGHZ_TX_GPIO ?= 0 ifneq ($(FURI_HAL_SUBGHZ_TX_GPIO), 0) CFLAGS += -DFURI_HAL_SUBGHZ_TX_GPIO=$(FURI_HAL_SUBGHZ_TX_GPIO) @@ -146,16 +149,16 @@ C_SOURCES += $(wildcard $(FURI_HAL_DIR)/*.c) # Other CFLAGS += \ -I$(MXPROJECT_DIR)/Inc \ - -I$(MXPROJECT_DIR)/Src/fatfs + -I$(MXPROJECT_DIR)/fatfs C_SOURCES += \ $(wildcard $(MXPROJECT_DIR)/Src/*.c) \ - $(wildcard $(MXPROJECT_DIR)/Src/fatfs/*.c) + $(wildcard $(MXPROJECT_DIR)/fatfs/*.c) # Linker options ifeq ($(NO_BOOTLOADER), 1) -LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_no_boot.ld +LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_no_bootloader.ld else -LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_boot.ld +LDFLAGS += -T$(MXPROJECT_DIR)/stm32wb55xx_flash_cm4_with_bootloader.ld endif SVD_FILE = ../debug/STM32WB55_CM4.svd diff --git a/firmware/targets/furi-hal-include/furi-hal-boot.h b/firmware/targets/furi-hal-include/furi-hal-boot.h deleted file mode 100644 index 195a7bb0..00000000 --- a/firmware/targets/furi-hal-include/furi-hal-boot.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @file furi-hal-boot.h - * Bootloader HAL API - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Boot modes */ -typedef enum { - FuriHalBootModeNormal, - FuriHalBootModeDFU -} FuriHalBootMode; - -/** Boot flags */ -typedef enum { - FuriHalBootFlagDefault=0, - FuriHalBootFlagFactoryReset=1, -} FuriHalBootFlag; - -/** Initialize boot subsystem - */ -void furi_hal_boot_init(); - -/** Set boot mode - * - * @param[in] mode FuriHalBootMode - */ -void furi_hal_boot_set_mode(FuriHalBootMode mode); - -/** Set boot flags - * - * @param[in] flags FuriHalBootFlag - */ -void furi_hal_boot_set_flags(FuriHalBootFlag flags); - -/** Get boot flag - * - * @return FuriHalBootFlag - */ -FuriHalBootFlag furi_hal_boot_get_flags(); - -#ifdef __cplusplus -} -#endif diff --git a/firmware/targets/furi-hal-include/furi-hal-bootloader.h b/firmware/targets/furi-hal-include/furi-hal-bootloader.h new file mode 100644 index 00000000..a1ef2c26 --- /dev/null +++ b/firmware/targets/furi-hal-include/furi-hal-bootloader.h @@ -0,0 +1,50 @@ +/** + * @file furi-hal-bootloader.h + * Bootloader HAL API + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Boot modes */ +typedef enum { + FuriHalBootloaderModeNormal, + FuriHalBootloaderModeDFU +} FuriHalBootloaderMode; + +/** Boot flags */ +typedef enum { + FuriHalBootloaderFlagDefault=0, + FuriHalBootloaderFlagFactoryReset=1, +} FuriHalBootloaderFlag; + +/** Initialize boot subsystem + */ +void furi_hal_bootloader_init(); + +/** Set bootloader mode + * + * @param[in] mode FuriHalBootloaderMode + */ +void furi_hal_bootloader_set_mode(FuriHalBootloaderMode mode); + +/** Set bootloader flags + * + * @param[in] flags FuriHalBootloaderFlag + */ +void furi_hal_bootloader_set_flags(FuriHalBootloaderFlag flags); + +/** Get boot flag + * + * @return FuriHalBootloaderFlag + */ +FuriHalBootloaderFlag furi_hal_bootloader_get_flags(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi-hal-include/furi-hal-bt.h b/firmware/targets/furi-hal-include/furi-hal-bt.h index fe9a6de9..c7d86165 100644 --- a/firmware/targets/furi-hal-include/furi-hal-bt.h +++ b/firmware/targets/furi-hal-include/furi-hal-bt.h @@ -9,6 +9,9 @@ #include #include #include +#include +#include + #define FURI_HAL_BT_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX @@ -20,6 +23,18 @@ extern "C" { */ void furi_hal_bt_init(); +/** Lock core2 state transition */ +void furi_hal_bt_lock_core2(); + +/** Lock core2 state transition */ +void furi_hal_bt_unlock_core2(); + +/** Start 2nd core and BLE stack + * + * @return true on success + */ +bool furi_hal_bt_start_core2(); + /** Start BLE app * @param event_cb - BleEventCallback instance * @param context - pointer to context @@ -52,12 +67,39 @@ void furi_hal_bt_dump_state(string_t buffer); */ bool furi_hal_bt_is_alive(); +/** Get key storage buffer address and size + * + * @param key_buff_addr pointer to store buffer address + * @param key_buff_size pointer to store buffer size + */ +void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size); + +/** Get SRAM2 hardware semaphore + * @note Must be called before SRAM2 read/write operations + */ +void furi_hal_bt_nvm_sram_sem_acquire(); + +/** Release SRAM2 hardware semaphore + * @note Must be called after SRAM2 read/write operations + */ +void furi_hal_bt_nvm_sram_sem_release(); + +/** Set key storage change callback + * + * @param callback BleGlueKeyStorageChangedCallback instance + * @param context pointer to context + */ +void furi_hal_bt_set_key_storage_change_callback(BleGlueKeyStorageChangedCallback callback, void* context); + /** Set data event callbacks * @param on_received_cb - SerialSvcDataReceivedCallback instance * @param on_sent_cb - SerialSvcDataSentCallback instance * @param context - pointer to context */ -void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); +void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); + +/** Notify that buffer is empty */ +void furi_hal_bt_notify_buffer_is_empty(); /** Send data through BLE * @param data - data buffer @@ -65,23 +107,6 @@ void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_recei */ bool furi_hal_bt_tx(uint8_t* data, uint16_t size); -/** Wait for Core2 startup */ -bool furi_hal_bt_wait_startup(); - -/** Lock shared access to flash controller - * - * @param[in] erase_flag true if erase operation - * - * @return true if lock was successful, false if not - */ -bool furi_hal_bt_lock_flash(bool erase_flag); - -/** Unlock shared access to flash controller - * - * @param[in] erase_flag true if erase operation - */ -void furi_hal_bt_unlock_flash(bool erase_flag); - /** Start ble tone tx at given channel and power * * @param[in] channel The channel diff --git a/firmware/targets/furi-hal-include/furi-hal-crypto.h b/firmware/targets/furi-hal-include/furi-hal-crypto.h index 0428f781..482b3d7a 100644 --- a/firmware/targets/furi-hal-include/furi-hal-crypto.h +++ b/firmware/targets/furi-hal-include/furi-hal-crypto.h @@ -11,7 +11,7 @@ /** FuriHalCryptoKey Type */ typedef enum { FuriHalCryptoKeyTypeMaster, /**< Master key */ - FuriHalCryptoKeyTypeSimple, /**< Simple enencrypted key */ + FuriHalCryptoKeyTypeSimple, /**< Simple enencrypted key */ FuriHalCryptoKeyTypeEncrypted, /**< Encrypted with Master key */ } FuriHalCryptoKeyType; @@ -59,7 +59,6 @@ bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv); */ bool furi_hal_crypto_store_unload_key(uint8_t slot); - /** Encrypt data * * @param input pointer to input data @@ -68,7 +67,7 @@ bool furi_hal_crypto_store_unload_key(uint8_t slot); * * @return true on success */ -bool furi_hal_crypto_encrypt(const uint8_t *input, uint8_t *output, size_t size); +bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size); /** Decrypt data * @@ -78,4 +77,4 @@ bool furi_hal_crypto_encrypt(const uint8_t *input, uint8_t *output, size_t size) * * @return true on success */ -bool furi_hal_crypto_decrypt(const uint8_t *input, uint8_t *output, size_t size); +bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size); diff --git a/firmware/targets/furi-hal-include/furi-hal-lock.h b/firmware/targets/furi-hal-include/furi-hal-lock.h new file mode 100644 index 00000000..d07ce571 --- /dev/null +++ b/firmware/targets/furi-hal-include/furi-hal-lock.h @@ -0,0 +1,5 @@ +#pragma once +#include + +bool furi_hal_lock_get(); +void furi_hal_lock_set(bool locked); diff --git a/firmware/targets/furi-hal-include/furi-hal-vcp.h b/firmware/targets/furi-hal-include/furi-hal-vcp.h index 996232cf..27c04fb1 100644 --- a/firmware/targets/furi-hal-include/furi-hal-vcp.h +++ b/firmware/targets/furi-hal-include/furi-hal-vcp.h @@ -52,6 +52,12 @@ size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeo */ void furi_hal_vcp_tx(const uint8_t* buffer, size_t size); +/** Check whether VCP is connected + * + * @return true if connected + */ +bool furi_hal_vcp_is_connected(void); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi-hal-include/furi-hal-version.h b/firmware/targets/furi-hal-include/furi-hal-version.h index 00dc5cba..29343d57 100644 --- a/firmware/targets/furi-hal-include/furi-hal-version.h +++ b/firmware/targets/furi-hal-include/furi-hal-version.h @@ -148,7 +148,7 @@ const uint8_t* furi_hal_version_get_ble_mac(); * * @return Address of boot version structure. */ -const struct Version* furi_hal_version_get_boot_version(); +const struct Version* furi_hal_version_get_bootloader_version(); /** Get address of version structure of firmware. * diff --git a/firmware/targets/furi-hal-include/furi-hal.h b/firmware/targets/furi-hal-include/furi-hal.h index 757dd121..eb1fefeb 100644 --- a/firmware/targets/furi-hal-include/furi-hal.h +++ b/firmware/targets/furi-hal-include/furi-hal.h @@ -9,7 +9,7 @@ template struct STOP_EXTERNING_ME {}; #endif -#include "furi-hal-boot.h" +#include "furi-hal-bootloader.h" #include "furi-hal-clock.h" #include "furi-hal-crypto.h" #include "furi-hal-console.h" @@ -36,7 +36,7 @@ template struct STOP_EXTERNING_ME {}; #include "furi-hal-usb.h" #include "furi-hal-usb-hid.h" #include "furi-hal-compress.h" -#include "furi-hal-lpuart.h" +#include "furi-hal-uart.h" /** Init furi-hal */ void furi_hal_init(); diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index 7a011965..58363cac 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -38,7 +38,7 @@ void platformDisableIrqCallback() { void platformSetIrqCallback(PlatformIrqCallback callback) { platform_irq_callback = callback; - platform_irq_thread_attr.name = "rfal_irq_worker"; + platform_irq_thread_attr.name = "RfalIrqWorker"; platform_irq_thread_attr.stack_size = 1024; platform_irq_thread_attr.priority = osPriorityISR; platform_irq_thread_id = osThreadNew(platformIrqWorker, NULL, &platform_irq_thread_attr); diff --git a/lib/app-scened-template/file-worker.c b/lib/app-scened-template/file-worker.c index c38c1261..a1a3bdf2 100644 --- a/lib/app-scened-template/file-worker.c +++ b/lib/app-scened-template/file-worker.c @@ -79,6 +79,32 @@ bool file_worker_remove(FileWorker* file_worker, const char* filename) { return file_worker_check_common_errors(file_worker); } +void file_worker_get_next_filename( + FileWorker* file_worker, + const char* dirname, + const char* filename, + const char* fileextension, + string_t nextfilename) { + string_t temp_str; + string_init(temp_str); + uint16_t num = 0; + + string_printf(temp_str, "%s/%s%s", dirname, filename, fileextension); + + while(storage_common_stat(file_worker->api, string_get_cstr(temp_str), NULL) == FSE_OK) { + num++; + string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); + } + + if(num) { + string_printf(nextfilename, "%s%d", filename, num); + } else { + string_printf(nextfilename, "%s", filename); + } + + string_clear(temp_str); +} + bool file_worker_read(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) { if(!file_worker_read_internal(file_worker, buffer, bytes_to_read)) { return false; @@ -88,7 +114,7 @@ bool file_worker_read(FileWorker* file_worker, void* buffer, uint16_t bytes_to_r } bool file_worker_read_until(FileWorker* file_worker, string_t str_result, char separator) { - string_clean(str_result); + string_reset(str_result); const uint8_t buffer_size = 32; uint8_t buffer[buffer_size]; @@ -302,7 +328,7 @@ bool file_worker_read_until_buffered( // fs_api->file.read now supports up to 512 bytes reading at a time furi_assert(file_buf_size <= 512); - string_clean(str_result); + string_reset(str_result); size_t newline_index = 0; bool found_eol = false; bool max_length_exceeded = false; @@ -341,7 +367,7 @@ bool file_worker_read_until_buffered( file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); if(storage_file_get_error(file_worker->file) != FSE_OK) { file_worker_show_error_internal(file_worker, "Cannot read\nfile"); - string_clear(str_result); + string_reset(str_result); *file_buf_cnt = 0; break; } @@ -350,12 +376,16 @@ bool file_worker_read_until_buffered( } } - if(max_length_exceeded) string_clean(str_result); + if(max_length_exceeded) string_reset(str_result); return string_size(str_result) || *file_buf_cnt; } -bool file_worker_get_value_from_key(FileWorker* file_worker, string_t key, char delimiter, string_t value) { +bool file_worker_get_value_from_key( + FileWorker* file_worker, + string_t key, + char delimiter, + string_t value) { bool found = false; string_t next_line; string_t next_key; diff --git a/lib/app-scened-template/file-worker.h b/lib/app-scened-template/file-worker.h index 3a2b83de..1b5f3407 100644 --- a/lib/app-scened-template/file-worker.h +++ b/lib/app-scened-template/file-worker.h @@ -68,6 +68,22 @@ bool file_worker_mkdir(FileWorker* file_worker, const char* dirname); */ bool file_worker_remove(FileWorker* file_worker, const char* filename); +/** + * @brief Get next free filename. + * + * @param file_worker FileWorker instance + * @param dirname + * @param filename + * @param fileextension + * @param nextfilename return name + */ +void file_worker_get_next_filename( + FileWorker* file_worker, + const char* dirname, + const char* filename, + const char* fileextension, + string_t nextfilename); + /** * @brief Reads data from a file. * @@ -194,7 +210,11 @@ bool file_worker_read_until_buffered( * @param value value for given key * @return true on success */ -bool file_worker_get_value_from_key(FileWorker* file_worker, string_t key, char delimiter, string_t value); +bool file_worker_get_value_from_key( + FileWorker* file_worker, + string_t key, + char delimiter, + string_t value); /** * @brief Check whether file exist or not diff --git a/lib/common-api/task-control-block.h b/lib/common-api/task-control-block.h index 8d7b9b91..32d2edec 100644 --- a/lib/common-api/task-control-block.h +++ b/lib/common-api/task-control-block.h @@ -1,7 +1,7 @@ #pragma once -#include "FreeRTOS.h" - +#include +#include typedef struct /* The old naming convention is used to prevent breaking kernel aware debuggers. */ { diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index d83cae05..578e3849 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -5,6 +5,8 @@ #include #include +#define TAG "Gauge" + uint16_t bq27220_read_word(uint8_t address) { uint8_t buffer[2] = {address}; uint16_t ret; @@ -70,13 +72,13 @@ bool bq27220_init(const ParamCEDV* cedv) { uint32_t timeout = 100; uint16_t design_cap = bq27220_get_design_capacity(); if(cedv->design_cap == design_cap) { - FURI_LOG_I("gauge", "Skip battery profile update"); + FURI_LOG_I(TAG, "Skip battery profile update"); return true; } - FURI_LOG_I("gauge", "Start updating battery profile"); + FURI_LOG_I(TAG, "Start updating battery profile"); OperationStatus status = {}; if(!bq27220_control(Control_ENTER_CFG_UPDATE)) { - FURI_LOG_E("gauge", "Can't configure update"); + FURI_LOG_E(TAG, "Can't configure update"); return false; }; @@ -111,10 +113,10 @@ bool bq27220_init(const ParamCEDV* cedv) { delay_us(10000); design_cap = bq27220_get_design_capacity(); if(cedv->design_cap == design_cap) { - FURI_LOG_I("gauge", "Battery profile update success"); + FURI_LOG_I(TAG, "Battery profile update success"); return true; } else { - FURI_LOG_E("gauge", "Battery profile update failed"); + FURI_LOG_E(TAG, "Battery profile update failed"); return false; } } diff --git a/lib/flipper_file/file_helper.c b/lib/flipper_file/file_helper.c new file mode 100644 index 00000000..0eabf0d1 --- /dev/null +++ b/lib/flipper_file/file_helper.c @@ -0,0 +1,204 @@ +#include "file_helper.h" + +const char flipper_file_eoln = '\n'; +const char flipper_file_eolr = '\r'; + +bool file_helper_seek(File* file, int32_t offset) { + uint64_t position = storage_file_tell(file); + return storage_file_seek(file, position + offset, true); +} + +bool file_helper_write_hex(File* file, const uint8_t* data, const uint16_t data_size) { + const uint8_t byte_text_size = 3; + char byte_text[byte_text_size]; + + bool result = true; + uint16_t bytes_written; + for(uint8_t i = 0; i < data_size; i++) { + snprintf(byte_text, byte_text_size, "%02X", data[i]); + + if(i != 0) { + // space + const char space = ' '; + bytes_written = storage_file_write(file, &space, sizeof(char)); + if(bytes_written != sizeof(char)) { + result = false; + break; + } + } + + bytes_written = storage_file_write(file, &byte_text, strlen(byte_text)); + if(bytes_written != strlen(byte_text)) { + result = false; + break; + } + } + + return result; +} + +bool file_helper_read_line(File* file, string_t str_result) { + string_reset(str_result); + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + + do { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + // TODO process EOF + if(bytes_were_read == 0) break; + + bool result = false; + bool error = false; + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + break; + } else if(buffer[i] == flipper_file_eolr) { + // Ignore + } else { + string_push_back(str_result, buffer[i]); + } + } + + if(result || error) { + break; + } + } while(true); + + return string_size(str_result) != 0; +} + +bool file_helper_seek_to_next_line(File* file) { + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + bool result = false; + bool error = false; + + do { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + if(bytes_were_read == 0) { + if(storage_file_eof(file)) { + result = true; + break; + } + } + + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + break; + } + } + + if(result || error) { + break; + } + } while(true); + + return result; +} + +bool file_helper_read_value(File* file, string_t value, bool* last) { + string_reset(value); + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + bool result = false; + bool error = false; + + while(true) { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + + if(bytes_were_read == 0) { + // check EOF + if(storage_file_eof(file) && string_size(value) > 0) { + result = true; + *last = true; + break; + } + } + + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + if(string_size(value) > 0) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + *last = true; + break; + } else { + error = true; + } + } else if(buffer[i] == ' ') { + if(string_size(value) > 0) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + *last = false; + break; + } + + } else if(buffer[i] == flipper_file_eolr) { + // Ignore + } else { + string_push_back(value, buffer[i]); + } + } + + if(error || result) break; + } + + return result; +} + +bool file_helper_write(File* file, const void* data, uint16_t data_size) { + uint16_t bytes_written = storage_file_write(file, data, data_size); + return bytes_written == data_size; +} + +bool file_helper_write_eol(File* file) { + return file_helper_write(file, &flipper_file_eoln, sizeof(char)); +} + +bool file_helper_copy(File* file_from, File* file_to, uint64_t start_offset, uint64_t stop_offset) { + bool result = false; + + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + uint64_t current_offset = start_offset; + + if(storage_file_seek(file_from, start_offset, true)) { + do { + int32_t bytes_count = MIN(buffer_size, stop_offset - current_offset); + if(bytes_count <= 0) { + result = true; + break; + } + + uint16_t bytes_were_read = storage_file_read(file_from, buffer, bytes_count); + if(bytes_were_read != bytes_count) break; + + uint16_t bytes_were_written = storage_file_write(file_to, buffer, bytes_count); + if(bytes_were_written != bytes_count) break; + + current_offset += bytes_count; + } while(true); + } + + return result; +} \ No newline at end of file diff --git a/lib/flipper_file/file_helper.h b/lib/flipper_file/file_helper.h new file mode 100644 index 00000000..66306797 --- /dev/null +++ b/lib/flipper_file/file_helper.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char flipper_file_eoln; +extern const char flipper_file_eolr; + +/** + * Negative seek helper + * @param file + * @param offset + * @return bool + */ +bool file_helper_seek(File* file, int32_t offset); + +/** + * Writes data to a file as a hexadecimal array. + * @param file + * @param data + * @param data_size + * @return true on success write + */ +bool file_helper_write_hex(File* file, const uint8_t* data, const uint16_t data_size); + +/** + * Reads data as a string from the stored rw pointer to the \\n symbol position. Ignores \r. + * @param file + * @param str_result + * @return true on success read + */ +bool file_helper_read_line(File* file, string_t str_result); + +/** + * Moves the RW pointer to the beginning of the next line + * @param file + * @return bool + */ +bool file_helper_seek_to_next_line(File* file); + +/** + * Read one value from array-like string (separated by ' ') + * @param file + * @param value + * @return bool + */ +bool file_helper_read_value(File* file, string_t value, bool* last); + +/** + * Write helper + * @param file + * @param data + * @param data_size + * @return bool + */ +bool file_helper_write(File* file, const void* data, uint16_t data_size); + +/** + * Write EOL + * @param file + * @return bool + */ +bool file_helper_write_eol(File* file); + +/** + * Appends part of one file to the end of another file + * @param file_from + * @param file_to + * @param start_offset + * @param stop_offset + * @return bool + */ +bool file_helper_copy(File* file_from, File* file_to, uint64_t start_offset, uint64_t stop_offset); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_file/flipper_file.c b/lib/flipper_file/flipper_file.c new file mode 100644 index 00000000..21cd5f26 --- /dev/null +++ b/lib/flipper_file/flipper_file.c @@ -0,0 +1,395 @@ +#include +#include "file_helper.h" +#include "flipper_file_helper.h" +#include "flipper_file.h" +#include "flipper_file_i.h" +#include +#include + +FlipperFile* flipper_file_alloc(Storage* storage) { + // furi_assert(storage); + + FlipperFile* flipper_file = malloc(sizeof(FlipperFile)); + flipper_file->storage = storage; + flipper_file->file = storage_file_alloc(flipper_file->storage); + + return flipper_file; +} + +void flipper_file_free(FlipperFile* flipper_file) { + furi_assert(flipper_file); + if(storage_file_is_open(flipper_file->file)) { + storage_file_close(flipper_file->file); + } + storage_file_free(flipper_file->file); + free(flipper_file); +} + +bool flipper_file_open_existing(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + bool result = storage_file_open( + flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_OPEN_EXISTING); + return result; +} + +bool flipper_file_open_append(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + + bool result = + storage_file_open(flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_OPEN_APPEND); + + // Add EOL if it is not there + if(storage_file_size(flipper_file->file) >= 1) { + do { + char last_char; + result = false; + + if(!file_helper_seek(flipper_file->file, -1)) break; + + uint16_t bytes_were_read = storage_file_read(flipper_file->file, &last_char, 1); + if(bytes_were_read != 1) break; + + if(last_char != flipper_file_eoln) { + if(!file_helper_write_eol(flipper_file->file)) break; + } + + result = true; + } while(false); + } + + return result; +} + +bool flipper_file_open_always(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + bool result = storage_file_open( + flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_CREATE_ALWAYS); + return result; +} + +bool flipper_file_open_new(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + bool result = storage_file_open( + flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_CREATE_NEW); + return result; +} + +bool flipper_file_close(FlipperFile* flipper_file) { + furi_assert(flipper_file); + if(storage_file_is_open(flipper_file->file)) { + return storage_file_close(flipper_file->file); + } + return true; +} + +bool flipper_file_rewind(FlipperFile* flipper_file) { + furi_assert(flipper_file); + return storage_file_seek(flipper_file->file, 0, true); +} + +bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version) { + bool result = false; + do { + result = flipper_file_read_string(flipper_file, flipper_file_filetype_key, filetype); + if(!result) break; + result = flipper_file_read_uint32(flipper_file, flipper_file_version_key, version, 1); + if(!result) break; + } while(false); + + return result; +} + +bool flipper_file_write_header( + FlipperFile* flipper_file, + string_t filetype, + const uint32_t version) { + bool result = false; + do { + result = flipper_file_write_string(flipper_file, flipper_file_filetype_key, filetype); + if(!result) break; + result = flipper_file_write_uint32(flipper_file, flipper_file_version_key, &version, 1); + if(!result) break; + } while(false); + + return result; +} + +bool flipper_file_write_header_cstr( + FlipperFile* flipper_file, + const char* filetype, + const uint32_t version) { + bool result = false; + string_t value; + string_init_set(value, filetype); + result = flipper_file_write_header(flipper_file, value, version); + string_clear(value); + return result; +} + +bool flipper_file_get_value_count(FlipperFile* flipper_file, const char* key, uint32_t* count) { + furi_assert(flipper_file); + bool result = false; + bool last = false; + + string_t value; + string_init(value); + + uint32_t position = storage_file_tell(flipper_file->file); + do { + if(!flipper_file_seek_to_key(flipper_file->file, key)) break; + + // Balance between speed and memory consumption + // I prefer lower speed but less memory consumption + *count = 0; + + result = true; + while(true) { + if(!file_helper_read_value(flipper_file->file, value, &last)) { + result = false; + break; + } + + *count = *count + 1; + if(last) break; + } + + } while(true); + + if(!storage_file_seek(flipper_file->file, position, true)) { + result = false; + } + + string_clear(value); + return result; +} + +bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data) { + furi_assert(flipper_file); + + bool result = false; + do { + const char comment_buffer[2] = {flipper_file_comment, ' '}; + result = file_helper_write(flipper_file->file, comment_buffer, sizeof(comment_buffer)); + if(!result) break; + + result = file_helper_write(flipper_file->file, string_get_cstr(data), string_size(data)); + if(!result) break; + + result = file_helper_write_eol(flipper_file->file); + } while(false); + + return result; +} + +bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data) { + bool result = false; + string_t value; + string_init_set(value, data); + result = flipper_file_write_comment(flipper_file, value); + string_clear(value); + return result; +} + +bool flipper_file_delete_key_and_call( + FlipperFile* flipper_file, + const char* key, + flipper_file_cb call, + const char* cb_key, + const void* cb_data, + const uint16_t cb_data_size) { + bool result = false; + File* scratch_file = storage_file_alloc(flipper_file->storage); + + do { + // get size + uint64_t file_size = storage_file_size(flipper_file->file); + if(file_size == 0) break; + + if(!storage_file_seek(flipper_file->file, 0, true)) break; + + // find key + if(!flipper_file_seek_to_key(flipper_file->file, key)) break; + // get key start position + uint64_t start_position = storage_file_tell(flipper_file->file) - strlen(key); + if(start_position >= 2) { + start_position -= 2; + } else { + // something wrong + break; + } + + // get value end position + if(!file_helper_seek_to_next_line(flipper_file->file)) break; + uint64_t end_position = storage_file_tell(flipper_file->file); + // newline symbol + if(end_position < file_size) { + end_position += 1; + } + + // open scratchpad + const char* scratch_name = ""; + if(!flipper_file_get_scratchpad_name(&scratch_name)) break; + + if(!storage_file_open( + scratch_file, scratch_name, FSAM_READ | FSAM_WRITE, FSOM_CREATE_ALWAYS)) + break; + + // copy key file before key to scratchpad + if(!file_helper_copy(flipper_file->file, scratch_file, 0, start_position)) break; + + // do something in between if needed + if(call != NULL) { + if(!call(scratch_file, cb_key, cb_data, cb_data_size)) break; + }; + + // copy key file after key value to scratchpad + if(!file_helper_copy(flipper_file->file, scratch_file, end_position, file_size)) break; + + file_size = storage_file_tell(scratch_file); + if(file_size == 0) break; + + if(!storage_file_seek(flipper_file->file, 0, true)) break; + + // copy whole scratchpad file to the original file + if(!file_helper_copy(scratch_file, flipper_file->file, 0, file_size)) break; + + // and truncate original file + if(!storage_file_truncate(flipper_file->file)) break; + + // close and remove scratchpad file + if(!storage_file_close(scratch_file)) break; + if(storage_common_remove(flipper_file->storage, scratch_name) != FSE_OK) break; + result = true; + } while(false); + + storage_file_free(scratch_file); + + return result; +} + +bool flipper_file_delete_key(FlipperFile* flipper_file, const char* key) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call(flipper_file, key, NULL, NULL, NULL, 0); +} + +bool flipper_file_write_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size, + FlipperFileValueType type) { + bool result = false; + string_t value; + string_init(value); + + do { + result = flipper_file_write_key(file, key); + if(!result) break; + + for(uint16_t i = 0; i < data_size; i++) { + switch(type) { + case FlipperFileValueHex: { + const uint8_t* data = _data; + string_printf(value, "%02X", data[i]); + }; break; + case FlipperFileValueFloat: { + const float* data = _data; + string_printf(value, "%f", data[i]); + }; break; + case FlipperFileValueInt32: { + const int32_t* data = _data; + string_printf(value, "%" PRIi32, data[i]); + }; break; + case FlipperFileValueUint32: { + const uint32_t* data = _data; + string_printf(value, "%" PRId32, data[i]); + }; break; + } + + if((i + 1) < data_size) { + string_cat(value, " "); + } + + result = file_helper_write(file, string_get_cstr(value), string_size(value)); + if(!result) break; + } + + result = file_helper_write_eol(file); + } while(false); + + string_clear(value); + return result; +} + +bool flipper_file_read_internal( + File* file, + const char* key, + void* _data, + const uint16_t data_size, + FlipperFileValueType type) { + bool result = false; + string_t value; + string_init(value); + + if(flipper_file_seek_to_key(file, key)) { + result = true; + for(uint16_t i = 0; i < data_size; i++) { + bool last = false; + result = file_helper_read_value(file, value, &last); + if(result) { + int scan_values = 0; + switch(type) { + case FlipperFileValueHex: { + uint8_t* data = _data; + // sscanf "%02X" does not work here + if(hex_chars_to_uint8( + string_get_char(value, 0), string_get_char(value, 1), &data[i])) { + scan_values = 1; + } + }; break; + case FlipperFileValueFloat: { + float* data = _data; + // newlib-nano does not have sscanf for floats + // scan_values = sscanf(string_get_cstr(value), "%f", &data[i]); + char* end_char; + data[i] = strtof(string_get_cstr(value), &end_char); + if(*end_char == 0) { + // very probably ok + scan_values = 1; + } + }; break; + case FlipperFileValueInt32: { + int32_t* data = _data; + scan_values = sscanf(string_get_cstr(value), "%" PRIi32, &data[i]); + }; break; + case FlipperFileValueUint32: { + uint32_t* data = _data; + scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]); + }; break; + } + + if(scan_values != 1) { + result = false; + break; + } + } else { + break; + } + + if(last && ((i + 1) != data_size)) { + result = false; + break; + } + } + } + + string_clear(value); + return result; +} + +File* flipper_file_get_file(FlipperFile* flipper_file) { + furi_assert(flipper_file); + furi_assert(flipper_file->file); + + return flipper_file->file; +} \ No newline at end of file diff --git a/lib/toolbox/flipper-file.h b/lib/flipper_file/flipper_file.h similarity index 50% rename from lib/toolbox/flipper-file.h rename to lib/flipper_file/flipper_file.h index db6ad938..4c0f6cf1 100644 --- a/lib/toolbox/flipper-file.h +++ b/lib/flipper_file/flipper_file.h @@ -17,8 +17,10 @@ * * ~~~~~~~~~~~~~~~~~~~~~ * String: text - * Uint32: 1 - * Hex Array: A4 B3 C2 D1 12 FF + * Int32: 1 2 -3 4 + * Uint32: 1 2 3 4 + * Float: 1.0 1234.654 + * Hex: A4 B3 C2 D1 12 FF * ~~~~~~~~~~~~~~~~~~~~~ * * End of line is LF when writing, but CR is supported when reading. @@ -33,7 +35,7 @@ * # Just test file * String: String value * UINT: 1234 - * Hex Array: 00 01 FF A3 + * Hex: 00 01 FF A3 * ~~~~~~~~~~~~~~~~~~~~~ * * Writing: @@ -48,12 +50,12 @@ * const uint16_t array_size = 4; * const uint8_t* array[array_size] = {0x00, 0x01, 0xFF, 0xA3}; * - * if(!flipper_file_new_write(file, "/ext/flipper_file_test")) break; + * if(!flipper_file_open_new(file, "/ext/flipper_file_test")) break; * if(!flipper_file_write_header_cstr(file, "Flipper Test File", version)) break; * if(!flipper_file_write_comment_cstr(file, "Just test file")) break; * if(!flipper_file_write_string_cstr(file, "String", string_value)) break; - * if(!flipper_file_flipper_file_write_uint32(file, "UINT", uint32_value)) break; - * if(!flipper_file_write_hex_array(file, "Hex Array", array, array_size)) break; + * if(!flipper_file_flipper_file_write_uint32(file, "UINT", &uint32_value, 1)) break; + * if(!flipper_file_write_hex(file, "Hex Array", array, array_size)) break; * * // signal that the file was written successfully * } while(0); @@ -77,11 +79,11 @@ * string_init(file_type); * string_init(string_value); * - * if(!flipper_file_open_read(file, "/ext/flipper_file_test")) break; + * if(!flipper_file_open_existing(file, "/ext/flipper_file_test")) break; * if(!flipper_file_read_header(file, file_type, &version)) break; * if(!flipper_file_read_string(file, "String", string_value)) break; - * if(!flipper_file_read_uint32(file, "UINT", &uint32_value)) break; - * if(!flipper_file_read_hex_array(file, "Hex Array", array, array_size)) break; + * if(!flipper_file_read_uint32(file, "UINT", &uint32_value, 1)) break; + * if(!flipper_file_read_hex(file, "Hex Array", array, array_size)) break; * * // signal that the file was read successfully * } while(0); @@ -118,20 +120,36 @@ FlipperFile* flipper_file_alloc(Storage* storage); void flipper_file_free(FlipperFile* flipper_file); /** - * Open file for reading. + * Open existing file. * @param flipper_file Pointer to a FlipperFile instance * @param filename File name and path * @return True on success */ -bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename); +bool flipper_file_open_existing(FlipperFile* flipper_file, const char* filename); /** - * Open file for writing. Creates a new file, or deletes the contents of the file if it already exists. + * Open existing file for writing and add values to the end of file. * @param flipper_file Pointer to a FlipperFile instance * @param filename File name and path * @return True on success */ -bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename); +bool flipper_file_open_append(FlipperFile* flipper_file, const char* filename); + +/** + * Open file. Creates a new file, or deletes the contents of the file if it already exists. + * @param flipper_file Pointer to a FlipperFile instance + * @param filename File name and path + * @return True on success + */ +bool flipper_file_open_always(FlipperFile* flipper_file, const char* filename); + +/** + * Open file. Creates a new file, fails if file already exists. + * @param flipper_file Pointer to a FlipperFile instance + * @param filename File name and path + * @return True on success + */ +bool flipper_file_open_new(FlipperFile* flipper_file, const char* filename); /** * Close the file. @@ -140,6 +158,13 @@ bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename); */ bool flipper_file_close(FlipperFile* flipper_file); +/** + * Rewind the file RW pointer. + * @param flipper_file Pointer to a FlipperFile instance + * @return True on success + */ +bool flipper_file_rewind(FlipperFile* flipper_file); + /** * Read the header (file type and version) from the file. * @param flipper_file Pointer to a FlipperFile instance @@ -173,6 +198,15 @@ bool flipper_file_write_header_cstr( const char* filetype, const uint32_t version); +/** + * Get the count of values by key + * @param flipper_file + * @param key + * @param count + * @return bool + */ +bool flipper_file_get_value_count(FlipperFile* flipper_file, const char* key, uint32_t* count); + /** * Read a string from a file by Key * @param flipper_file Pointer to a FlipperFile instance @@ -201,22 +235,116 @@ bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, strin bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data); /** - * Read uint32 from a file by Key + * Read array of uint32 from a file by Key * @param flipper_file Pointer to a FlipperFile instance * @param key Key * @param data Value + * @param data_size Values count * @return True on success */ -bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data); +bool flipper_file_read_uint32( + FlipperFile* flipper_file, + const char* key, + uint32_t* data, + const uint16_t data_size); /** - * Write key and uint32 to file. + * Write key and array of uint32 to file. * @param flipper_file Pointer to a FlipperFile instance * @param key Key * @param data Value + * @param data_size Values count * @return True on success */ -bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data); +bool flipper_file_write_uint32( + FlipperFile* flipper_file, + const char* key, + const uint32_t* data, + const uint16_t data_size); + +/** + * Read array of int32 from a file by Key + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_read_int32( + FlipperFile* flipper_file, + const char* key, + int32_t* data, + const uint16_t data_size); + +/** + * Write key and array of int32 to file. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_write_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size); + +/** + * Read array of float from a file by Key + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_read_float( + FlipperFile* flipper_file, + const char* key, + float* data, + const uint16_t data_size); + +/** + * Write key and array of float to file. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_write_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size); + +/** + * Read hex array from a file by Key + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Value size + * @return True on success + */ +bool flipper_file_read_hex( + FlipperFile* flipper_file, + const char* key, + uint8_t* data, + const uint16_t data_size); + +/** + * Write key and hex array to file. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_write_hex( + FlipperFile* flipper_file, + const char* key, + const uint8_t* data, + const uint16_t data_size); /** * Write comment to file. @@ -235,33 +363,96 @@ bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data); bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data); /** - * Read hex array from a file by Key + * Removes the first matching key and its value from the file. Changes the RW pointer to an undefined position. * @param flipper_file Pointer to a FlipperFile instance * @param key Key - * @param data Value - * @param data_size Value size * @return True on success */ -bool flipper_file_read_hex_array( +bool flipper_file_delete_key(FlipperFile* flipper_file, const char* key); + +/** + * Updates the value of the first matching key to a string value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_file_update_string(FlipperFile* flipper_file, const char* key, string_t data); + +/** + * Updates the value of the first matching key to a string value. Plain C version. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_file_update_string_cstr(FlipperFile* flipper_file, const char* key, const char* data); + +/** + * Updates the value of the first matching key to a uint32 array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_update_uint32( FlipperFile* flipper_file, const char* key, - uint8_t* data, + const uint32_t* data, const uint16_t data_size); /** - * Write key and hex array to file. - * @param flipper_file Pointer to a FlipperFile instance + * Updates the value of the first matching key to a int32 array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance * @param key Key * @param data Value - * @param data_size Value size + * @param data_size Values count * @return True on success */ -bool flipper_file_write_hex_array( +bool flipper_file_update_int32( + FlipperFile* flipper_file, + 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. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_update_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size); + +/** + * Updates the value of the first matching key to a hex array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_update_hex( FlipperFile* flipper_file, const char* key, const uint8_t* data, const uint16_t data_size); +/** Get file descriptor. + * + * We higly don't recommend to use it. + * This instance is owned by FlipperFile. + * @param flipper_file + * @return File* + */ +File* flipper_file_get_file(FlipperFile* flipper_file); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_float.c b/lib/flipper_file/flipper_file_float.c new file mode 100644 index 00000000..288e545d --- /dev/null +++ b/lib/flipper_file/flipper_file_float.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_float_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueFloat); +}; + +bool flipper_file_read_float( + FlipperFile* flipper_file, + const char* key, + float* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueFloat); +} + +bool flipper_file_write_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_float_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_update_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_float_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_helper.c b/lib/flipper_file/flipper_file_helper.c new file mode 100644 index 00000000..76711109 --- /dev/null +++ b/lib/flipper_file/flipper_file_helper.c @@ -0,0 +1,118 @@ +#include "flipper_file_helper.h" + +const char* flipper_file_filetype_key = "Filetype"; +const char* flipper_file_version_key = "Version"; +const char flipper_file_delimiter = ':'; +const char flipper_file_comment = '#'; + +#ifdef __linux__ +const char* flipper_file_scratchpad = ".scratch.pad"; +#else +const char* flipper_file_scratchpad = "/any/.scratch.pad"; +#endif + +bool flipper_file_read_valid_key(File* file, string_t key) { + string_reset(key); + bool found = false; + bool error = false; + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + bool accumulate = true; + bool new_line = true; + + while(true) { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + if(bytes_were_read == 0) break; + + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + // EOL found, clean data, start accumulating data and set the new_line flag + string_reset(key); + accumulate = true; + new_line = true; + } else if(buffer[i] == flipper_file_eolr) { + // Ignore + } else if(buffer[i] == flipper_file_comment && new_line) { + // if there is a comment character and we are at the beginning of a new line + // do not accumulate comment data and reset the new_line flag + accumulate = false; + new_line = false; + } else if(buffer[i] == flipper_file_delimiter) { + if(new_line) { + // we are on a "new line" and found the delimiter + // this can only be if we have previously found some kind of key, so + // clear the data, set the flag that we no longer want to accumulate data + // and reset the new_line flag + string_reset(key); + accumulate = false; + new_line = false; + } else { + // parse the delimiter only if we are accumulating data + if(accumulate) { + // we found the delimiter, move the rw pointer to the correct location + // and signal that we have found something + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + found = true; + break; + } + } + } else { + // just new symbol, reset the new_line flag + new_line = false; + if(accumulate) { + // and accumulate data if we want + string_push_back(key, buffer[i]); + } + } + } + + if(found || error) break; + } + + return found; +} + +bool flipper_file_seek_to_key(File* file, const char* key) { + bool found = false; + string_t readed_key; + + string_init(readed_key); + + while(!storage_file_eof(file)) { + if(flipper_file_read_valid_key(file, readed_key)) { + if(string_cmp_str(readed_key, key) == 0) { + if(!file_helper_seek(file, 2)) break; + + found = true; + break; + } + } + } + string_clear(readed_key); + + return found; +} + +bool flipper_file_write_key(File* file, const char* key) { + bool result = false; + + do { + result = file_helper_write(file, key, strlen(key)); + if(!result) break; + + const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; + result = file_helper_write(file, delimiter_buffer, sizeof(delimiter_buffer)); + } while(false); + + return result; +} + +bool flipper_file_get_scratchpad_name(const char** name) { + // TODO do not rewrite existing file + *name = flipper_file_scratchpad; + return true; +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_helper.h b/lib/flipper_file/flipper_file_helper.h new file mode 100644 index 00000000..770be74e --- /dev/null +++ b/lib/flipper_file/flipper_file_helper.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include "file_helper.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char* flipper_file_filetype_key; +extern const char* flipper_file_version_key; +extern const char flipper_file_delimiter; +extern const char flipper_file_comment; + +/** + * Reads a valid key from a file as a string. + * After reading, the rw pointer will be on the flipper_file_delimiter symbol. + * Optimized not to read comments and values into RAM. + * @param file + * @param key + * @return true on success read + */ +bool flipper_file_read_valid_key(File* file, string_t key); + +/** + * Sets rw pointer to the data after the key + * @param file + * @param key + * @return true if key was found + */ +bool flipper_file_seek_to_key(File* file, const char* key); + +/** + * Write key and key delimiter + * @param file + * @param key + * @return bool + */ +bool flipper_file_write_key(File* file, const char* key); + +/** + * Get scratchpad name and path + * @param name + * @return bool + */ +bool flipper_file_get_scratchpad_name(const char** name); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_hex.c b/lib/flipper_file/flipper_file_hex.c new file mode 100644 index 00000000..602652eb --- /dev/null +++ b/lib/flipper_file/flipper_file_hex.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_hex_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueHex); +}; + +bool flipper_file_write_hex( + FlipperFile* flipper_file, + const char* key, + const uint8_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_hex_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_read_hex( + FlipperFile* flipper_file, + const char* key, + uint8_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueHex); +} + +bool flipper_file_update_hex( + FlipperFile* flipper_file, + const char* key, + const uint8_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_hex_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_i.h b/lib/flipper_file/flipper_file_i.h new file mode 100644 index 00000000..68f7fe6a --- /dev/null +++ b/lib/flipper_file/flipper_file_i.h @@ -0,0 +1,72 @@ +#include +#include + +struct FlipperFile { + File* file; + Storage* storage; +}; + +/** + * Value write type callback + */ +typedef bool (*flipper_file_cb)(File* file, const char* key, const void* data, uint16_t data_size); + +/** + * + * @param flipper_file + * @param key + * @param cb + * @param cb_key + * @param cb_data + * @param cb_data_size + * @return bool + */ +bool flipper_file_delete_key_and_call( + FlipperFile* flipper_file, + const char* key, + flipper_file_cb cb, + const char* cb_key, + const void* cb_data, + const uint16_t cb_data_size); + +/** + * Value types + */ +typedef enum { + FlipperFileValueHex, + FlipperFileValueFloat, + FlipperFileValueInt32, + FlipperFileValueUint32, +} FlipperFileValueType; + +/** + * Internal write values function + * @param file + * @param key + * @param _data + * @param data_size + * @param type + * @return bool + */ +bool flipper_file_write_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size, + FlipperFileValueType type); + +/** + * Internal read values function + * @param file + * @param key + * @param _data + * @param data_size + * @param type + * @return bool + */ +bool flipper_file_read_internal( + File* file, + const char* key, + void* _data, + const uint16_t data_size, + FlipperFileValueType type); \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_int32.c b/lib/flipper_file/flipper_file_int32.c new file mode 100644 index 00000000..6b8ad3b8 --- /dev/null +++ b/lib/flipper_file/flipper_file_int32.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_int32_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueInt32); +}; + +bool flipper_file_read_int32( + FlipperFile* flipper_file, + const char* key, + int32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueInt32); +} + +bool flipper_file_write_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_int32_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_update_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_int32_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_string.c b/lib/flipper_file/flipper_file_string.c new file mode 100644 index 00000000..6b75fcc5 --- /dev/null +++ b/lib/flipper_file/flipper_file_string.c @@ -0,0 +1,67 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_string_internal( + File* file, + const char* key, + const void* data, + const uint16_t data_size) { + bool result = false; + (void)data_size; + + do { + result = flipper_file_write_key(file, key); + if(!result) break; + + result = file_helper_write(file, string_get_cstr(data), string_size(data)); + if(!result) break; + + result = file_helper_write_eol(file); + } while(false); + + return result; +}; + +bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data) { + furi_assert(flipper_file); + + bool result = false; + if(flipper_file_seek_to_key(flipper_file->file, key)) { + if(file_helper_read_line(flipper_file->file, data)) { + result = true; + } + } + return result; +} + +bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data) { + furi_assert(flipper_file); + return flipper_file_write_string_internal(flipper_file->file, key, data, 0); +} + +bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { + bool result = false; + string_t value; + string_init_set(value, data); + result = flipper_file_write_string(flipper_file, key, value); + string_clear(value); + return result; +} + +bool flipper_file_update_string(FlipperFile* flipper_file, const char* key, string_t data) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_string_internal, key, data, 0); +} + +bool flipper_file_update_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { + bool result = false; + string_t value; + string_init_set(value, data); + result = flipper_file_update_string(flipper_file, key, value); + string_clear(value); + return result; +} diff --git a/lib/flipper_file/flipper_file_uint32.c b/lib/flipper_file/flipper_file_uint32.c new file mode 100644 index 00000000..6a3b58e4 --- /dev/null +++ b/lib/flipper_file/flipper_file_uint32.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_uint32_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueUint32); +}; + +bool flipper_file_read_uint32( + FlipperFile* flipper_file, + const char* key, + uint32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueUint32); +} + +bool flipper_file_write_uint32( + FlipperFile* flipper_file, + const char* key, + const uint32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_uint32_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_update_uint32( + FlipperFile* flipper_file, + const char* key, + const uint32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_uint32_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/irda/worker/irda_worker.c b/lib/irda/worker/irda_worker.c index 98b091f4..b9324334 100644 --- a/lib/irda/worker/irda_worker.c +++ b/lib/irda/worker/irda_worker.c @@ -7,6 +7,7 @@ #include #include #include + #include #include @@ -215,7 +216,7 @@ IrdaWorker* irda_worker_alloc() { IrdaWorker* instance = furi_alloc(sizeof(IrdaWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "irda_worker"); + furi_thread_set_name(instance->thread, "IrdaWorker"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); diff --git a/lib/lfs_config.h b/lib/lfs_config.h new file mode 100644 index 00000000..7a883982 --- /dev/null +++ b/lib/lfs_config.h @@ -0,0 +1,187 @@ +#pragma once + +#include + +#ifdef FURI_NDEBUG +#define LFS_NO_ASSERT +#endif + +#define LFS_TAG "Lfs" + +#define LFS_TRACE(...) FURI_LOG_D(LFS_TAG, __VA_ARGS__); + +#define LFS_DEBUG(...) FURI_LOG_I(LFS_TAG, __VA_ARGS__); + +#define LFS_WARN(...) FURI_LOG_W(LFS_TAG, __VA_ARGS__); + +#define LFS_ERROR(...) FURI_LOG_E(LFS_TAG, __VA_ARGS__); + +#define LFS_ASSERT furi_assert + +// Because crc +#undef LFS_CONFIG + +// System includes +#include +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_DEBUG) || \ + !defined(LFS_NO_WARN) || \ + !defined(LFS_NO_ERROR) || \ + defined(LFS_YES_TRACE) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Align to nearest multiple of a size +static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { + return a - (a % alignment); +} + +static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { + return lfs_aligndown(a + alignment-1, alignment); +} + +// Find the smallest power of 2 greater than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert between 32-bit little-endian and native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Convert between 32-bit big-endian and native order +static inline uint32_t lfs_frombe32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return __builtin_bswap32(a); +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return a; +#else + return (((uint8_t*)&a)[0] << 24) | + (((uint8_t*)&a)[1] << 16) | + (((uint8_t*)&a)[2] << 8) | + (((uint8_t*)&a)[3] << 0); +#endif +} + +static inline uint32_t lfs_tobe32(uint32_t a) { + return lfs_frombe32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +// Note, memory must be 64-bit aligned +static inline void *lfs_malloc(size_t size) { +#ifndef LFS_NO_MALLOC + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#ifndef LFS_NO_MALLOC + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/lib/lib.mk b/lib/lib.mk index f1b3ddd9..34f43bd4 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -37,7 +37,7 @@ C_SOURCES += $(FATFS_DIR)/option/unicode.c # Little FS LITTLEFS_DIR = $(LIB_DIR)/littlefs -CFLAGS += -I$(LITTLEFS_DIR) +CFLAGS += -I$(LITTLEFS_DIR) -DLFS_CONFIG=lfs_config.h C_SOURCES += $(LITTLEFS_DIR)/lfs.c C_SOURCES += $(LITTLEFS_DIR)/lfs_util.c @@ -120,3 +120,7 @@ C_SOURCES += $(wildcard $(LIB_DIR)/nanopb/*.c) # heatshrink CFLAGS += -I$(LIB_DIR)/heatshrink C_SOURCES += $(wildcard $(LIB_DIR)/heatshrink/*.c) + +# Toolbox +CFLAGS += -I$(LIB_DIR)/flipper_file +C_SOURCES += $(wildcard $(LIB_DIR)/flipper_file/*.c) \ No newline at end of file diff --git a/lib/mlib b/lib/mlib index 3c83e408..62c8ac3e 160000 --- a/lib/mlib +++ b/lib/mlib @@ -1 +1 @@ -Subproject commit 3c83e4088ccb6d5513c08a7c6475b3cbdba76796 +Subproject commit 62c8ac3e5d4a7a4f8757328e7a80286fde2686b6 diff --git a/lib/subghz/protocols/subghz_protocol_came.c b/lib/subghz/protocols/subghz_protocol_came.c index cb1c6720..47597775 100644 --- a/lib/subghz/protocols/subghz_protocol_came.c +++ b/lib/subghz/protocols/subghz_protocol_came.c @@ -28,8 +28,8 @@ SubGhzProtocolCame* subghz_protocol_came_alloc() { instance->common.te_delta = 150; instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_came_to_str; - instance->common.to_save_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_came_to_save_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 = @@ -88,8 +88,6 @@ void subghz_protocol_came_parse(SubGhzProtocolCame* instance, bool level, uint32 instance->common.te_delta * 51)) { //Need protocol 36 te_short //Found header CAME instance->common.parser_step = CameDecoderStepFoundStartBit; - } else { - instance->common.parser_step = CameDecoderStepReset; } break; case CameDecoderStepFoundStartBit: @@ -169,54 +167,16 @@ void subghz_protocol_came_to_str(SubGhzProtocolCame* instance, string_t output) code_found_reverse_lo); } -void subghz_protocol_came_to_save_str(SubGhzProtocolCame* instance, string_t output) { - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found & 0x00000000ffffffff)); +bool subghz_protocol_came_to_save_file(SubGhzProtocolCame* instance, FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_came_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolCame* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - uint32_t temp_key = 0; - res = sscanf(string_get_cstr(temp_str), "Key: %08lX\n", &temp_key); - if(res != 1) { - break; - } - instance->common.code_last_found = (uint64_t)temp_key; - - loaded = true; - } while(0); - - string_clear(temp_str); - - return loaded; + FlipperFile* flipper_file, + SubGhzProtocolCame* instance, + const char* file_path) { + return subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); } void subghz_decoder_came_to_load_protocol(SubGhzProtocolCame* instance, void* context) { diff --git a/lib/subghz/protocols/subghz_protocol_came.h b/lib/subghz/protocols/subghz_protocol_came.h index 3edcd8f9..22805c6b 100644 --- a/lib/subghz/protocols/subghz_protocol_came.h +++ b/lib/subghz/protocols/subghz_protocol_came.h @@ -45,26 +45,29 @@ void subghz_protocol_came_parse(SubGhzProtocolCame* instance, bool level, uint32 */ void subghz_protocol_came_to_str(SubGhzProtocolCame* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolCame instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_came_to_save_str(SubGhzProtocolCame* instance, string_t output); +bool subghz_protocol_came_to_save_file(SubGhzProtocolCame* instance, FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolCame instance + * @param file_path - file path * @return bool */ bool subghz_protocol_came_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolCame* instance); + FlipperFile* flipper_file, + 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); \ No newline at end of file +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 index 0422ff6a..fbf9ca38 100644 --- a/lib/subghz/protocols/subghz_protocol_came_atomo.c +++ b/lib/subghz/protocols/subghz_protocol_came_atomo.c @@ -1,10 +1,16 @@ #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 { @@ -22,14 +28,8 @@ SubGhzProtocolCameAtomo* subghz_protocol_came_atomo_alloc() { instance->common.te_delta = 250; instance->common.type_protocol = SubGhzProtocolCommonTypeStatic; instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_came_atomo_to_str; - // instance->common.to_save_string = - // (SubGhzProtocolCommonGetStrSave)subghz_protocol_came_atomo_to_save_str; - //instance->common.to_load_protocol_from_file = - // (SubGhzProtocolCommonLoadFromFile)subghz_protocol_came_atomo_to_load_protocol_from_file; instance->common.to_load_protocol = (SubGhzProtocolCommonLoadFromRAW)subghz_decoder_came_atomo_to_load_protocol; - // instance->common.get_upload_protocol = - // (SubGhzProtocolCommonEncoderGetUpLoad)subghz_protocol_came_atomo_send_key; return instance; } @@ -39,11 +39,112 @@ void subghz_protocol_came_atomo_free(SubGhzProtocolCameAtomo* 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) { @@ -78,8 +179,6 @@ void subghz_protocol_came_atomo_parse( ManchesterEventShortLow, &instance->manchester_saved_state, NULL); - } else { - instance->common.parser_step = CameAtomoDecoderStepReset; } break; case CameAtomoDecoderStepDecoderData: @@ -89,26 +188,10 @@ void subghz_protocol_came_atomo_parse( } 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 >= + 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; - // 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; - // FURI_LOG_I( - // "ATOMO", - // "%08lX%08lX %08lX%08lX %d", - // code_found_hi, - // code_found_lo, - // code_found_reverse_hi, - // code_found_reverse_lo, - // instance->common.code_last_count_bit); if(instance->common.callback) instance->common.callback( (SubGhzProtocolCommon*)instance, instance->common.context); @@ -151,76 +234,26 @@ void subghz_protocol_came_atomo_parse( } } 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 %dbit\r\n" - "Key:0x%lX%08lX\r\n", + "%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); + code_found_lo, + instance->common.serial, + instance->common.btn, + instance->common.cnt); } -// void subghz_protocol_came_atomo_to_save_str(SubGhzProtocolCameAtomo* instance, string_t output) { -// string_printf( -// output, -// "Protocol: %s\n" -// "Bit: %d\n" -// "Key: %08lX%08lX\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 & 0xFFFFFFFF)); -// } - -// bool subghz_protocol_came_atomo_to_load_protocol_from_file( -// FileWorker* file_worker, -// SubGhzProtocolCameAtomo* instance) { -// bool loaded = false; -// string_t temp_str; -// string_init(temp_str); -// int res = 0; -// int data = 0; - -// do { -// // Read and parse bit data from 2nd line -// if(!file_worker_read_until(file_worker, temp_str, '\n')) { -// break; -// } -// res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); -// if(res != 1) { -// break; -// } -// instance->common.code_last_count_bit = (uint8_t)data; - -// // Read and parse key data from 3nd line -// if(!file_worker_read_until(file_worker, temp_str, '\n')) { -// break; -// } -// // strlen("Key: ") = 5 -// string_right(temp_str, 5); - -// uint8_t buf_key[8] = {0}; -// if(!subghz_protocol_common_read_hex(temp_str, buf_key, 8)) { -// break; -// } - -// for(uint8_t i = 0; i < 8; i++) { -// instance->common.code_last_found = instance->common.code_last_found << 8 | buf_key[i]; -// } - -// loaded = true; -// } while(0); - -// string_clear(temp_str); - -// subghz_protocol_came_atomo_remote_controller(instance); -// return loaded; -// } - void subghz_decoder_came_atomo_to_load_protocol(SubGhzProtocolCameAtomo* instance, void* context) { furi_assert(context); furi_assert(instance); diff --git a/lib/subghz/protocols/subghz_protocol_came_atomo.h b/lib/subghz/protocols/subghz_protocol_came_atomo.h index 9547fec2..cc841441 100644 --- a/lib/subghz/protocols/subghz_protocol_came_atomo.h +++ b/lib/subghz/protocols/subghz_protocol_came_atomo.h @@ -16,15 +16,12 @@ SubGhzProtocolCameAtomo* subghz_protocol_came_atomo_alloc(); */ void subghz_protocol_came_atomo_free(SubGhzProtocolCameAtomo* instance); -// /** Get upload protocol -// * -// * @param instance - SubGhzProtocolCameAtomo instance -// * @param encoder - SubGhzProtocolCommonEncoder encoder -// * @return bool -// */ -// bool subghz_protocol_came_atomo_send_key( -// SubGhzProtocolCameAtomo* instance, -// SubGhzProtocolCommonEncoder* encoder); +/** 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 @@ -48,23 +45,6 @@ void subghz_protocol_came_atomo_parse( */ void subghz_protocol_came_atomo_to_str(SubGhzProtocolCameAtomo* instance, string_t output); -// /** Get a string to save the protocol -// * -// * @param instance - SubGhzProtocolCameAtomo instance -// * @param output - the resulting string -// */ -// void subghz_protocol_came_atomo_to_save_str(SubGhzProtocolCameAtomo* instance, string_t output); - -// /** Loading protocol from file -// * -// * @param file_worker - FileWorker file_worker -// * @param instance - SubGhzProtocolCameAtomo instance -// * @return bool -// */ -// bool subghz_protocol_came_atomo_to_load_protocol_from_file( -// FileWorker* file_worker, -// SubGhzProtocolCameAtomo* instance); - /** Loading protocol from bin data * * @param instance - SubGhzProtocolCameAtomo instance diff --git a/lib/subghz/protocols/subghz_protocol_came_twee.c b/lib/subghz/protocols/subghz_protocol_came_twee.c index 7a7274e8..54416902 100644 --- a/lib/subghz/protocols/subghz_protocol_came_twee.c +++ b/lib/subghz/protocols/subghz_protocol_came_twee.c @@ -36,8 +36,8 @@ SubGhzProtocolCameTwee* subghz_protocol_came_twee_alloc() { 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_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_came_twee_to_save_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 = @@ -247,8 +247,6 @@ void subghz_protocol_came_twee_parse( ManchesterEventShortLow, &instance->manchester_saved_state, NULL); - } else { - instance->common.parser_step = CameTweeDecoderStepReset; } break; case CameTweeDecoderStepDecoderData: @@ -327,61 +325,22 @@ void subghz_protocol_came_twee_to_str(SubGhzProtocolCameTwee* instance, string_t CNT_TO_DIP(instance->common.cnt)); } -void subghz_protocol_came_twee_to_save_str(SubGhzProtocolCameTwee* instance, string_t output) { - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX%08lX\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 & 0xFFFFFFFF)); +bool subghz_protocol_came_twee_to_save_file( + SubGhzProtocolCameTwee* instance, + FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_came_twee_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolCameTwee* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - // strlen("Key: ") = 5 - string_right(temp_str, 5); - - uint8_t buf_key[8] = {0}; - if(!subghz_protocol_common_read_hex(temp_str, buf_key, 8)) { - break; - } - - for(uint8_t i = 0; i < 8; i++) { - instance->common.code_last_found = instance->common.code_last_found << 8 | buf_key[i]; - } - - loaded = true; - } while(0); - - string_clear(temp_str); - - subghz_protocol_came_twee_remote_controller(instance); - return loaded; + FlipperFile* flipper_file, + SubGhzProtocolCameTwee* instance, + const char* file_path) { + if(subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file)) { + subghz_protocol_came_twee_remote_controller(instance); + return true; + } + return false; } void subghz_decoder_came_twee_to_load_protocol(SubGhzProtocolCameTwee* instance, void* context) { diff --git a/lib/subghz/protocols/subghz_protocol_came_twee.h b/lib/subghz/protocols/subghz_protocol_came_twee.h index 09a36e21..32e17eff 100644 --- a/lib/subghz/protocols/subghz_protocol_came_twee.h +++ b/lib/subghz/protocols/subghz_protocol_came_twee.h @@ -48,22 +48,27 @@ void subghz_protocol_came_twee_parse( */ void subghz_protocol_came_twee_to_str(SubGhzProtocolCameTwee* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolCameTwee instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_came_twee_to_save_str(SubGhzProtocolCameTwee* instance, string_t output); +bool subghz_protocol_came_twee_to_save_file( + SubGhzProtocolCameTwee* instance, + FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolCameTwee instance + * @param file_path - file path * @return bool */ bool subghz_protocol_came_twee_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolCameTwee* instance); + FlipperFile* flipper_file, + SubGhzProtocolCameTwee* instance, + const char* file_path); /** Loading protocol from bin data * diff --git a/lib/subghz/protocols/subghz_protocol_common.c b/lib/subghz/protocols/subghz_protocol_common.c index 2bc0133d..927e2157 100644 --- a/lib/subghz/protocols/subghz_protocol_common.c +++ b/lib/subghz/protocols/subghz_protocol_common.c @@ -2,7 +2,6 @@ #include #include - SubGhzProtocolCommonEncoder* subghz_protocol_encoder_common_alloc() { SubGhzProtocolCommonEncoder* instance = furi_alloc(sizeof(SubGhzProtocolCommonEncoder)); instance->upload = furi_alloc(SUBGHZ_ENCODER_UPLOAD_MAX_SIZE * sizeof(LevelDuration)); @@ -13,6 +12,9 @@ SubGhzProtocolCommonEncoder* subghz_protocol_encoder_common_alloc() { 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); } @@ -22,10 +24,34 @@ size_t subghz_encoder_common_get_repeat_left(SubGhzProtocolCommonEncoder* instan 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->repeat == 0){ + if(instance->callback) { + return instance->callback((SubGhzProtocolCommon*)instance->context); + } + + if(instance->repeat == 0) { return level_duration_reset(); } @@ -39,46 +65,53 @@ LevelDuration subghz_protocol_encoder_common_yield(void* context) { return ret; } -void subghz_protocol_common_add_bit(SubGhzProtocolCommon *common, uint8_t bit){ +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))){ +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; icallback = callback; common->context = context; } - void subghz_protocol_common_to_str(SubGhzProtocolCommon* instance, string_t output) { - if (instance->to_string) { + 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); + 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; + 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) { + if(code_found_hi > 0) { string_cat_printf( output, "Protocol %s, %d Bit\r\n" @@ -92,8 +125,7 @@ void subghz_protocol_common_to_str(SubGhzProtocolCommon* instance, string_t outp code_found_reverse_hi, code_found_reverse_lo, instance->serial, - instance->btn - ); + instance->btn); } else { string_cat_printf( output, @@ -108,8 +140,7 @@ void subghz_protocol_common_to_str(SubGhzProtocolCommon* instance, string_t outp code_found_reverse_hi, code_found_reverse_lo, instance->serial, - instance->btn - ); + instance->btn); } } } @@ -124,9 +155,9 @@ bool subghz_protocol_common_read_hex(string_t str, uint8_t* buff, uint16_t len) 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){ + if(string_size(str) > 2) { string_right(str, 2); - }else if(iname)) { + FURI_LOG_E(SUBGHZ_PARSER_TAG, "Unable to add Protocol"); + break; + } + uint32_t temp = instance->code_last_count_bit; + if(!flipper_file_write_uint32( + flipper_file, "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_file_write_hex(flipper_file, "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, + FlipperFile* flipper_file) { + furi_assert(instance); + furi_assert(flipper_file); + bool loaded = false; + string_t temp_str; + string_init(temp_str); + uint32_t temp_data = 0; + + do { + if(!flipper_file_read_uint32(flipper_file, "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_file_read_hex(flipper_file, "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 index 52e4853d..6064f895 100644 --- a/lib/subghz/protocols/subghz_protocol_common.h +++ b/lib/subghz/protocols/subghz_protocol_common.h @@ -3,7 +3,7 @@ #include #include #include -#include "file-worker.h" +#include #define bit_read(value, bit) (((value) >> (bit)) & 0x01) #define bit_set(value, bit) ((value) |= (1UL << (bit))) @@ -16,13 +16,24 @@ #define SUBGHZ_APP_FOLDER "/any/subghz" #define SUBGHZ_APP_PATH_FOLDER "/any/subghz/saved" +#define SUBGHZ_RAW_FOLDER "/ext/subghz" +#define SUBGHZ_RAW_PATH_FOLDER "/ext/subghz/saved" #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; @@ -34,11 +45,14 @@ typedef void (*SubGhzProtocolCommonCallback)(SubGhzProtocolCommon* parser, void* typedef void (*SubGhzProtocolCommonToStr)(SubGhzProtocolCommon* instance, string_t output); //Get string to save -typedef void (*SubGhzProtocolCommonGetStrSave)(SubGhzProtocolCommon* instance, string_t output); +typedef bool ( + *SubGhzProtocolCommonSaveFile)(SubGhzProtocolCommon* instance, FlipperFile* flipper_file); //Load protocol from file -typedef bool ( - *SubGhzProtocolCommonLoadFromFile)(FileWorker* file_worker, SubGhzProtocolCommon* instance); +typedef bool (*SubGhzProtocolCommonLoadFromFile)( + FlipperFile* flipper_file, + SubGhzProtocolCommon* instance, + const char* file_path); //Load protocol typedef void (*SubGhzProtocolCommonLoadFromRAW)(SubGhzProtocolCommon* instance, void* context); //Get upload encoder protocol @@ -46,6 +60,9 @@ 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; @@ -71,7 +88,7 @@ struct SubGhzProtocolCommon { /* Dump To String */ SubGhzProtocolCommonToStr to_string; /* Get string to save */ - SubGhzProtocolCommonGetStrSave to_save_string; + SubGhzProtocolCommonSaveFile to_save_file; /* Load protocol from file */ SubGhzProtocolCommonLoadFromFile to_load_protocol_from_file; /* Load protocol from RAW data */ @@ -86,6 +103,11 @@ struct SubGhzProtocolCommonEncoder { size_t front; size_t size_upload; LevelDuration* upload; + + SubGhzProtocolCommonEncoderCallback callback; + SubGhzProtocolCommonEncoderCallbackEnd callback_end; + void* context; + void* context_end; }; struct SubGhzProtocolCommonLoad { @@ -108,6 +130,16 @@ SubGhzProtocolCommonEncoder* subghz_protocol_encoder_common_alloc(); */ 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 @@ -175,3 +207,21 @@ void subghz_protocol_common_to_str(SubGhzProtocolCommon* instance, string_t outp * @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_file - FlipperFile + * @return bool + */ +bool subghz_protocol_common_to_save_file(SubGhzProtocolCommon* instance, FlipperFile* flipper_file); + +/** Loading data to a file + * + * @param instance - SubGhzProtocolCommon instance + * @param flipper_file - FlipperFile + * @return bool + */ +bool subghz_protocol_common_to_load_protocol_from_file( + SubGhzProtocolCommon* instance, + FlipperFile* flipper_file); diff --git a/lib/subghz/protocols/subghz_protocol_faac_slh.c b/lib/subghz/protocols/subghz_protocol_faac_slh.c index 5cc0a995..5bce4f8d 100644 --- a/lib/subghz/protocols/subghz_protocol_faac_slh.c +++ b/lib/subghz/protocols/subghz_protocol_faac_slh.c @@ -95,8 +95,6 @@ void subghz_protocol_faac_slh_parse(SubGhzProtocolFaacSLH* instance, bool level, if((level) && (DURATION_DIFF(duration, instance->common.te_long * 2) < instance->common.te_delta * 3)) { instance->common.parser_step = FaacSLHDecoderStepFoundPreambula; - } else { - instance->common.parser_step = FaacSLHDecoderStepReset; } break; case FaacSLHDecoderStepFoundPreambula: @@ -167,7 +165,7 @@ void subghz_protocol_faac_slh_to_str(SubGhzProtocolFaacSLH* instance, string_t o string_cat_printf( output, "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" + "Key:%lX%08lX\r\n" "Fix:%08lX \r\n" "Hop:%08lX \r\n" "Sn:%07lX Btn:%lX\r\n", diff --git a/lib/subghz/protocols/subghz_protocol_gate_tx.c b/lib/subghz/protocols/subghz_protocol_gate_tx.c index a89eb941..05e8c151 100644 --- a/lib/subghz/protocols/subghz_protocol_gate_tx.c +++ b/lib/subghz/protocols/subghz_protocol_gate_tx.c @@ -21,8 +21,8 @@ SubGhzProtocolGateTX* subghz_protocol_gate_tx_alloc(void) { 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_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_gate_tx_to_save_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 = @@ -94,8 +94,6 @@ void subghz_protocol_gate_tx_parse(SubGhzProtocolGateTX* instance, bool level, u instance->common.te_delta * 47)) { //Found Preambula instance->common.parser_step = GateTXDecoderStepFoundStartBit; - } else { - instance->common.parser_step = GateTXDecoderStepReset; } break; case GateTXDecoderStepFoundStartBit: @@ -169,55 +167,22 @@ void subghz_protocol_gate_tx_to_str(SubGhzProtocolGateTX* instance, string_t out instance->common.btn); } -void subghz_protocol_gate_tx_to_save_str(SubGhzProtocolGateTX* instance, string_t output) { - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found & 0x00000000ffffffff)); +bool subghz_protocol_gate_tx_to_save_file( + SubGhzProtocolGateTX* instance, + FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_gate_tx_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolGateTX* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - uint32_t temp_key = 0; - res = sscanf(string_get_cstr(temp_str), "Key: %08lX\n", &temp_key); - if(res != 1) { - break; - } - instance->common.code_last_found = (uint64_t)temp_key; + FlipperFile* flipper_file, + SubGhzProtocolGateTX* instance, + const char* file_path) { + if(subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file)) { subghz_protocol_gate_tx_check_remote_controller(instance); - - loaded = true; - } while(0); - - string_clear(temp_str); - - return loaded; + return true; + } + return false; } void subghz_decoder_gate_tx_to_load_protocol(SubGhzProtocolGateTX* instance, void* context) { @@ -227,4 +192,4 @@ void subghz_decoder_gate_tx_to_load_protocol(SubGhzProtocolGateTX* instance, voi 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); -} \ No newline at end of file +} diff --git a/lib/subghz/protocols/subghz_protocol_gate_tx.h b/lib/subghz/protocols/subghz_protocol_gate_tx.h index 7b91f7dc..5954f6ca 100644 --- a/lib/subghz/protocols/subghz_protocol_gate_tx.h +++ b/lib/subghz/protocols/subghz_protocol_gate_tx.h @@ -22,7 +22,9 @@ void subghz_protocol_gate_tx_free(SubGhzProtocolGateTX* instance); * @param encoder - SubGhzProtocolCommonEncoder encoder * @return bool */ -bool subghz_protocol_gate_tx_send_key(SubGhzProtocolGateTX* instance, SubGhzProtocolCommonEncoder* encoder); +bool subghz_protocol_gate_tx_send_key( + SubGhzProtocolGateTX* instance, + SubGhzProtocolCommonEncoder* encoder); /** Reset internal state * @param instance - SubGhzProtocolGateTX instance @@ -43,20 +45,27 @@ void subghz_protocol_gate_tx_parse(SubGhzProtocolGateTX* instance, bool level, u */ void subghz_protocol_gate_tx_to_str(SubGhzProtocolGateTX* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolGateTX instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_gate_tx_to_save_str(SubGhzProtocolGateTX* instance, string_t output); +bool subghz_protocol_gate_tx_to_save_file( + SubGhzProtocolGateTX* instance, + FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolGateTX instance + * @param file_path - file path * @return bool */ -bool subghz_protocol_gate_tx_to_load_protocol_from_file(FileWorker* file_worker, SubGhzProtocolGateTX* instance); +bool subghz_protocol_gate_tx_to_load_protocol_from_file( + FlipperFile* flipper_file, + SubGhzProtocolGateTX* instance, + const char* file_path); /** Loading protocol from bin data * diff --git a/lib/subghz/protocols/subghz_protocol_hormann.c b/lib/subghz/protocols/subghz_protocol_hormann.c new file mode 100644 index 00000000..4b6a8094 --- /dev/null +++ b/lib/subghz/protocols/subghz_protocol_hormann.c @@ -0,0 +1,209 @@ +#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 = furi_alloc(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, + FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); +} + +bool subghz_protocol_hormann_to_load_protocol_from_file( + FlipperFile* flipper_file, + SubGhzProtocolHormann* instance, + const char* file_path) { + return subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); +} + +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 new file mode 100644 index 00000000..6032f7a6 --- /dev/null +++ b/lib/subghz/protocols/subghz_protocol_hormann.h @@ -0,0 +1,75 @@ +#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_file - FlipperFile + * @return bool + */ +bool subghz_protocol_hormann_to_save_file( + SubGhzProtocolHormann* instance, + FlipperFile* flipper_file); + +/** Loading protocol from file + * + * @param flipper_file - FlipperFile + * @param instance - SubGhzProtocolHormann instance + * @param file_path - file path + * @return bool + */ +bool subghz_protocol_hormann_to_load_protocol_from_file( + FlipperFile* flipper_file, + 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 index e283fa30..ce47c201 100644 --- a/lib/subghz/protocols/subghz_protocol_ido.c +++ b/lib/subghz/protocols/subghz_protocol_ido.c @@ -94,8 +94,6 @@ void subghz_protocol_ido_parse(SubGhzProtocolIDo* instance, bool level, uint32_t if((level) && (DURATION_DIFF(duration, instance->common.te_short * 10) < instance->common.te_delta * 5)) { instance->common.parser_step = IDoDecoderStepFoundPreambula; - } else { - instance->common.parser_step = IDoDecoderStepReset; } break; case IDoDecoderStepFoundPreambula: diff --git a/lib/subghz/protocols/subghz_protocol_keeloq.c b/lib/subghz/protocols/subghz_protocol_keeloq.c index 9c81dc09..f3f33085 100644 --- a/lib/subghz/protocols/subghz_protocol_keeloq.c +++ b/lib/subghz/protocols/subghz_protocol_keeloq.c @@ -32,8 +32,8 @@ SubGhzProtocolKeeloq* subghz_protocol_keeloq_alloc(SubGhzKeystore* keystore) { instance->common.te_delta = 140; instance->common.type_protocol = SubGhzProtocolCommonTypeDynamic; instance->common.to_string = (SubGhzProtocolCommonToStr)subghz_protocol_keeloq_to_str; - instance->common.to_save_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_keeloq_to_save_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 = @@ -69,7 +69,7 @@ uint8_t subghz_protocol_keeloq_check_remote_controller_selector( M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { switch(manufacture_code->type) { case KEELOQ_LEARNING_SIMPLE: - //Simple Learning + // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0x3FF) == end_serial) || @@ -314,10 +314,7 @@ void subghz_protocol_keeloq_parse(SubGhzProtocolKeeloq* instance, bool level, ui DURATION_DIFF(duration, instance->common.te_short) < instance->common.te_delta) { instance->common.parser_step = KeeloqDecoderStepCheckPreambula; instance->common.header_count++; - } else { - instance->common.parser_step = KeeloqDecoderStepReset; } - break; case KeeloqDecoderStepCheckPreambula: if((!level) && @@ -422,58 +419,16 @@ void subghz_protocol_keeloq_to_str(SubGhzProtocolKeeloq* instance, string_t outp instance->common.serial); } -void subghz_protocol_keeloq_to_save_str(SubGhzProtocolKeeloq* instance, string_t output) { - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX%08lX\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 & 0xFFFFFFFF)); +bool subghz_protocol_keeloq_to_save_file(SubGhzProtocolKeeloq* instance, FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_keeloq_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolKeeloq* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - // strlen("Key: ") = 5 - string_right(temp_str, 5); - - uint8_t buf_key[8] = {0}; - if(!subghz_protocol_common_read_hex(temp_str, buf_key, 8)) { - break; - } - - for(uint8_t i = 0; i < 8; i++) { - instance->common.code_last_found = instance->common.code_last_found << 8 | buf_key[i]; - } - loaded = true; - } while(0); - string_clear(temp_str); - - return loaded; + FlipperFile* flipper_file, + SubGhzProtocolKeeloq* instance, + const char* file_path) { + return subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); } void subghz_decoder_keeloq_to_load_protocol(SubGhzProtocolKeeloq* instance, void* context) { diff --git a/lib/subghz/protocols/subghz_protocol_keeloq.h b/lib/subghz/protocols/subghz_protocol_keeloq.h index 7b2cc6d8..35c0c426 100644 --- a/lib/subghz/protocols/subghz_protocol_keeloq.h +++ b/lib/subghz/protocols/subghz_protocol_keeloq.h @@ -53,7 +53,9 @@ uint64_t subghz_protocol_keeloq_gen_key(void* context); * @param encoder - SubGhzProtocolCommonEncoder encoder * @return bool */ -bool subghz_protocol_keeloq_send_key(SubGhzProtocolKeeloq* instance, SubGhzProtocolCommonEncoder* encoder); +bool subghz_protocol_keeloq_send_key( + SubGhzProtocolKeeloq* instance, + SubGhzProtocolCommonEncoder* encoder); /** Reset internal state * @param instance - SubGhzProtocolKeeloq instance @@ -74,20 +76,25 @@ void subghz_protocol_keeloq_parse(SubGhzProtocolKeeloq* instance, bool level, ui */ void subghz_protocol_keeloq_to_str(SubGhzProtocolKeeloq* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolKeeloq instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_keeloq_to_save_str(SubGhzProtocolKeeloq* instance, string_t output); +bool subghz_protocol_keeloq_to_save_file(SubGhzProtocolKeeloq* instance, FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolKeeloq instance + * @param file_path - file path * @return bool */ -bool subghz_protocol_keeloq_to_load_protocol_from_file(FileWorker* file_worker, SubGhzProtocolKeeloq* instance); +bool subghz_protocol_keeloq_to_load_protocol_from_file( + FlipperFile* flipper_file, + SubGhzProtocolKeeloq* instance, + const char* file_path); /** Loading protocol from bin data * diff --git a/lib/subghz/protocols/subghz_protocol_kia.c b/lib/subghz/protocols/subghz_protocol_kia.c index 2ead7548..2046497b 100644 --- a/lib/subghz/protocols/subghz_protocol_kia.c +++ b/lib/subghz/protocols/subghz_protocol_kia.c @@ -78,8 +78,6 @@ void subghz_protocol_kia_parse(SubGhzProtocolKIA* instance, bool level, uint32_t instance->common.parser_step = KIADecoderStepCheckPreambula; instance->common.te_last = duration; instance->common.header_count = 0; - } else { - instance->common.parser_step = KIADecoderStepReset; } break; case KIADecoderStepCheckPreambula: diff --git a/lib/subghz/protocols/subghz_protocol_nero_radio.c b/lib/subghz/protocols/subghz_protocol_nero_radio.c index 2331e3ab..321a74e2 100644 --- a/lib/subghz/protocols/subghz_protocol_nero_radio.c +++ b/lib/subghz/protocols/subghz_protocol_nero_radio.c @@ -21,8 +21,8 @@ SubGhzProtocolNeroRadio* subghz_protocol_nero_radio_alloc(void) { 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_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_nero_radio_to_save_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 = @@ -84,23 +84,6 @@ void subghz_protocol_nero_radio_reset(SubGhzProtocolNeroRadio* instance) { instance->common.parser_step = NeroRadioDecoderStepReset; } -/** Analysis of received data - * - * @param instance SubGhzProtocolNeroRadio instance - */ -// void subghz_protocol_nero_radio_check_remote_controller(SubGhzProtocolNeroRadio* instance) { -// //пока не понятно с серийником, но код статический -// // uint64_t code_found_reverse = subghz_protocol_common_reverse_key(instance->common.code_found, instance->common.code_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; - -// //if (instance->common.callback) instance->common.callback((SubGhzProtocolCommon*)instance, instance->common.context); - -// } - void subghz_protocol_nero_radio_parse( SubGhzProtocolNeroRadio* instance, bool level, @@ -112,8 +95,6 @@ void subghz_protocol_nero_radio_parse( instance->common.parser_step = NeroRadioDecoderStepCheckPreambula; instance->common.te_last = duration; instance->common.header_count = 0; - } else { - instance->common.parser_step = NeroRadioDecoderStepReset; } break; case NeroRadioDecoderStepCheckPreambula: @@ -228,63 +209,18 @@ void subghz_protocol_nero_radio_to_str(SubGhzProtocolNeroRadio* instance, string code_found_reverse_lo); } -void subghz_protocol_nero_radio_to_save_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; - - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX%08lX\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo); +bool subghz_protocol_nero_radio_to_save_file( + SubGhzProtocolNeroRadio* instance, + FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_nero_radio_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolNeroRadio* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - // strlen("Key: ") = 5 - string_right(temp_str, 5); - - uint8_t buf_key[8] = {0}; - if(!subghz_protocol_common_read_hex(temp_str, buf_key, 8)) { - break; - } - - for(uint8_t i = 0; i < 8; i++) { - instance->common.code_last_found = instance->common.code_last_found << 8 | buf_key[i]; - } - - loaded = true; - } while(0); - - string_clear(temp_str); - - return loaded; + FlipperFile* flipper_file, + SubGhzProtocolNeroRadio* instance, + const char* file_path) { + return subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); } void subghz_decoder_nero_radio_to_load_protocol(SubGhzProtocolNeroRadio* instance, void* context) { diff --git a/lib/subghz/protocols/subghz_protocol_nero_radio.h b/lib/subghz/protocols/subghz_protocol_nero_radio.h index 11f13201..01d85836 100644 --- a/lib/subghz/protocols/subghz_protocol_nero_radio.h +++ b/lib/subghz/protocols/subghz_protocol_nero_radio.h @@ -22,7 +22,9 @@ void subghz_protocol_nero_radio_free(SubGhzProtocolNeroRadio* instance); * @param encoder - SubGhzProtocolCommonEncoder encoder * @return bool */ -bool subghz_protocol_nero_radio_send_key(SubGhzProtocolNeroRadio* instance, SubGhzProtocolCommonEncoder* encoder); +bool subghz_protocol_nero_radio_send_key( + SubGhzProtocolNeroRadio* instance, + SubGhzProtocolCommonEncoder* encoder); /** Reset internal state * @param instance - SubGhzProtocolNeroRadio instance @@ -40,7 +42,10 @@ void subghz_protocol_nero_radio_check_remote_controller(SubGhzProtocolNeroRadio* * @param instance - SubGhzProtocolNeroRadio instance * @param data - LevelDuration level_duration */ -void subghz_protocol_nero_radio_parse(SubGhzProtocolNeroRadio* instance, bool level, uint32_t duration); +void subghz_protocol_nero_radio_parse( + SubGhzProtocolNeroRadio* instance, + bool level, + uint32_t duration); /** Outputting information from the parser * @@ -49,20 +54,27 @@ void subghz_protocol_nero_radio_parse(SubGhzProtocolNeroRadio* instance, bool le */ void subghz_protocol_nero_radio_to_str(SubGhzProtocolNeroRadio* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolNeroRadio instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_nero_radio_to_save_str(SubGhzProtocolNeroRadio* instance, string_t output); +bool subghz_protocol_nero_radio_to_save_file( + SubGhzProtocolNeroRadio* instance, + FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolNeroRadio instance + * @param file_path - file path * @return bool */ -bool subghz_protocol_nero_radio_to_load_protocol_from_file(FileWorker* file_worker, SubGhzProtocolNeroRadio* instance); +bool subghz_protocol_nero_radio_to_load_protocol_from_file( + FlipperFile* flipper_file, + SubGhzProtocolNeroRadio* instance, + const char* file_path); /** Loading protocol from bin data * diff --git a/lib/subghz/protocols/subghz_protocol_nero_sketch.c b/lib/subghz/protocols/subghz_protocol_nero_sketch.c index 24a7efc0..204ddd18 100644 --- a/lib/subghz/protocols/subghz_protocol_nero_sketch.c +++ b/lib/subghz/protocols/subghz_protocol_nero_sketch.c @@ -21,8 +21,8 @@ SubGhzProtocolNeroSketch* subghz_protocol_nero_sketch_alloc(void) { 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_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_nero_sketch_to_save_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 = @@ -85,23 +85,6 @@ void subghz_protocol_nero_sketch_reset(SubGhzProtocolNeroSketch* instance) { instance->common.parser_step = NeroSketchDecoderStepReset; } -/** Analysis of received data - * - * @param instance SubGhzProtocolNeroSketch instance - */ -// void subghz_protocol_nero_sketch_check_remote_controller(SubGhzProtocolNeroSketch* instance) { -// //пока не понятно с серийником, но код статический -// // uint64_t code_found_reverse = subghz_protocol_common_reverse_key(instance->common.code_found, instance->common.code_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; - -// //if (instance->common.callback) instance->common.callback((SubGhzProtocolCommon*)instance, instance->common.context); - -// } - void subghz_protocol_nero_sketch_parse( SubGhzProtocolNeroSketch* instance, bool level, @@ -113,8 +96,6 @@ void subghz_protocol_nero_sketch_parse( instance->common.parser_step = NeroSketchDecoderStepCheckPreambula; instance->common.te_last = duration; instance->common.header_count = 0; - } else { - instance->common.parser_step = NeroSketchDecoderStepReset; } break; case NeroSketchDecoderStepCheckPreambula: @@ -221,59 +202,18 @@ void subghz_protocol_nero_sketch_to_str(SubGhzProtocolNeroSketch* instance, stri code_found_reverse_lo); } -void subghz_protocol_nero_sketch_to_save_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; - - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX%08lX\n", - instance->common.name, - instance->common.code_last_count_bit, - code_found_hi, - code_found_lo); +bool subghz_protocol_nero_sketch_to_save_file( + SubGhzProtocolNeroSketch* instance, + FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_nero_sketch_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolNeroSketch* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - uint32_t temp_key_hi = 0; - uint32_t temp_key_lo = 0; - res = sscanf(string_get_cstr(temp_str), "Key: %08lX%08lX\n", &temp_key_hi, &temp_key_lo); - if(res != 2) { - break; - } - instance->common.code_last_found = (uint64_t)temp_key_hi << 32 | temp_key_lo; - - loaded = true; - } while(0); - - string_clear(temp_str); - - return loaded; + FlipperFile* flipper_file, + SubGhzProtocolNeroSketch* instance, + const char* file_path) { + return subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); } void subghz_decoder_nero_sketch_to_load_protocol(SubGhzProtocolNeroSketch* instance, void* context) { @@ -282,4 +222,4 @@ void subghz_decoder_nero_sketch_to_load_protocol(SubGhzProtocolNeroSketch* insta SubGhzProtocolCommonLoad* data = context; instance->common.code_last_found = data->code_found; instance->common.code_last_count_bit = data->code_count_bit; -} \ No newline at end of file +} diff --git a/lib/subghz/protocols/subghz_protocol_nero_sketch.h b/lib/subghz/protocols/subghz_protocol_nero_sketch.h index 0fe781c8..33786c42 100644 --- a/lib/subghz/protocols/subghz_protocol_nero_sketch.h +++ b/lib/subghz/protocols/subghz_protocol_nero_sketch.h @@ -22,7 +22,9 @@ void subghz_protocol_nero_sketch_free(SubGhzProtocolNeroSketch* instance); * @param encoder - SubGhzProtocolCommonEncoder encoder * @return bool */ -bool subghz_protocol_nero_sketch_send_key(SubGhzProtocolNeroSketch* instance, SubGhzProtocolCommonEncoder* encoder); +bool subghz_protocol_nero_sketch_send_key( + SubGhzProtocolNeroSketch* instance, + SubGhzProtocolCommonEncoder* encoder); /** Reset internal state * @param instance - SubGhzProtocolNeroSketch instance @@ -40,7 +42,10 @@ void subghz_protocol_nero_sketch_check_remote_controller(SubGhzProtocolNeroSketc * @param instance - SubGhzProtocolNeroSketch instance * @param data - LevelDuration level_duration */ -void subghz_protocol_nero_sketch_parse(SubGhzProtocolNeroSketch* instance, bool level, uint32_t duration); +void subghz_protocol_nero_sketch_parse( + SubGhzProtocolNeroSketch* instance, + bool level, + uint32_t duration); /** Outputting information from the parser * @@ -49,24 +54,31 @@ void subghz_protocol_nero_sketch_parse(SubGhzProtocolNeroSketch* instance, bool */ void subghz_protocol_nero_sketch_to_str(SubGhzProtocolNeroSketch* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolNeroSketch instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_nero_sketch_to_save_str(SubGhzProtocolNeroSketch* instance, string_t output); +bool subghz_protocol_nero_sketch_to_save_file( + SubGhzProtocolNeroSketch* instance, + FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolNeroSketch instance + * @param file_path - file path * @return bool */ -bool subghz_protocol_nero_sketch_to_load_protocol_from_file(FileWorker* file_worker, SubGhzProtocolNeroSketch* instance); +bool subghz_protocol_nero_sketch_to_load_protocol_from_file( + FlipperFile* flipper_file, + 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); \ No newline at end of file +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 index 25991380..56ac43c5 100644 --- a/lib/subghz/protocols/subghz_protocol_nice_flo.c +++ b/lib/subghz/protocols/subghz_protocol_nice_flo.c @@ -27,8 +27,8 @@ SubGhzProtocolNiceFlo* subghz_protocol_nice_flo_alloc() { 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_string = - (SubGhzProtocolCommonGetStrSave)subghz_protocol_nice_flo_to_save_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 = @@ -86,8 +86,6 @@ void subghz_protocol_nice_flo_parse(SubGhzProtocolNiceFlo* instance, bool level, instance->common.te_delta * 36)) { //Found header Nice Flo instance->common.parser_step = NiceFloDecoderStepFoundStartBit; - } else { - instance->common.parser_step = NiceFloDecoderStepReset; } break; case NiceFloDecoderStepFoundStartBit: @@ -166,54 +164,18 @@ void subghz_protocol_nice_flo_to_str(SubGhzProtocolNiceFlo* instance, string_t o code_found_reverse_lo); } -void subghz_protocol_nice_flo_to_save_str(SubGhzProtocolNiceFlo* instance, string_t output) { - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Key: %08lX\n", - instance->common.name, - instance->common.code_last_count_bit, - (uint32_t)(instance->common.code_last_found & 0x00000000ffffffff)); +bool subghz_protocol_nice_flo_to_save_file( + SubGhzProtocolNiceFlo* instance, + FlipperFile* flipper_file) { + return subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); } bool subghz_protocol_nice_flo_to_load_protocol_from_file( - FileWorker* file_worker, - SubGhzProtocolNiceFlo* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse key data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - uint32_t temp_key = 0; - res = sscanf(string_get_cstr(temp_str), "Key: %08lX\n", &temp_key); - if(res != 1) { - break; - } - instance->common.code_last_found = (uint64_t)temp_key; - - loaded = true; - } while(0); - - string_clear(temp_str); - - return loaded; + FlipperFile* flipper_file, + SubGhzProtocolNiceFlo* instance, + const char* file_path) { + return subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); } void subghz_decoder_nice_flo_to_load_protocol(SubGhzProtocolNiceFlo* instance, void* context) { @@ -224,4 +186,4 @@ void subghz_decoder_nice_flo_to_load_protocol(SubGhzProtocolNiceFlo* instance, v instance->common.code_last_count_bit = data->code_count_bit; instance->common.serial = 0x0; instance->common.btn = 0x0; -} \ No newline at end of file +} diff --git a/lib/subghz/protocols/subghz_protocol_nice_flo.h b/lib/subghz/protocols/subghz_protocol_nice_flo.h index c5ed8759..a22c416b 100644 --- a/lib/subghz/protocols/subghz_protocol_nice_flo.h +++ b/lib/subghz/protocols/subghz_protocol_nice_flo.h @@ -22,7 +22,9 @@ void subghz_protocol_nice_flo_free(SubGhzProtocolNiceFlo* instance); * @param encoder - SubGhzProtocolCommonEncoder encoder * @return bool */ -bool subghz_protocol_nice_flo_send_key(SubGhzProtocolNiceFlo* instance, SubGhzProtocolCommonEncoder* encoder); +bool subghz_protocol_nice_flo_send_key( + SubGhzProtocolNiceFlo* instance, + SubGhzProtocolCommonEncoder* encoder); /** Reset internal state * @param instance - SubGhzProtocolNiceFlo instance @@ -43,20 +45,27 @@ void subghz_protocol_nice_flo_parse(SubGhzProtocolNiceFlo* instance, bool level, */ void subghz_protocol_nice_flo_to_str(SubGhzProtocolNiceFlo* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzProtocolNiceFlo instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_protocol_nice_flo_to_save_str(SubGhzProtocolNiceFlo* instance, string_t output); +bool subghz_protocol_nice_flo_to_save_file( + SubGhzProtocolNiceFlo* instance, + FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzProtocolNiceFlo instance + * @param file_path - file path * @return bool */ -bool subghz_protocol_nice_flo_to_load_protocol_from_file(FileWorker* file_worker, SubGhzProtocolNiceFlo* instance); +bool subghz_protocol_nice_flo_to_load_protocol_from_file( + FlipperFile* flipper_file, + SubGhzProtocolNiceFlo* instance, + const char* file_path); /** Loading protocol from bin data * diff --git a/lib/subghz/protocols/subghz_protocol_nice_flor_s.c b/lib/subghz/protocols/subghz_protocol_nice_flor_s.c index 32d528bc..b1b8e4ac 100644 --- a/lib/subghz/protocols/subghz_protocol_nice_flor_s.c +++ b/lib/subghz/protocols/subghz_protocol_nice_flor_s.c @@ -2,12 +2,15 @@ #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; @@ -44,7 +47,7 @@ void subghz_protocol_nice_flor_s_free(SubGhzProtocolNiceFlorS* instance) { void subghz_protocol_nice_flor_s_name_file(SubGhzProtocolNiceFlorS* instance, const char* name) { instance->rainbow_table_file_name = name; - printf("Loading Nice FloR S rainbow table %s\r\n", name); + FURI_LOG_I(TAG, "Loading rainbow table from %s", name); } /** Send bit @@ -103,17 +106,13 @@ void subghz_protocol_nice_flor_s_send_key( 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 = 0; - FileWorker* file_worker = file_worker_alloc(true); - if(file_worker_open( - file_worker, instance->rainbow_table_file_name, FSAM_READ, FSOM_OPEN_EXISTING)) { - file_worker_seek(file_worker, address, true); - file_worker_read(file_worker, &buffer, 1); + 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; } - file_worker_close(file_worker); - file_worker_free(file_worker); - - return buffer; } /** Decrypt protocol Nice Flor S @@ -169,8 +168,6 @@ void subghz_protocol_nice_flor_s_parse( instance->common.te_delta * 38)) { //Found start header Nice Flor-S instance->common.parser_step = NiceFlorSDecoderStepCheckHeader; - } else { - instance->common.parser_step = NiceFlorSDecoderStepReset; } break; case NiceFlorSDecoderStepCheckHeader: diff --git a/lib/subghz/protocols/subghz_protocol_princeton.c b/lib/subghz/protocols/subghz_protocol_princeton.c index 822a5917..0f2fdf77 100644 --- a/lib/subghz/protocols/subghz_protocol_princeton.c +++ b/lib/subghz/protocols/subghz_protocol_princeton.c @@ -11,6 +11,8 @@ #define SUBGHZ_PT_COUNT_KEY 5 #define SUBGHZ_PT_TIMEOUT 320 +#define TAG "SubghzPrinceton" + struct SubGhzEncoderPrinceton { uint32_t key; uint16_t te; @@ -67,7 +69,7 @@ void subghz_encoder_princeton_print_log(void* context) { float duty_cycle = ((float)instance->time_high / (instance->time_high + instance->time_low)) * 100; FURI_LOG_I( - "EncoderPrinceton", + TAG "Encoder", "Radio ON=%dus, OFF=%dus, DutyCycle=%d,%d%%", instance->time_high, instance->time_low, @@ -141,8 +143,8 @@ SubGhzDecoderPrinceton* subghz_decoder_princeton_alloc(void) { 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_string = - (SubGhzProtocolCommonGetStrSave)subghz_decoder_princeton_to_save_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 = @@ -210,8 +212,6 @@ void subghz_decoder_princeton_parse( instance->common.code_found = 0; instance->common.code_count_bit = 0; instance->te = 0; - } else { - instance->common.parser_step = PrincetonDecoderStepReset; } break; case PrincetonDecoderStepSaveDuration: @@ -293,67 +293,27 @@ void subghz_decoder_princeton_to_str(SubGhzDecoderPrinceton* instance, string_t instance->te); } -void subghz_decoder_princeton_to_save_str(SubGhzDecoderPrinceton* instance, string_t output) { - string_printf( - output, - "Protocol: %s\n" - "Bit: %d\n" - "Te: %d\n" - "Key: %08lX\n", - instance->common.name, - instance->common.code_last_count_bit, - instance->te, - (uint32_t)(instance->common.code_last_found & 0x00000000ffffffff)); +bool subghz_decoder_princeton_to_save_file( + SubGhzDecoderPrinceton* instance, + FlipperFile* flipper_file) { + bool res = subghz_protocol_common_to_save_file((SubGhzProtocolCommon*)instance, flipper_file); + if(res) { + res = flipper_file_write_uint32(flipper_file, "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( - FileWorker* file_worker, - SubGhzDecoderPrinceton* instance) { - bool loaded = false; - string_t temp_str; - string_init(temp_str); - int res = 0; - int data = 0; - - do { - // Read and parse bit data from 2nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Bit: %d\n", &data); - if(res != 1) { - break; - } - instance->common.code_last_count_bit = (uint8_t)data; - - // Read and parse te data from 3nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - res = sscanf(string_get_cstr(temp_str), "Te: %d\n", &data); - if(res != 1) { - break; - } - instance->te = (uint16_t)data; - - // Read and parse key data from 4nd line - if(!file_worker_read_until(file_worker, temp_str, '\n')) { - break; - } - uint32_t temp_key = 0; - res = sscanf(string_get_cstr(temp_str), "Key: %08lX\n", &temp_key); - if(res != 1) { - break; - } - instance->common.code_last_found = (uint64_t)temp_key; - instance->common.serial = instance->common.code_last_found >> 4; - instance->common.btn = (uint8_t)instance->common.code_last_found & 0x00000F; - - loaded = true; - } while(0); - - string_clear(temp_str); - + FlipperFile* flipper_file, + SubGhzDecoderPrinceton* instance, + const char* file_path) { + bool loaded = subghz_protocol_common_to_load_protocol_from_file( + (SubGhzProtocolCommon*)instance, flipper_file); + if(loaded) { + loaded = flipper_file_read_uint32(flipper_file, "TE", (uint32_t*)&instance->te, 1); + if(!loaded) FURI_LOG_E(SUBGHZ_PARSER_TAG, "Missing TE"); + } return loaded; } diff --git a/lib/subghz/protocols/subghz_protocol_princeton.h b/lib/subghz/protocols/subghz_protocol_princeton.h index 9ccb7cd3..bc9bf54e 100644 --- a/lib/subghz/protocols/subghz_protocol_princeton.h +++ b/lib/subghz/protocols/subghz_protocol_princeton.h @@ -100,26 +100,31 @@ void subghz_decoder_princeton_parse( */ void subghz_decoder_princeton_to_str(SubGhzDecoderPrinceton* instance, string_t output); -/** Get a string to save the protocol +/** Adding data to a file * * @param instance - SubGhzDecoderPrinceton instance - * @param output - the resulting string + * @param flipper_file - FlipperFile + * @return bool */ -void subghz_decoder_princeton_to_save_str(SubGhzDecoderPrinceton* instance, string_t output); +bool subghz_decoder_princeton_to_save_file( + SubGhzDecoderPrinceton* instance, + FlipperFile* flipper_file); /** Loading protocol from file * - * @param file_worker - FileWorker file_worker + * @param flipper_file - FlipperFile * @param instance - SubGhzDecoderPrinceton instance + * @param file_path - file path * @return bool */ -bool subghz_decoder_princeton_to_load_protocol_from_file(FileWorker* file_worker, SubGhzDecoderPrinceton* instance); +bool subghz_decoder_princeton_to_load_protocol_from_file( + FlipperFile* flipper_file, + 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) ; +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 new file mode 100644 index 00000000..b9b9c5bc --- /dev/null +++ b/lib/subghz/protocols/subghz_protocol_raw.c @@ -0,0 +1,248 @@ +#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; + FlipperFile* flipper_file; + SubGhzFileEncoderWorker* file_worker_encoder; + uint32_t file_is_open; + string_t file_name; + size_t sample_write; + bool last_level; +}; + +typedef enum { + RAWFileIsOpenClose = 0, + RAWFileIsOpenWrite, + RAWFileIsOpenRead, +} RAWFilIsOpen; + +SubGhzProtocolRAW* subghz_protocol_raw_alloc(void) { + SubGhzProtocolRAW* instance = furi_alloc(sizeof(SubGhzProtocolRAW)); + + instance->upload_raw = NULL; + instance->ind_write = 0; + + instance->last_level = false; + + instance->storage = furi_record_open("storage"); + instance->flipper_file = flipper_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_file_free(instance->flipper_file); + furi_record_close("storage"); + + free(instance); +} + +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; + subghz_protocol_encoder_common_set_callback( + encoder, subghz_file_encoder_worker_get_level_duration, instance->file_worker_encoder); + subghz_protocol_encoder_common_set_callback_end( + encoder, subghz_protocol_raw_file_encoder_worker_stop, 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_file = flipper_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; + } + // Create saved directory if necessary + if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_PATH_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_APP_PATH_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); + + if(!storage_simply_remove(instance->storage, string_get_cstr(dev_file_name))) { + break; + } + + // Open file + if(!flipper_file_open_always(instance->flipper_file, string_get_cstr(dev_file_name))) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", dev_file_name); + break; + } + + if(!flipper_file_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_file_write_uint32(instance->flipper_file, "Frequency", &frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + if(!flipper_file_write_string_cstr(instance->flipper_file, "Preset", preset)) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + + if(!flipper_file_write_string_cstr(instance->flipper_file, "Protocol", instance->common.name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + instance->upload_raw = furi_alloc(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_file_close(instance->flipper_file); + 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_file_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; +} + +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( + FlipperFile* flipper_file, + SubGhzProtocolRAW* instance, + const char* file_path) { + furi_assert(file_path); + subghz_protocol_raw_set_last_file_name(instance, file_path); + return true; +} \ No newline at end of file diff --git a/lib/subghz/protocols/subghz_protocol_raw.h b/lib/subghz/protocols/subghz_protocol_raw.h new file mode 100644 index 00000000..59202b47 --- /dev/null +++ b/lib/subghz/protocols/subghz_protocol_raw.h @@ -0,0 +1,64 @@ +#pragma once + +#include "subghz_protocol_common.h" + +typedef struct SubGhzProtocolRAW SubGhzProtocolRAW; + +/** Allocate SubGhzProtocolRAW + * + * @return SubGhzProtocolRAW* + */ +SubGhzProtocolRAW* subghz_protocol_raw_alloc(); + +/** Free SubGhzProtocolRAW + * + * @param instance + */ +void subghz_protocol_raw_free(SubGhzProtocolRAW* instance); + +/** 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( + FlipperFile* flipper_file, + 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 index b5124f93..5f9786a0 100644 --- a/lib/subghz/protocols/subghz_protocol_scher_khan.c +++ b/lib/subghz/protocols/subghz_protocol_scher_khan.c @@ -130,8 +130,6 @@ void subghz_protocol_scher_khan_parse( instance->common.parser_step = ScherKhanDecoderStepCheckPreambula; instance->common.te_last = duration; instance->common.header_count = 0; - } else { - instance->common.parser_step = ScherKhanDecoderStepReset; } break; case ScherKhanDecoderStepCheckPreambula: diff --git a/lib/subghz/protocols/subghz_protocol_star_line.c b/lib/subghz/protocols/subghz_protocol_star_line.c index e1752b65..089a4540 100644 --- a/lib/subghz/protocols/subghz_protocol_star_line.c +++ b/lib/subghz/protocols/subghz_protocol_star_line.c @@ -238,7 +238,6 @@ void subghz_protocol_star_line_parse( instance->common.parser_step = StarLineDecoderStepCheckDuration; } } else { - instance->common.parser_step = StarLineDecoderStepReset; instance->common.header_count = 0; } break; diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c new file mode 100644 index 00000000..7bde91fa --- /dev/null +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -0,0 +1,221 @@ +#include "subghz_file_encoder_worker.h" +#include + +#include +#include + +#define TAG "SubGhzFileEncoderWorker" + +#define SUBGHZ_FILE_ENCODER_LOAD 512 + +struct SubGhzFileEncoderWorker { + FuriThread* thread; + StreamBufferHandle_t stream; + + Storage* storage; + FlipperFile* flipper_file; + + volatile bool worker_running; + bool level; + int32_t duration; + string_t str_data; + string_t file_path; +}; + +void subghz_file_encoder_worker_add_livel_duration( + SubGhzFileEncoderWorker* instance, + int32_t duration) { + bool res = true; + if(duration < 0 && !instance->level) { + instance->duration += duration; + res = false; + } else if(duration > 0 && instance->level) { + instance->duration += duration; + res = false; + } else if(duration == 0) { + instance->duration = 0; + } + + if(res) { + instance->level = !instance->level; + instance->duration += duration; + xStreamBufferSend(instance->stream, &instance->duration, sizeof(int32_t), 10); + instance->duration = 0; + } +} + +bool subghz_file_encoder_worker_data_parse( + SubGhzFileEncoderWorker* instance, + const char* strStart, + size_t len) { + char* str1; + size_t ind_start = (size_t)strStart; //store the start address of the beginning of the line + bool res = false; + + str1 = strstr( + strStart, "RAW_Data: "); //looking for the beginning of the desired title in the line + if(str1 != NULL) { + str1 = strchr( + str1, + ' '); //if found, shift the pointer by 1 element per line "RAW_Data: -1, 2, -2..." + while( + strchr(str1, ' ') != NULL && + ((size_t)str1 < + (len + + ind_start))) { //check that there is still an element in the line and that it has not gone beyond the line + str1 = strchr(str1, ' '); + str1 += 1; //if found, shift the pointer by next element per line + subghz_file_encoder_worker_add_livel_duration(instance, atoi(str1)); + } + res = true; + } + return res; +} + +LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { + furi_assert(context); + SubGhzFileEncoderWorker* instance = context; + int32_t duration; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + int ret = xStreamBufferReceiveFromISR( + instance->stream, &duration, sizeof(int32_t), &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + if(ret == sizeof(int32_t)) { + LevelDuration level_duration = {.level = LEVEL_DURATION_RESET}; + if(duration < 0) { + level_duration = level_duration_make(false, duration * -1); + } else if(duration > 0) { + level_duration = level_duration_make(true, duration); + } else if(duration == 0) { + level_duration = level_duration_reset(); + FURI_LOG_I(TAG, "Stop transmission"); + } + return level_duration; + } else { + FURI_LOG_E(TAG, "Slow flash read"); + return level_duration_wait(); + } +} + +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t subghz_file_encoder_worker_thread(void* context) { + SubGhzFileEncoderWorker* instance = context; + FURI_LOG_I(TAG, "Worker start"); + bool res = false; + File* file = flipper_file_get_file(instance->flipper_file); + do { + if(!flipper_file_open_existing( + instance->flipper_file, string_get_cstr(instance->file_path))) { + FURI_LOG_E( + TAG, + "Unable to open file for read: %s", + string_get_cstr(instance->file_path)); + break; + } + if(!flipper_file_read_string(instance->flipper_file, "Protocol", instance->str_data)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + //skip the end of the previous line "\n" + storage_file_seek(file, 1, false); + res = true; + FURI_LOG_I(TAG, "Start transmission"); + } while(0); + + while(res && instance->worker_running) { + size_t stream_free_byte = xStreamBufferSpacesAvailable(instance->stream); + if((stream_free_byte / sizeof(int32_t)) >= SUBGHZ_FILE_ENCODER_LOAD) { + if(file_helper_read_line(file, instance->str_data)) { + //skip the end of the previous line "\n" + storage_file_seek(file, 1, false); + if(!subghz_file_encoder_worker_data_parse( + instance, + string_get_cstr(instance->str_data), + strlen(string_get_cstr(instance->str_data)))) { + //to stop DMA correctly + subghz_file_encoder_worker_add_livel_duration(instance, LEVEL_DURATION_RESET); + subghz_file_encoder_worker_add_livel_duration(instance, LEVEL_DURATION_RESET); + + break; + } + } else { + subghz_file_encoder_worker_add_livel_duration(instance, LEVEL_DURATION_RESET); + subghz_file_encoder_worker_add_livel_duration(instance, LEVEL_DURATION_RESET); + break; + } + } + } + //waiting for the end of the transfer + FURI_LOG_I(TAG, "End read file"); + while(instance->worker_running) { + osDelay(50); + } + flipper_file_close(instance->flipper_file); + + FURI_LOG_I(TAG, "Worker stop"); + return 0; +} + +SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { + SubGhzFileEncoderWorker* instance = furi_alloc(sizeof(SubGhzFileEncoderWorker)); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "SubghzFEWorker"); + furi_thread_set_stack_size(instance->thread, 2048); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, subghz_file_encoder_worker_thread); + instance->stream = xStreamBufferCreate(sizeof(int32_t) * 2048, sizeof(int32_t)); + + instance->storage = furi_record_open("storage"); + instance->flipper_file = flipper_file_alloc(instance->storage); + + string_init(instance->str_data); + string_init(instance->file_path); + instance->level = false; + + return instance; +} + +void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { + furi_assert(instance); + + vStreamBufferDelete(instance->stream); + furi_thread_free(instance->thread); + + string_clear(instance->str_data); + string_clear(instance->file_path); + + flipper_file_free(instance->flipper_file); + furi_record_close("storage"); + + free(instance); +} + +bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(!instance->worker_running); + + xStreamBufferReset(instance->stream); + string_set(instance->file_path, file_path); + instance->worker_running = true; + bool res = furi_thread_start(instance->thread); + return res; +} + +void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + furi_thread_join(instance->thread); +} + +bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance) { + furi_assert(instance); + return instance->worker_running; +} diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h new file mode 100644 index 00000000..0c014a0b --- /dev/null +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +typedef struct SubGhzFileEncoderWorker SubGhzFileEncoderWorker; + +/** Allocate SubGhzFileEncoderWorker + * + * @return SubGhzFileEncoderWorker* + */ +SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc(); + +/** Free SubGhzFileEncoderWorker + * + * @param instance SubGhzFileEncoderWorker instance + */ +void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance); + +LevelDuration subghz_file_encoder_worker_get_level_duration(void* context); + +/** Start SubGhzFileEncoderWorker + * + * @param instance 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 + */ +void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance); + +/** Check if worker is running + * + * @param instance SubGhzFileEncoderWorker instance + * @return bool - true if running + */ +bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance); diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index dea11e0b..174b39d1 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -1,10 +1,29 @@ #include "subghz_keystore.h" #include +#include + #include +#include +#include + +#define TAG "SubGhzKeystore" #define FILE_BUFFER_SIZE 64 +#define SUBGHZ_KEYSTORE_FILE_TYPE "Flipper SubGhz Keystore File" +#define SUBGHZ_KEYSTORE_FILE_RAW_TYPE "Flipper SubGhz Keystore RAW File" +#define SUBGHZ_KEYSTORE_FILE_VERSION 0 + +#define SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT 1 +#define SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE 512 +#define SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE (SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE * 2) + +typedef enum { + SubGhzKeystoreEncryptionNone, + SubGhzKeystoreEncryptionAES256, +} SubGhzKeystoreEncryption; + struct SubGhzKeystore { SubGhzKeyArray_t data; }; @@ -21,65 +40,559 @@ void subghz_keystore_free(SubGhzKeystore* instance) { furi_assert(instance); for - M_EACH(manufacture_code, instance->data, SubGhzKeyArray_t) { - string_clear(manufacture_code->name); - manufacture_code->key = 0; - } + M_EACH(manufacture_code, instance->data, SubGhzKeyArray_t) { + string_clear(manufacture_code->name); + manufacture_code->key = 0; + } SubGhzKeyArray_clear(instance->data); free(instance); } -static void subghz_keystore_add_key(SubGhzKeystore* instance, const char* name, uint64_t key, uint16_t type) { +static void subghz_keystore_add_key( + SubGhzKeystore* instance, + const char* name, + uint64_t key, + uint16_t type) { SubGhzKey* manufacture_code = SubGhzKeyArray_push_raw(instance->data); string_init_set_str(manufacture_code->name, name); manufacture_code->key = key; manufacture_code->type = type; } -static void subghz_keystore_process_line(SubGhzKeystore* instance, string_t line) { +static bool subghz_keystore_process_line(SubGhzKeystore* instance, char* line) { uint64_t key = 0; uint16_t type = 0; char skey[17] = {0}; char name[65] = {0}; - int ret = sscanf(string_get_cstr(line), "%16s:%hu:%64s", skey, &type, name); + int ret = sscanf(line, "%16s:%hu:%64s", skey, &type, name); key = strtoull(skey, NULL, 16); - if (ret == 3) { + if(ret == 3) { subghz_keystore_add_key(instance, name, key, type); + return true; } else { - printf("Failed to load line: %s\r\n", string_get_cstr(line)); + FURI_LOG_E(TAG, "Failed to load line: %s\r\n", line); + return false; } } -void subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { - File* manufacture_keys_file = storage_file_alloc(furi_record_open("storage")); - string_t line; - string_init(line); - if(storage_file_open(manufacture_keys_file, file_name, FSAM_READ, FSOM_OPEN_EXISTING)) { - printf("Loading manufacture keys file %s\r\n", file_name); - char buffer[FILE_BUFFER_SIZE]; - uint16_t ret; - do { - ret = storage_file_read(manufacture_keys_file, buffer, FILE_BUFFER_SIZE); - for (uint16_t i=0; i < ret; i++) { - if (buffer[i] == '\n' && string_size(line) > 0) { - subghz_keystore_process_line(instance, line); - string_clean(line); +static void subghz_keystore_mess_with_iv(uint8_t* iv) { + // Please do not share decrypted manufacture keys + // Sharing them will bring some discomfort to legal owners + // And potential legal action against you + // While you reading this code think about your own personal responsibility + asm volatile("movs r0, #0x0 \n" + "movs r1, #0x0 \n" + "movs r2, #0x0 \n" + "movs r3, #0x0 \n" + "nani: \n" + "ldrb r1, [r0, %0]\n" + "mov r2, r1 \n" + "add r1, r3 \n" + "mov r3, r2 \n" + "strb r1, [r0, %0]\n" + "adds r0, #0x1 \n" + "cmp r0, #0xF \n" + "bls nani \n" + : + : "r"(iv) + : "r0", "r1", "r2", "r3", "memory"); +} + +static bool subghz_keystore_read_file(SubGhzKeystore* instance, File* file, uint8_t* iv) { + bool result = true; + char buffer[FILE_BUFFER_SIZE]; + + char* decrypted_line = furi_alloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + char* encrypted_line = furi_alloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + size_t encrypted_line_cursor = 0; + + if(iv) furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv); + + size_t ret = 0; + do { + ret = storage_file_read(file, buffer, FILE_BUFFER_SIZE); + for(uint16_t i = 0; i < ret; i++) { + if(buffer[i] == '\n' && encrypted_line_cursor > 0) { + // Process line + if(iv) { + // Data alignment check, 32 instead of 16 because of hex encoding + size_t len = strlen(encrypted_line); + if(len % 32 == 0) { + // Inplace hex to bin conversion + for(size_t i = 0; i < len; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(encrypted_line[i], &hi_nibble); + hex_char_to_hex_nibble(encrypted_line[i + 1], &lo_nibble); + encrypted_line[i / 2] = (hi_nibble << 4) | lo_nibble; + } + len /= 2; + + if(furi_hal_crypto_decrypt( + (uint8_t*)encrypted_line, (uint8_t*)decrypted_line, len)) { + subghz_keystore_process_line(instance, decrypted_line); + } else { + FURI_LOG_E(TAG, "Decryption failed"); + result = false; + break; + } + } else { + FURI_LOG_E( + TAG, "Invalid encrypted data: %s", encrypted_line); + } } else { - string_push_back(line, buffer[i]); + subghz_keystore_process_line(instance, encrypted_line); + } + // reset line buffer + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + encrypted_line_cursor = 0; + } else if(buffer[i] == '\r' || buffer[i] == '\n') { + // do not add line endings to the buffer + } else { + if(encrypted_line_cursor < SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE) { + encrypted_line[encrypted_line_cursor] = buffer[i]; + encrypted_line_cursor++; + } else { + FURI_LOG_E(TAG, "Malformed file"); + result = false; + break; } } - } while(ret > 0); - } else { - printf("Manufacture keys file is not found: %s\r\n", file_name); - } - string_clear(line); - storage_file_close(manufacture_keys_file); - storage_file_free(manufacture_keys_file); + } + } while(ret > 0 && result); + + if(iv) furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + + free(encrypted_line); + free(decrypted_line); + + return result; +} + +bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { + furi_assert(instance); + bool result = false; + uint8_t iv[16]; + uint32_t version; + SubGhzKeystoreEncryption encryption; + + string_t filetype; + string_init(filetype); + + Storage* storage = furi_record_open("storage"); + + FlipperFile* flipper_file = flipper_file_alloc(storage); + do { + if(!flipper_file_open_existing(flipper_file, file_name)) { + FURI_LOG_E(TAG, "Unable to open file for read: %s", file_name); + break; + } + if(!flipper_file_read_header(flipper_file, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(!flipper_file_read_uint32(flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { + FURI_LOG_E(TAG, "Missing encryption type"); + break; + } + + if(strcmp(string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_TYPE) != 0 || + version != SUBGHZ_KEYSTORE_FILE_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + File* file = flipper_file_get_file(flipper_file); + if(encryption == SubGhzKeystoreEncryptionNone) { + result = subghz_keystore_read_file(instance, file, NULL); + } else if(encryption == SubGhzKeystoreEncryptionAES256) { + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + subghz_keystore_mess_with_iv(iv); + result = subghz_keystore_read_file(instance, file, iv); + } else { + FURI_LOG_E(TAG, "Unknown encryption"); + break; + } + } while(0); + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + furi_record_close("storage"); + + string_clear(filetype); + + return result; +} + +bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8_t* iv) { + furi_assert(instance); + bool result = false; + + Storage* storage = furi_record_open("storage"); + char* decrypted_line = furi_alloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + char* encrypted_line = furi_alloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + + FlipperFile* flipper_file = flipper_file_alloc(storage); + do { + if(!flipper_file_open_always(flipper_file, file_name)) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", file_name); + break; + } + if(!flipper_file_write_header_cstr( + flipper_file, SUBGHZ_KEYSTORE_FILE_TYPE, SUBGHZ_KEYSTORE_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + uint32_t encryption = SubGhzKeystoreEncryptionAES256; + if(!flipper_file_write_uint32(flipper_file, "Encryption", &encryption, 1)) { + FURI_LOG_E(TAG, "Unable to add Encryption"); + break; + } + if(!flipper_file_write_hex(flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Unable to add IV"); + break; + } + + subghz_keystore_mess_with_iv(iv); + + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + + File* file = flipper_file_get_file(flipper_file); + size_t encrypted_line_count = 0; + for + M_EACH(key, instance->data, SubGhzKeyArray_t) { + // Wipe buffer before packing + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + // Form unecreypted line + int len = snprintf( + decrypted_line, + SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE, + "%08lX%08lX:%hu:%s", + (uint32_t)(key->key >> 32), + (uint32_t)key->key, + key->type, + string_get_cstr(key->name)); + // Verify length and align + furi_assert(len > 0); + if(len % 16 != 0) { + len += (16 - len % 16); + } + furi_assert(len % 16 == 0); + furi_assert(len <= SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + // Form encrypted line + if(!furi_hal_crypto_encrypt( + (uint8_t*)decrypted_line, (uint8_t*)encrypted_line, len)) { + FURI_LOG_E(TAG, "Encryption failed"); + break; + } + // HEX Encode encrypted line + const char xx[] = "0123456789ABCDEF"; + for(size_t i = 0; i < len; i++) { + size_t cursor = len - i - 1; + size_t hex_cursor = len * 2 - i * 2 - 1; + encrypted_line[hex_cursor] = xx[encrypted_line[cursor] & 0xF]; + encrypted_line[hex_cursor - 1] = xx[(encrypted_line[cursor] >> 4) & 0xF]; + } + storage_file_write(file, encrypted_line, strlen(encrypted_line)); + storage_file_write(file, "\n", 1); + encrypted_line_count++; + + FURI_LOG_I( + TAG, "Encrypted: `%s` -> `%s`", decrypted_line, encrypted_line); + } + furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + result = encrypted_line_count == SubGhzKeyArray_size(instance->data); + } while(0); + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + + free(encrypted_line); + free(decrypted_line); + furi_record_close("storage"); + + return result; } SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance) { furi_assert(instance); return &instance->data; } + +bool subghz_keystore_raw_encrypted_save( + const char* input_file_name, + const char* output_file_name, + uint8_t* iv) { + bool encrypted = false; + uint32_t version; + string_t filetype; + string_init(filetype); + SubGhzKeystoreEncryption encryption; + + Storage* storage = furi_record_open("storage"); + + char* encrypted_line = furi_alloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + + FlipperFile* input_flipper_file = flipper_file_alloc(storage); + do { + if(!flipper_file_open_existing(input_flipper_file, input_file_name)) { + FURI_LOG_E(TAG, "Unable to open file for read: %s", input_file_name); + break; + } + if(!flipper_file_read_header(input_flipper_file, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(!flipper_file_read_uint32(input_flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { + FURI_LOG_E(TAG, "Missing encryption type"); + break; + } + + if(strcmp(string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || + version != SUBGHZ_KEYSTORE_FILE_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + if(encryption != SubGhzKeystoreEncryptionNone) { + FURI_LOG_E(TAG, "Already encryption"); + break; + } + File* input_file = flipper_file_get_file(input_flipper_file); + + FlipperFile* output_flipper_file = flipper_file_alloc(storage); + + if(!flipper_file_open_always(output_flipper_file, output_file_name)) { + FURI_LOG_E(TAG, "Unable to open file for write: %s", output_file_name); + break; + } + if(!flipper_file_write_header_cstr( + output_flipper_file, string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + uint32_t encryption = SubGhzKeystoreEncryptionAES256; + if(!flipper_file_write_uint32( + output_flipper_file, "Encryption", &encryption, 1)) { + FURI_LOG_E(TAG, "Unable to add Encryption"); + break; + } + if(!flipper_file_write_hex(output_flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Unable to add IV"); + break; + } + + if(!flipper_file_write_string_cstr(output_flipper_file, "Encrypt_data", "RAW")) { + FURI_LOG_E(TAG, "Unable to add Encrypt_data"); + break; + } + + subghz_keystore_mess_with_iv(iv); + + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + + File* output_file = flipper_file_get_file(output_flipper_file); + char buffer[FILE_BUFFER_SIZE]; + bool result = true; + + size_t ret = 0; + furi_assert(FILE_BUFFER_SIZE % 16 == 0); + + //skip the end of the previous line "\n" + storage_file_read(input_file, buffer, 1); + + do { + memset(buffer, 0, FILE_BUFFER_SIZE); + ret = storage_file_read(input_file, buffer, FILE_BUFFER_SIZE); + if(ret == 0) { + break; + } + + for(uint16_t i = 0; i < FILE_BUFFER_SIZE - 1; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(buffer[i], &hi_nibble); + hex_char_to_hex_nibble(buffer[i + 1], &lo_nibble); + buffer[i / 2] = (hi_nibble << 4) | lo_nibble; + } + + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + // Form encrypted line + if(!furi_hal_crypto_encrypt( + (uint8_t*)buffer, (uint8_t*)encrypted_line, FILE_BUFFER_SIZE / 2)) { + FURI_LOG_E(TAG, "Encryption failed"); + result = false; + break; + } + + // HEX Encode encrypted line + const char xx[] = "0123456789ABCDEF"; + for(size_t i = 0; i < FILE_BUFFER_SIZE / 2; i++) { + size_t cursor = FILE_BUFFER_SIZE / 2 - i - 1; + size_t hex_cursor = FILE_BUFFER_SIZE - i * 2 - 1; + encrypted_line[hex_cursor] = xx[encrypted_line[cursor] & 0xF]; + encrypted_line[hex_cursor - 1] = xx[(encrypted_line[cursor] >> 4) & 0xF]; + } + storage_file_write(output_file, encrypted_line, strlen(encrypted_line)); + + } while(ret > 0 && result); + + flipper_file_close(output_flipper_file); + flipper_file_free(output_flipper_file); + + furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + + if(!result) break; + + encrypted = true; + } while(0); + + flipper_file_close(input_flipper_file); + flipper_file_free(input_flipper_file); + + free(encrypted_line); + + furi_record_close("storage"); + + return encrypted; +} + +bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* data, size_t len) { + bool result = false; + uint8_t iv[16]; + uint32_t version; + SubGhzKeystoreEncryption encryption; + + string_t str_temp; + string_init(str_temp); + + Storage* storage = furi_record_open("storage"); + char* decrypted_line = furi_alloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + + FlipperFile* flipper_file = flipper_file_alloc(storage); + do { + if(!flipper_file_open_existing(flipper_file, file_name)) { + FURI_LOG_E(TAG, "Unable to open file for read: %s", file_name); + break; + } + if(!flipper_file_read_header(flipper_file, str_temp, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(!flipper_file_read_uint32(flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { + FURI_LOG_E(TAG, "Missing encryption type"); + break; + } + + if(strcmp(string_get_cstr(str_temp), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || + version != SUBGHZ_KEYSTORE_FILE_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + File* file = flipper_file_get_file(flipper_file); + if(encryption != SubGhzKeystoreEncryptionAES256) { + FURI_LOG_E(TAG, "Unknown encryption"); + break; + } + + if(offset < 16) { + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + subghz_keystore_mess_with_iv(iv); + } + + if(!flipper_file_read_string(flipper_file, "Encrypt_data", str_temp)) { + FURI_LOG_E(TAG, "Missing Encrypt_data"); + break; + } + + size_t bufer_size; + if(len <= (16 - offset % 16)) { + bufer_size = 32; + } else { + bufer_size = (((len) / 16) + 2) * 32; + } + furi_assert(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE >= bufer_size / 2); + + char buffer[bufer_size]; + size_t ret = 0; + bool decrypted = true; + //skip the end of the previous line "\n" + storage_file_read(file, buffer, 1); + + size_t size = storage_file_size(file); + size -= storage_file_tell(file); + if(size < (offset * 2 + len * 2)) { + FURI_LOG_E(TAG, "Seek position exceeds file size"); + break; + } + + if(offset >= 16) { + storage_file_seek(file, ((offset / 16) - 1) * 32, false); + ret = storage_file_read(file, buffer, 32); + furi_assert(ret == 32); + for(uint16_t i = 0; i < ret - 1; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(buffer[i], &hi_nibble); + hex_char_to_hex_nibble(buffer[i + 1], &lo_nibble); + iv[i / 2] = (hi_nibble << 4) | lo_nibble; + } + } + + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + + do { + memset(buffer, 0, bufer_size); + ret = storage_file_read(file, buffer, bufer_size); + furi_assert(ret == bufer_size); + for(uint16_t i = 0; i < ret - 1; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(buffer[i], &hi_nibble); + hex_char_to_hex_nibble(buffer[i + 1], &lo_nibble); + buffer[i / 2] = (hi_nibble << 4) | lo_nibble; + } + + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + + if(!furi_hal_crypto_decrypt( + (uint8_t*)buffer, (uint8_t*)decrypted_line, bufer_size / 2)) { + decrypted = false; + FURI_LOG_E(TAG, "Decryption failed"); + break; + } + memcpy(data, (uint8_t*)decrypted_line + (offset - (offset / 16) * 16), len); + + } while(0); + furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + if(decrypted) result = true; + } while(0); + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + + furi_record_close("storage"); + + free(decrypted_line); + + string_clear(str_temp); + + return result; +} diff --git a/lib/subghz/subghz_keystore.h b/lib/subghz/subghz_keystore.h index 4cd388f5..58af35e5 100644 --- a/lib/subghz/subghz_keystore.h +++ b/lib/subghz/subghz_keystore.h @@ -33,7 +33,14 @@ void subghz_keystore_free(SubGhzKeystore* instance); * @param instance - SubGhzKeystore instance * @param filename - const char* full path to the file */ -void subghz_keystore_load(SubGhzKeystore* instance, const char* filename); +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 + */ +bool subghz_keystore_save(SubGhzKeystore* instance, const char* filename, uint8_t* iv); /** Get array of keys and names manufacture * @@ -41,3 +48,22 @@ void subghz_keystore_load(SubGhzKeystore* instance, const char* filename); * @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 + */ +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 + */ +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_parser.c b/lib/subghz/subghz_parser.c index faf0b6dd..93c48cb7 100644 --- a/lib/subghz/subghz_parser.c +++ b/lib/subghz/subghz_parser.c @@ -16,12 +16,16 @@ #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 "subghz_keystore.h" #include #include +#define SUBGHZ_PARSER_TAG "SubGhzParser" + typedef enum { SubGhzProtocolTypeCame, SubGhzProtocolTypeCameTwee, @@ -38,6 +42,8 @@ typedef enum { SubGhzProtocolTypeNeroRadio, SubGhzProtocolTypeScherKhan, SubGhzProtocolTypeKIA, + SubGhzProtocolTypeRAW, + SubGhzProtocolTypeHormann, SubGhzProtocolTypeMax, } SubGhzProtocolType; @@ -109,6 +115,10 @@ SubGhzParser* subghz_parser_alloc() { (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(); return instance; } @@ -143,6 +153,9 @@ void subghz_parser_free(SubGhzParser* instance) { 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_keystore_free(instance->keystore); @@ -197,8 +210,17 @@ void subghz_parser_load_nice_flor_s_file(SubGhzParser* instance, const char* fil (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); +} + void subghz_parser_load_keeloq_file(SubGhzParser* instance, const char* file_name) { - subghz_keystore_load(instance->keystore, file_name); + if (subghz_keystore_load(instance->keystore, file_name)) { + FURI_LOG_I(SUBGHZ_PARSER_TAG, "Successfully loaded keeloq keys from %s", file_name); + } else { + FURI_LOG_W(SUBGHZ_PARSER_TAG, "Failed to load keeloq keysfrom %s", file_name); + } } void subghz_parser_reset(SubGhzParser* instance) { @@ -229,6 +251,14 @@ void subghz_parser_reset(SubGhzParser* instance) { 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]); +} + +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) { @@ -274,4 +304,6 @@ void subghz_parser_parse(SubGhzParser* instance, bool level, uint32_t duration) duration); subghz_protocol_kia_parse( (SubGhzProtocolKIA*)instance->protocols[SubGhzProtocolTypeKIA], level, duration); + subghz_protocol_hormann_parse( + (SubGhzProtocolHormann*)instance->protocols[SubGhzProtocolTypeHormann], level, duration); } diff --git a/lib/subghz/subghz_parser.h b/lib/subghz/subghz_parser.h index 69341c18..7e6c72b8 100644 --- a/lib/subghz/subghz_parser.h +++ b/lib/subghz/subghz_parser.h @@ -3,7 +3,7 @@ #include "protocols/subghz_protocol_common.h" typedef void (*SubGhzProtocolTextCallback)(string_t text, void* context); -typedef void (*SubGhzProtocolCommonCallbackDump)(SubGhzProtocolCommon *parser, void* context); +typedef void (*SubGhzProtocolCommonCallbackDump)(SubGhzProtocolCommon* parser, void* context); typedef struct SubGhzParser SubGhzParser; @@ -33,7 +33,10 @@ SubGhzProtocolCommon* subghz_parser_get_by_name(SubGhzParser* instance, const ch * @param callback - SubGhzProtocolTextCallback callback * @param context */ -void subghz_parser_enable_dump_text(SubGhzParser* instance, SubGhzProtocolTextCallback callback, void* context); +void subghz_parser_enable_dump_text( + SubGhzParser* instance, + SubGhzProtocolTextCallback callback, + void* context); /** Outputting data SubGhzParser from all parsers * @@ -41,7 +44,10 @@ void subghz_parser_enable_dump_text(SubGhzParser* instance, SubGhzProtocolTextCa * @param callback - SubGhzProtocolTextCallback callback * @param context */ -void subghz_parser_enable_dump(SubGhzParser* instance, SubGhzProtocolCommonCallbackDump callback, void* context); +void subghz_parser_enable_dump( + SubGhzParser* instance, + SubGhzProtocolCommonCallbackDump callback, + void* context); /** File name rainbow table Nice Flor-S * @@ -50,6 +56,13 @@ void subghz_parser_enable_dump(SubGhzParser* instance, SubGhzProtocolCommonCallb */ 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 @@ -63,6 +76,8 @@ void subghz_parser_load_keeloq_file(SubGhzParser* instance, const char* file_nam */ 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 diff --git a/lib/subghz/subghz_worker.c b/lib/subghz/subghz_worker.c index 8697b394..f1f05f43 100644 --- a/lib/subghz/subghz_worker.c +++ b/lib/subghz/subghz_worker.c @@ -3,6 +3,8 @@ #include #include +#define TAG "SubGhzWorker" + struct SubGhzWorker { FuriThread* thread; StreamBufferHandle_t stream; @@ -54,7 +56,7 @@ static int32_t subghz_worker_thread_callback(void* context) { xStreamBufferReceive(instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { if(level_duration_is_reset(level_duration)) { - printf("."); + FURI_LOG_E(TAG, "Overrun buffer");; if(instance->overrun_callback) instance->overrun_callback(instance->context); } else { bool level = level_duration_get_level(level_duration); @@ -90,12 +92,12 @@ SubGhzWorker* subghz_worker_alloc() { SubGhzWorker* instance = furi_alloc(sizeof(SubGhzWorker)); instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "subghz_worker"); + furi_thread_set_name(instance->thread, "SubghzWorker"); furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_worker_thread_callback); - instance->stream = xStreamBufferCreate(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); + instance->stream = xStreamBufferCreate(sizeof(LevelDuration) * 2048, sizeof(LevelDuration)); //setting filter instance->filter_running = true; diff --git a/lib/toolbox/flipper-file-cpp.cpp b/lib/toolbox/flipper-file-cpp.cpp deleted file mode 100644 index 84e6d181..00000000 --- a/lib/toolbox/flipper-file-cpp.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "flipper-file-cpp.h" - -FlipperFileCpp::FlipperFileCpp(Storage* storage) { - file = flipper_file_alloc(storage); -} - -FlipperFileCpp::~FlipperFileCpp() { - flipper_file_free(file); -} - -bool FlipperFileCpp::open_read(const char* filename) { - return flipper_file_open_read(file, filename); -} - -bool FlipperFileCpp::new_write(const char* filename) { - return flipper_file_new_write(file, filename); -} - -bool FlipperFileCpp::close() { - return flipper_file_close(file); -} - -bool FlipperFileCpp::read_header(string_t filetype, uint32_t* version) { - return flipper_file_read_header(file, filetype, version); -} - -bool FlipperFileCpp::write_header(string_t filetype, const uint32_t version) { - return flipper_file_write_header(file, filetype, version); -} - -bool FlipperFileCpp::write_header_cstr(const char* filetype, const uint32_t version) { - return flipper_file_write_header_cstr(file, filetype, version); -} - -bool FlipperFileCpp::read_string(const char* key, string_t data) { - return flipper_file_read_string(file, key, data); -} - -bool FlipperFileCpp::write_string(const char* key, string_t data) { - return flipper_file_write_string(file, key, data); -} - -bool FlipperFileCpp::write_string_cstr(const char* key, const char* data) { - return flipper_file_write_string_cstr(file, key, data); -} - -bool FlipperFileCpp::read_uint32(const char* key, uint32_t* data) { - return flipper_file_read_uint32(file, key, data); -} - -bool FlipperFileCpp::write_uint32(const char* key, const uint32_t data) { - return flipper_file_write_uint32(file, key, data); -} - -bool FlipperFileCpp::write_comment(string_t data) { - return flipper_file_write_comment(file, data); -} - -bool FlipperFileCpp::write_comment_cstr(const char* data) { - return flipper_file_write_comment_cstr(file, data); -} - -bool FlipperFileCpp::write_hex_array( - const char* key, - const uint8_t* data, - const uint16_t data_size) { - return flipper_file_write_hex_array(file, key, data, data_size); -} - -bool FlipperFileCpp::read_hex_array(const char* key, uint8_t* data, const uint16_t data_size) { - return flipper_file_read_hex_array(file, key, data, data_size); -} diff --git a/lib/toolbox/flipper-file-cpp.h b/lib/toolbox/flipper-file-cpp.h deleted file mode 100644 index bac19cba..00000000 --- a/lib/toolbox/flipper-file-cpp.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include "flipper-file.h" - -class FlipperFileCpp { -private: - FlipperFile* file; - -public: - FlipperFileCpp(Storage* storage); - ~FlipperFileCpp(); - - bool open_read(const char* filename); - - bool new_write(const char* filename); - - bool close(); - - bool read_header(string_t filetype, uint32_t* version); - - bool write_header(string_t filetype, const uint32_t version); - - bool write_header_cstr(const char* filetype, const uint32_t version); - - bool read_string(const char* key, string_t data); - - bool write_string(const char* key, string_t data); - - bool write_string_cstr(const char* key, const char* data); - - bool read_uint32(const char* key, uint32_t* data); - - bool write_uint32(const char* key, const uint32_t data); - - bool write_comment(string_t data); - - bool write_comment_cstr(const char* data); - - bool write_hex_array(const char* key, const uint8_t* data, const uint16_t data_size); - - bool read_hex_array(const char* key, uint8_t* data, const uint16_t data_size); -}; diff --git a/lib/toolbox/flipper-file.c b/lib/toolbox/flipper-file.c deleted file mode 100644 index e16cd868..00000000 --- a/lib/toolbox/flipper-file.c +++ /dev/null @@ -1,464 +0,0 @@ -#include -#include "flipper-file.h" -#include -#include - -struct FlipperFile { - File* file; -}; - -const char* flipper_file_filetype_key = "Filetype"; -const char* flipper_file_version_key = "Version"; -const char flipper_file_eoln = '\n'; -const char flipper_file_eolr = '\r'; -const char flipper_file_delimiter = ':'; -const char flipper_file_comment = '#'; - -/** - * Writes data to a file as a hexadecimal array. - * @param file - * @param data - * @param data_size - * @return true on success write - */ -bool flipper_file_write_hex_internal(File* file, const uint8_t* data, const uint16_t data_size) { - const uint8_t byte_text_size = 3; - char byte_text[byte_text_size]; - - bool result = true; - uint16_t bytes_written; - for(uint8_t i = 0; i < data_size; i++) { - snprintf(byte_text, byte_text_size, "%02X", data[i]); - - if(i != 0) { - // space - const char space = ' '; - bytes_written = storage_file_write(file, &space, sizeof(space)); - if(bytes_written != sizeof(space)) { - result = false; - break; - } - } - - bytes_written = storage_file_write(file, &byte_text, strlen(byte_text)); - if(bytes_written != strlen(byte_text)) { - result = false; - break; - } - } - - return result; -} - -/** - * Reads a valid key from a file as a string. - * After reading, the rw pointer will be on the flipper_file_delimiter symbol. - * Optimized not to read comments and values into RAM. - * @param file - * @param key - * @return true on success read - */ -bool flipper_file_read_valid_key(File* file, string_t key) { - string_clean(key); - bool found = false; - bool error = false; - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; - bool accumulate = true; - bool new_line = true; - - while(true) { - uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); - if(bytes_were_read == 0) break; - - for(uint16_t i = 0; i < bytes_were_read; i++) { - if(buffer[i] == flipper_file_eoln) { - // EOL found, clean data, start accumulating data and set the new_line flag - string_clean(key); - accumulate = true; - new_line = true; - } else if(buffer[i] == flipper_file_eolr) { - // Ignore - } else if(buffer[i] == flipper_file_comment && new_line) { - // if there is a comment character and we are at the beginning of a new line - // do not accumulate comment data and reset the new_line flag - accumulate = false; - new_line = false; - } else if(buffer[i] == flipper_file_delimiter) { - if(new_line) { - // we are on a "new line" and found the delimiter - // this can only be if we have previously found some kind of key, so - // clear the data, set the flag that we no longer want to accumulate data - // and reset the new_line flag - string_clean(key); - accumulate = false; - new_line = false; - } else { - // parse the delimiter only if we are accumulating data - if(accumulate) { - // we found the delimiter, move the rw pointer to the correct location - // and signal that we have found something - // TODO negative seek - uint64_t position = storage_file_tell(file); - position = position - bytes_were_read + i; - if(!storage_file_seek(file, position, true)) { - error = true; - break; - } - - found = true; - break; - } - } - } else { - // just new symbol, reset the new_line flag - new_line = false; - if(accumulate) { - // and accumulate data if we want - string_push_back(key, buffer[i]); - } - } - } - - if(found || error) break; - } - - return found; -} - -/** - * Sets rw pointer to the data after the key - * @param file - * @param key - * @return true if key was found - */ -bool flipper_file_seek_to_key(File* file, const char* key) { - bool found = false; - string_t readed_key; - - string_init(readed_key); - - // TODO optimize this to search from a stored rw pointer - if(storage_file_seek(file, 0, true)) { - while(!storage_file_eof(file)) { - if(flipper_file_read_valid_key(file, readed_key)) { - if(string_cmp_str(readed_key, key) == 0) { - uint64_t position = storage_file_tell(file); - if(!storage_file_seek(file, position + 2, true)) break; - - found = true; - break; - } - } - } - } - string_clear(readed_key); - - return found; -} - -/** - * Reads data as a string from the stored rw pointer to the \r or \n symbol position - * @param file - * @param str_result - * @return true on success read - */ -bool flipper_file_read_until(File* file, string_t str_result) { - string_clean(str_result); - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; - - do { - uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); - if(bytes_were_read == 0) break; - - bool result = false; - bool error = false; - for(uint16_t i = 0; i < bytes_were_read; i++) { - if(buffer[i] == flipper_file_eoln) { - // TODO negative seek - uint64_t position = storage_file_tell(file); - position = position - bytes_were_read + i; - if(!storage_file_seek(file, position, true)) { - error = true; - break; - } - - result = true; - break; - } else if(buffer[i] == flipper_file_eolr) { - // Ignore - } else { - string_push_back(str_result, buffer[i]); - } - } - - if(result || error) { - break; - } - } while(true); - - return string_size(str_result) != 0; -} - -/** - * Reads single hexadecimal data from a file to byte - * @param file - * @param byte - * @return bool - */ -bool flipper_file_read_hex_byte(File* file, uint8_t* byte) { - uint8_t hi_nibble_value, low_nibble_value; - uint8_t text[3]; - bool result = false; - - uint16_t bytes_were_read = storage_file_read(file, text, 3); - if(bytes_were_read >= 2) { - if(text[0] != ' ') { - if(hex_char_to_hex_nibble(text[0], &hi_nibble_value) && - hex_char_to_hex_nibble(text[1], &low_nibble_value)) { - *byte = (hi_nibble_value << 4) | low_nibble_value; - result = true; - } - } else { - if(hex_char_to_hex_nibble(text[1], &hi_nibble_value) && - hex_char_to_hex_nibble(text[2], &low_nibble_value)) { - *byte = (hi_nibble_value << 4) | low_nibble_value; - result = true; - } - } - } - - return result; -} - -FlipperFile* flipper_file_alloc(Storage* storage) { - FlipperFile* flipper_file = malloc(sizeof(FlipperFile)); - flipper_file->file = storage_file_alloc(storage); - return flipper_file; -} - -void flipper_file_free(FlipperFile* flipper_file) { - furi_assert(flipper_file); - if(storage_file_is_open(flipper_file->file)) { - storage_file_close(flipper_file->file); - } - storage_file_free(flipper_file->file); - free(flipper_file); -} - -bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename) { - furi_assert(flipper_file); - bool result = storage_file_open(flipper_file->file, filename, FSAM_READ, FSOM_OPEN_EXISTING); - return result; -} - -bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename) { - furi_assert(flipper_file); - bool result = storage_file_open(flipper_file->file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS); - return result; -} - -bool flipper_file_close(FlipperFile* flipper_file) { - furi_assert(flipper_file); - if(storage_file_is_open(flipper_file->file)) { - return storage_file_close(flipper_file->file); - } - return true; -} - -bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version) { - bool result = false; - do { - result = flipper_file_read_string(flipper_file, flipper_file_filetype_key, filetype); - if(!result) break; - result = flipper_file_read_uint32(flipper_file, flipper_file_version_key, version); - if(!result) break; - } while(false); - - return result; -} - -bool flipper_file_write_header( - FlipperFile* flipper_file, - string_t filetype, - const uint32_t version) { - bool result = false; - do { - result = flipper_file_write_string(flipper_file, flipper_file_filetype_key, filetype); - if(!result) break; - result = flipper_file_write_uint32(flipper_file, flipper_file_version_key, version); - if(!result) break; - } while(false); - - return result; -} - -bool flipper_file_write_header_cstr( - FlipperFile* flipper_file, - const char* filetype, - const uint32_t version) { - bool result = false; - string_t value; - string_init_set(value, filetype); - result = flipper_file_write_header(flipper_file, value, version); - string_clear(value); - return result; -} - -bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data) { - furi_assert(flipper_file); - - bool result = false; - if(flipper_file_seek_to_key(flipper_file->file, key)) { - if(flipper_file_read_until(flipper_file->file, data)) { - result = true; - } - } - return result; -} - -bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data) { - furi_assert(flipper_file); - - bool result = false; - do { - uint16_t bytes_written; - bytes_written = storage_file_write(flipper_file->file, key, strlen(key)); - if(bytes_written != strlen(key)) break; - - const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; - bytes_written = - storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer)); - if(bytes_written != sizeof(delimiter_buffer)) break; - - bytes_written = - storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data)); - if(bytes_written != string_size(data)) break; - - bytes_written = - storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); - if(bytes_written != sizeof(flipper_file_eoln)) break; - - result = true; - } while(false); - - return result; -} - -bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { - bool result = false; - string_t value; - string_init_set(value, data); - result = flipper_file_write_string(flipper_file, key, value); - string_clear(value); - return result; -} - -bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data) { - bool result = false; - string_t value; - string_init(value); - - result = flipper_file_read_string(flipper_file, key, value); - if(result) { - int ret = sscanf(string_get_cstr(value), "%" PRIu32, data); - if(ret != 1) result = false; - } - - string_clear(value); - return result; -} - -bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data) { - bool result = false; - string_t value; - string_init_printf(value, "%" PRIu32, data); - result = flipper_file_write_string(flipper_file, key, value); - string_clear(value); - return result; -} - -bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data) { - furi_assert(flipper_file); - - bool result = false; - do { - uint16_t bytes_written; - const char comment_buffer[2] = {flipper_file_comment, ' '}; - bytes_written = - storage_file_write(flipper_file->file, comment_buffer, sizeof(comment_buffer)); - if(bytes_written != sizeof(comment_buffer)) break; - - bytes_written = - storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data)); - if(bytes_written != string_size(data)) break; - - bytes_written = - storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); - if(bytes_written != sizeof(flipper_file_eoln)) break; - - result = true; - } while(false); - - return result; -} - -bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data) { - bool result = false; - string_t value; - string_init_set(value, data); - result = flipper_file_write_comment(flipper_file, value); - string_clear(value); - return result; -} - -bool flipper_file_write_hex_array( - FlipperFile* flipper_file, - const char* key, - const uint8_t* data, - const uint16_t data_size) { - furi_assert(flipper_file); - - bool result = false; - do { - uint16_t bytes_written; - bytes_written = storage_file_write(flipper_file->file, key, strlen(key)); - if(bytes_written != strlen(key)) break; - - const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; - bytes_written = - storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer)); - if(bytes_written != sizeof(delimiter_buffer)) break; - - if(!flipper_file_write_hex_internal(flipper_file->file, data, data_size)) break; - - bytes_written = - storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); - if(bytes_written != sizeof(flipper_file_eoln)) break; - - result = true; - } while(false); - - return result; -} - -bool flipper_file_read_hex_array( - FlipperFile* flipper_file, - const char* key, - uint8_t* data, - const uint16_t data_size) { - furi_assert(flipper_file); - - bool result = false; - if(flipper_file_seek_to_key(flipper_file->file, key)) { - result = true; - for(uint16_t i = 0; i < data_size; i++) { - if(!flipper_file_read_hex_byte(flipper_file->file, &data[i])) { - result = false; - break; - } - } - } - return result; -} \ No newline at end of file diff --git a/lib/toolbox/hex.c b/lib/toolbox/hex.c index cc869beb..8b443e12 100644 --- a/lib/toolbox/hex.c +++ b/lib/toolbox/hex.c @@ -13,4 +13,16 @@ bool hex_char_to_hex_nibble(char c, uint8_t* nibble) { } else { return false; } +} + +bool hex_chars_to_uint8(char hi, char low, uint8_t* value) { + uint8_t hi_nibble_value, low_nibble_value; + + if(hex_char_to_hex_nibble(hi, &hi_nibble_value) && + hex_char_to_hex_nibble(low, &low_nibble_value)) { + *value = (hi_nibble_value << 4) | low_nibble_value; + return true; + } else { + return false; + } } \ No newline at end of file diff --git a/lib/toolbox/hex.h b/lib/toolbox/hex.h index 2c5d8404..ac67549a 100644 --- a/lib/toolbox/hex.h +++ b/lib/toolbox/hex.h @@ -7,14 +7,22 @@ extern "C" { #endif /** - * @brief Convert ASCII hex value to nibble - * + * Convert ASCII hex value to nibble * @param c ASCII character * @param nibble nibble pointer, output * @return bool conversion status */ bool hex_char_to_hex_nibble(char c, uint8_t* nibble); +/** + * Convert ASCII hex values to byte + * @param hi hi nibble text + * @param low low nibble text + * @param value output value + * @return bool conversion status + */ +bool hex_chars_to_uint8(char hi, char low, uint8_t* value); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/toolbox/level_duration.h b/lib/toolbox/level_duration.h index 958c3890..bef0b6ec 100644 --- a/lib/toolbox/level_duration.h +++ b/lib/toolbox/level_duration.h @@ -9,6 +9,7 @@ #define LEVEL_DURATION_RESET 0U #define LEVEL_DURATION_LEVEL_LOW 1U #define LEVEL_DURATION_LEVEL_HIGH 2U +#define LEVEL_DURATION_WAIT 3U #define LEVEL_DURATION_RESERVED 0x800000U typedef struct { @@ -29,10 +30,20 @@ static inline LevelDuration level_duration_reset() { return level_duration; } +static inline LevelDuration level_duration_wait() { + LevelDuration level_duration; + level_duration.level = LEVEL_DURATION_WAIT; + return level_duration; +} + static inline bool level_duration_is_reset(LevelDuration level_duration) { return level_duration.level == LEVEL_DURATION_RESET; } +static inline bool level_duration_is_wait(LevelDuration level_duration) { + return level_duration.level == LEVEL_DURATION_WAIT; +} + static inline bool level_duration_get_level(LevelDuration level_duration) { return level_duration.level == LEVEL_DURATION_LEVEL_HIGH; } diff --git a/lib/toolbox/manchester-decoder.c b/lib/toolbox/manchester-decoder.c index cbeab44a..2affe5cf 100644 --- a/lib/toolbox/manchester-decoder.c +++ b/lib/toolbox/manchester-decoder.c @@ -20,10 +20,10 @@ bool manchester_advance( new_state = manchester_reset_state; } else { if(new_state == ManchesterStateMid0) { - *data = false; + if (data) *data = false; result = true; } else if(new_state == ManchesterStateMid1) { - *data = true; + if (data) *data = true; result = true; } } diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c new file mode 100644 index 00000000..653e51c8 --- /dev/null +++ b/lib/toolbox/saved_struct.c @@ -0,0 +1,149 @@ +#include "saved_struct.h" +#include +#include +#include + +#define TAG "SavedStruct" + +typedef struct { + uint8_t magic; + uint8_t version; + uint8_t checksum; + uint8_t flags; + uint32_t timestamp; +} SavedStructHeader; + +bool saved_struct_save(const char* path, + void* data, + size_t size, + uint8_t magic, + uint8_t version) { + furi_assert(path); + furi_assert(data); + furi_assert(size); + SavedStructHeader header; + + FURI_LOG_I(TAG, "Saving \"%s\"", path); + + // Store + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + bool result = true; + bool saved = storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS); + if(!saved) { + FURI_LOG_E( + TAG, + "Open failed \"%s\". Error: \'%s\'", + path, + storage_file_get_error_desc(file)); + result = false; + } + + if(result) { + // Calculate checksum + uint8_t checksum = 0; + uint8_t* source = data; + for(size_t i = 0; i < size; i++) { + checksum += source[i]; + } + // Set header + header.magic = magic; + header.version = version; + header.checksum = checksum; + header.flags = 0; + header.timestamp = 0; + + uint16_t bytes_count = storage_file_write(file, &header, sizeof(header)); + bytes_count += storage_file_write(file, data, size); + + if(bytes_count != (size + sizeof(header))) { + FURI_LOG_E( + TAG, + "Write failed \"%s\". Error: \'%s\'", + path, + storage_file_get_error_desc(file)); + result = false; + } + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close("storage"); + return result; +} + +bool saved_struct_load(const char* path, + void* data, + size_t size, + uint8_t magic, + uint8_t version) { + FURI_LOG_I(TAG, "Loading \"%s\"", path); + + SavedStructHeader header; + + uint8_t* data_read = furi_alloc(size); + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + bool result = true; + bool loaded = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); + if (!loaded) { + FURI_LOG_E( + TAG, + "Failed to read \"%s\". Error: %s", + path, + storage_file_get_error_desc(file)); + result = false; + } + + if (result) { + uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + bytes_count += storage_file_read(file, data_read, size); + + if(bytes_count != (sizeof(SavedStructHeader) + size)) { + FURI_LOG_E(TAG, "Size mismatch of file \"%s\"", path); + result = false; + } + } + + if(result && (header.magic != magic || header.version != version)) { + FURI_LOG_E( + TAG, + "Magic(%d != %d) or Version(%d != %d) mismatch of file \"%s\"", + header.magic, + magic, + header.version, + version, + path); + result = false; + } + + if(result) { + uint8_t checksum = 0; + const uint8_t* source = (const uint8_t*)data_read; + for(size_t i = 0; i < size; i++) { + checksum += source[i]; + } + + if(header.checksum != checksum) { + FURI_LOG_E( + TAG, + "Checksum(%d != %d) mismatch of file \"%s\"", + header.checksum, + checksum, + path); + result = false; + } + } + + if (result) { + memcpy(data, data_read, size); + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close("storage"); + free(data_read); + + return result; +} + diff --git a/lib/toolbox/saved_struct.h b/lib/toolbox/saved_struct.h new file mode 100644 index 00000000..09b7024f --- /dev/null +++ b/lib/toolbox/saved_struct.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +bool saved_struct_load(const char* path, + void* data, + size_t size, + uint8_t magic, + uint8_t version); + +bool saved_struct_save(const char* path, + void* data, + size_t size, + uint8_t magic, + uint8_t version); + diff --git a/make/rules.mk b/make/rules.mk index 390b9ee0..7f4332ab 100644 --- a/make/rules.mk +++ b/make/rules.mk @@ -32,7 +32,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 +all: $(OBJ_DIR)/$(PROJECT).elf $(OBJ_DIR)/$(PROJECT).hex $(OBJ_DIR)/$(PROJECT).bin $(OBJ_DIR)/$(PROJECT).dfu $(OBJ_DIR)/$(PROJECT).json $(OBJ_DIR)/$(PROJECT).elf: $(OBJECTS) @echo "\tLD\t" $@ @@ -54,6 +54,10 @@ $(OBJ_DIR)/$(PROJECT).dfu: $(OBJ_DIR)/$(PROJECT).hex -o $(OBJ_DIR)/$(PROJECT).dfu \ -l "Flipper Zero $(shell echo $(TARGET) | tr a-z A-Z)" > /dev/null +$(OBJ_DIR)/$(PROJECT).json: $(OBJ_DIR)/$(PROJECT).dfu + @echo "\tJSON\t" $@ + @python3 ../scripts/meta.py -p $(PROJECT) $(CFLAGS) > $(OBJ_DIR)/$(PROJECT).json + $(OBJ_DIR)/%.o: %.c $(OBJ_DIR)/BUILD_FLAGS @echo "\tCC\t" $< "->" $@ @$(CC) $(CFLAGS) -c $< -o $@ @@ -96,7 +100,7 @@ debug_other: -ex "svd_load $(SVD_FILE)" \ -blackmagic: flash +blackmagic: arm-none-eabi-gdb-py \ -ex 'target extended-remote $(BLACKMAGIC)' \ -ex 'monitor swdp_scan' \ @@ -125,14 +129,6 @@ zz: clean zzz: clean $(MAKE) debug -FORMAT_SOURCES := $(shell find ../applications -iname "*.h" -o -iname "*.c" -o -iname "*.cpp") -FORMAT_SOURCES += $(shell find ../bootloader -iname "*.h" -o -iname "*.c" -o -iname "*.cpp") -FORMAT_SOURCES += $(shell find ../core -iname "*.h" -o -iname "*.c" -o -iname "*.cpp") - -format: - @echo "Formatting sources with clang-format" - @clang-format -style=file -i $(FORMAT_SOURCES) - 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 diff --git a/make/toolchain.mk b/make/toolchain.mk index 9b4f6f8e..9b7368c2 100644 --- a/make/toolchain.mk +++ b/make/toolchain.mk @@ -18,11 +18,11 @@ BIN = $(CP) -O binary -S DEBUG ?= 1 COMPACT ?= 0 ifeq ($(DEBUG), 1) -CFLAGS += -DFURI_DEBUG -DNDEBUG -DLFS_NO_ASSERT -Og -g +CFLAGS += -DFURI_DEBUG -DNDEBUG -Og -g else ifeq ($(COMPACT), 1) -CFLAGS += -DFURI_NDEBUG -DNDEBUG -DLFS_NO_ASSERT -Os +CFLAGS += -DFURI_NDEBUG -DNDEBUG -Os else -CFLAGS += -DFURI_NDEBUG -DNDEBUG -DLFS_NO_ASSERT -Og +CFLAGS += -DFURI_NDEBUG -DNDEBUG -Og endif CFLAGS += -fdata-sections -ffunction-sections -fno-math-errno -fstack-usage -MMD -MP -MF"$(@:%.o=%.d)" diff --git a/scripts/flash.py b/scripts/flash.py index 0e920076..be65b1dc 100755 --- a/scripts/flash.py +++ b/scripts/flash.py @@ -18,14 +18,14 @@ class Main(App): self.parser_wipe = self.subparsers.add_parser("wipe", help="Wipe MCU Flash") self.parser_wipe.set_defaults(func=self.wipe) # Core 1 boot - self.parser_core1boot = self.subparsers.add_parser( - "core1boot", help="Flash Core1 Bootloader" + self.parser_core1bootloader = self.subparsers.add_parser( + "core1bootloader", help="Flash Core1 Bootloader" ) - self._addArgsSWD(self.parser_core1boot) - self.parser_core1boot.add_argument( + self._addArgsSWD(self.parser_core1bootloader) + self.parser_core1bootloader.add_argument( "bootloader", type=str, help="Bootloader binary" ) - self.parser_core1boot.set_defaults(func=self.core1boot) + self.parser_core1bootloader.set_defaults(func=self.core1bootloader) # Core 1 firmware self.parser_core1firmware = self.subparsers.add_parser( "core1firmware", help="Flash Core1 Firmware" @@ -37,7 +37,7 @@ class Main(App): self.parser_core1firmware.set_defaults(func=self.core1firmware) # Core 1 all self.parser_core1 = self.subparsers.add_parser( - "core1", help="Flash Core1 Boot and Firmware" + "core1", help="Flash Core1 Bootloader and Firmware" ) self._addArgsSWD(self.parser_core1) self.parser_core1.add_argument("bootloader", type=str, help="Bootloader binary") @@ -97,7 +97,7 @@ class Main(App): self.logger.info(f"Complete") return 0 - def core1boot(self): + def core1bootloader(self): self.logger.info(f"Flashing bootloader") cp = CubeProgrammer(self.args.port) cp.flashBin("0x08000000", self.args.bootloader) diff --git a/scripts/flipper/copro.py b/scripts/flipper/copro.py index a7f3b95f..f5198e44 100644 --- a/scripts/flipper/copro.py +++ b/scripts/flipper/copro.py @@ -19,7 +19,7 @@ MANIFEST_TEMPLATE = { "minor": 12, "sub": 1, "branch": 0, - "release": 7, + "release": 1, }, "files": [], }, diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 589c5768..53c01d8c 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -1,4 +1,5 @@ import os +import sys import serial import time import hashlib @@ -65,7 +66,7 @@ class FlipperStorage: self.port.reset_input_buffer() # Send a command with a known syntax to make sure the buffer is flushed self.send("device_info\r") - self.read.until("hardware_model :") + self.read.until("hardware_model") # And read buffer until we get prompt self.read.until(self.CLI_PROMPT) @@ -143,7 +144,7 @@ class FlipperStorage: walk_dirs = [] path = path.replace("//", "/") - self.send_and_wait_eol('storage list "' + path + '"\r') + self.send_and_wait_eol(f'storage list "{path}"\r') data = self.read.until(self.CLI_PROMPT) lines = data.split(b"\r\n") @@ -198,9 +199,7 @@ class FlipperStorage: if size == 0: break - self.send_and_wait_eol( - 'storage write_chunk "' + filename_to + '" ' + str(size) + "\r" - ) + self.send_and_wait_eol(f'storage write_chunk "{filename_to}" {size}\r') answer = self.read.until(self.CLI_EOL) if self.has_error(answer): self.last_error = self.get_error(answer) @@ -214,9 +213,8 @@ class FlipperStorage: percent = str(math.ceil(file.tell() / filesize * 100)) total_chunks = str(math.ceil(filesize / buffer_size)) current_chunk = str(math.ceil(file.tell() / buffer_size)) - print( - percent + "%, chunk " + current_chunk + " of " + total_chunks, end="\r" - ) + sys.stdout.write(f"\r{percent}%, chunk {current_chunk} of {total_chunks}") + sys.stdout.flush() file.close() print() return True @@ -246,9 +244,8 @@ class FlipperStorage: percent = str(math.ceil(readed_size / size * 100)) total_chunks = str(math.ceil(size / buffer_size)) current_chunk = str(math.ceil(readed_size / buffer_size)) - print( - percent + "%, chunk " + current_chunk + " of " + total_chunks, end="\r" - ) + sys.stdout.write(f"\r{percent}%, chunk {current_chunk} of {total_chunks}") + sys.stdout.flush() print() self.read.until(self.CLI_PROMPT) return filedata diff --git a/scripts/meta.py b/scripts/meta.py new file mode 100644 index 00000000..3cb8cb78 --- /dev/null +++ b/scripts/meta.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import argparse +import json + + +class Main: + def __init__(self): + # parse CFLAGS + self.parser = argparse.ArgumentParser(allow_abbrev=False) + self.parser.add_argument("-p", dest="project", required=True) + self.parser.add_argument("-DBUILD_DATE", dest="build_date", required=True) + self.parser.add_argument("-DGIT_COMMIT", dest="commit", required=True) + self.parser.add_argument("-DGIT_BRANCH", dest="branch", required=True) + self.parser.add_argument("-DTARGET", dest="target", type=int, required=True) + + def __call__(self): + self.args, _ = self.parser.parse_known_args() + + meta = {} + for k, v in vars(self.args).items(): + if k == "project": + continue + if isinstance(v, str): + v = v.strip('"') + meta[self.args.project + "_" + k] = v + + print(json.dumps(meta, indent=4)) + + +if __name__ == "__main__": + Main()() diff --git a/scripts/otp.py b/scripts/otp.py index c16f98fe..edad4b07 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -170,9 +170,9 @@ class Main(App): os.remove(filename) except Exception as e: self.logger.exception(e) - return 0 + return 1 - return 1 + return 0 def flash_second(self): self.logger.info(f"Flashing second block of OTP")